From 45d2c8234a5142df3652c601b55dd68c4545431c Mon Sep 17 00:00:00 2001 From: zhenyan121 <104683324+zhenyan121@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:21:05 +0800 Subject: [PATCH] feature: chat (#36) * fix(config): load config on demand in get/set instead of asserting * feat(chat): implement in-game chat box with UTF-8 support - Add ChatMessage struct and ChatBox widget for displaying and sending messages - Introduce split_utf8() in text_tools for splitting UTF-8 strings by character count - Add TimeTools utility (get_time_ticks) for message timestamps - Update TextField with start_typing/stop_typing methods and set_typing() - Integrate text input handling in WorldScene and WorldUIManager for chat - Add utf8cpp library for robust UTF-8 validation and processing - Add ChatBox rendering and input logic (src/ui/chat_box.cpp) - Register ChatBox source in CMakeLists.txt - Fix Renderer::render_lable to skip rendering if VAO is null - Update credits to include utf8cpp library * fix(ui): correct chat box layout with dynamic height and bottom anchor * feat(chat): implement client-server chat messaging Add protobuf definition for ChatMsg, new packet ID CHAT_MSG (4001), and handle sending/receiving chat messages on both client and server side. Wire up UI components (ChatBox, TextField) to allow message input and display, including a message queue in ClientWorld for thread-safe processing. Also add default initialization for ChatMessage::time. * feat(chat): distinguish between send and cancel in chat input * feat(ui): add background to Label widget and refactor debug overlay * refactor(ui): decouple ChatBox from image dependencies, allow custom TextField backgrounds TextField's background is now a generic `Widget` pointer, enabling use of any widget (e.g., `Rect`, `Image`) as background. `ChatBox` no longer creates its own `TextField`; it accepts one via `set_text_field`. Removed `set_d_image` and `set_default_image` in favor of `set_background`. Added a shared default image constant in `default_image.hpp`. Updated `host_game_ui.cpp`, `join_game_ui.cpp`, and `world_ui_manager.cpp` to construct backgrounds accordingly. * refactor(ui): move width, height, and fill properties to base Widget Consolidate width, height, and fill (fill_parent, fill_width, fill_height) into the base Widget class. Replace per-widget set_fill(bool) with set_fill_parent(bool), set_fill_width(bool), and set_fill_height(bool). * refactor(ui): generalize Label background, add ChatBox width method * feat(chat): implement message expiration after 5 seconds * feat(ui): add word wrap for chat messages with font width measurement * feat(audio): implement voice chat using Opus codec Add voice chat support with Opus encoding/decoding, push-to-talk (V key), and new audio recording infrastructure. * feat(audio): implement voice chat with Opus codec and streaming audio source Add AudioStreamSource for continuous PCM playback. Refactor voice send/receive to encode with Opus and stream to a dedicated source instead of per-frame sources. Enable voice transmission over network. * feat(audio): add spatial voice source with underwater effects Update voice source to support spatial positioning, low-pass filter, and reverb effect for underwater immersion. Adjust source pool size and low-pass filter parameter. Correct receive_voice parameter type. * feat(settings): add player voice volume and voice input type options - Add player voice volume slider and voice input type combo in settings UI. - Add localization strings for player voice and voice input types. - Add config overloads for const char* default values. - Implement voice input types (PTT, always, off) in world scene. - Refactor WorldScene member order and add load_config method. - Update audio engine to use player voice volume. - Downgrade some logging to debug. * fix(event): delegate error UI events to each handler Move error UI event check from top of handle_event into each individual event handler (mouse_move, mouse_button, mouse_wheel, key, text_input). Also flatten window resize dispatch: all sub-handlers are called regardless of previous handler return values, and the method always returns false. * feat(chat): add colored system messages for player join/exit * chore(logging): reduce verbosity for audio loading and input events * fix(settings): fix typo in config key "vloume" * feat(ui): add microphone icon when recording voice * feat(voice): limit voice message range to 48 units * chore(logging): downgrade text input log level to debug * feat(credits): add license identifiers and reorganize license files * feat: add sensitive word filter for chat messages Implement AC automaton-based filtering using a sensitive lexicon loaded from a JSON asset file. The filter is applied to both player names and chat messages before broadcasting, and can be toggled via the `sensitive_filter` config option. * feat(server): add config option to disable voice chat * fix: correct utf8 validation, add missing include, remove debug log --- CMakeLists.txt | 1 + assets/SensitiveLexicon.json | 4 + assets/lang/en_US.json | 7 +- assets/lang/zh_CN.json | 7 +- assets/{fonts => licenses}/OFL-1.1.txt | 0 assets/licenses/OPENAL_SOFT_LICENSE.txt | 437 ++++++++++++++ assets/sound/ambient/water/bubble001.ogg | Bin 30671 -> 8860 bytes assets/sound/ambient/water/bubble002.ogg | Bin 16017 -> 5788 bytes assets/texture/ui/microphone.png | Bin 0 -> 137 bytes cmake/Dependencies.cmake | 2 +- cmake/modules/FindOpus.cmake | 25 + include/Cubed/audio/audio_engine.hpp | 21 +- include/Cubed/audio/audio_recording.hpp | 33 ++ include/Cubed/audio/audio_source.hpp | 6 +- include/Cubed/audio/audio_stream_source.hpp | 46 ++ include/Cubed/audio/source_pool.hpp | 5 +- include/Cubed/config.hpp | 17 +- include/Cubed/gameplay/chat_message.hpp | 15 + include/Cubed/gameplay/client_world.hpp | 16 + include/Cubed/gameplay/network_client.hpp | 1 + include/Cubed/gameplay/packet.hpp | 8 + include/Cubed/gameplay/server_world.hpp | 10 + include/Cubed/scene/settings_scene.hpp | 1 + include/Cubed/scene/world_scene.hpp | 22 +- include/Cubed/tools/font.hpp | 1 + include/Cubed/tools/sensitive_filter.hpp | 24 + include/Cubed/tools/text_tools.hpp | 28 + include/Cubed/tools/time_tools.hpp | 10 + include/Cubed/ui/button.hpp | 6 +- include/Cubed/ui/chat_box.hpp | 56 ++ include/Cubed/ui/color.hpp | 35 +- include/Cubed/ui/column_layout.hpp | 4 +- include/Cubed/ui/default_image.hpp | 5 + include/Cubed/ui/image.hpp | 8 - include/Cubed/ui/label.hpp | 4 +- include/Cubed/ui/rect.hpp | 8 - include/Cubed/ui/slider.hpp | 7 +- include/Cubed/ui/text_field.hpp | 24 +- include/Cubed/ui/widget.hpp | 23 +- include/Cubed/ui/world_ui_manager.hpp | 11 + include/utf8cpp/.clang-format | 2 + include/utf8cpp/utf8.h | 46 ++ include/utf8cpp/utf8/checked.h | 359 ++++++++++++ include/utf8cpp/utf8/core.h | 610 ++++++++++++++++++++ include/utf8cpp/utf8/cpp11.h | 70 +++ include/utf8cpp/utf8/cpp17.h | 96 +++ include/utf8cpp/utf8/cpp20.h | 124 ++++ include/utf8cpp/utf8/unchecked.h | 286 +++++++++ src/CMakeLists.txt | 4 + src/app.cpp | 6 +- src/audio/audio_buffer.cpp | 10 +- src/audio/audio_engine.cpp | 118 +++- src/audio/audio_loader.cpp | 16 +- src/audio/audio_recording.cpp | 77 +++ src/audio/audio_source.cpp | 13 +- src/audio/audio_stream_source.cpp | 142 +++++ src/audio/source_pool.cpp | 26 +- src/debug_collector.cpp | 104 ++-- src/gameplay/client_player.cpp | 3 +- src/gameplay/client_world.cpp | 40 ++ src/gameplay/network_client.cpp | 20 +- src/gameplay/server_world.cpp | 114 +++- src/gameplay/session.cpp | 12 + src/proto/auth/auth.proto | 1 + src/proto/chat/chat.proto | 14 + src/proto/packet.proto | 3 +- src/render/renderer.cpp | 3 + src/scene/settings_scene.cpp | 3 + src/scene/world_scene.cpp | 144 ++++- src/tools/font.cpp | 22 + src/tools/sensitive_filter.cpp | 118 ++++ src/ui/button.cpp | 22 +- src/ui/chat_box.cpp | 200 +++++++ src/ui/column_layout.cpp | 2 +- src/ui/credits_ui.cpp | 44 +- src/ui/error_ui.cpp | 4 +- src/ui/host_game_ui.cpp | 15 +- src/ui/image.cpp | 37 +- src/ui/join_game_ui.cpp | 10 +- src/ui/label.cpp | 43 +- src/ui/main_menu_ui_manager.cpp | 2 +- src/ui/pause_menu_ui_manager.cpp | 2 +- src/ui/rect.cpp | 42 +- src/ui/settings_ui.cpp | 32 +- src/ui/slider.cpp | 22 +- src/ui/text_field.cpp | 110 ++-- src/ui/ui_vertex_data.cpp | 6 +- src/ui/widget.cpp | 53 +- src/ui/world_ui_manager.cpp | 97 +++- vcpkg.json | 1 + 90 files changed, 3931 insertions(+), 357 deletions(-) create mode 100644 assets/SensitiveLexicon.json rename assets/{fonts => licenses}/OFL-1.1.txt (100%) create mode 100644 assets/licenses/OPENAL_SOFT_LICENSE.txt create mode 100644 assets/texture/ui/microphone.png create mode 100644 cmake/modules/FindOpus.cmake create mode 100644 include/Cubed/audio/audio_recording.hpp create mode 100644 include/Cubed/audio/audio_stream_source.hpp create mode 100644 include/Cubed/gameplay/chat_message.hpp create mode 100644 include/Cubed/tools/sensitive_filter.hpp create mode 100644 include/Cubed/tools/time_tools.hpp create mode 100644 include/Cubed/ui/chat_box.hpp create mode 100644 include/Cubed/ui/default_image.hpp create mode 100644 include/utf8cpp/.clang-format create mode 100644 include/utf8cpp/utf8.h create mode 100644 include/utf8cpp/utf8/checked.h create mode 100644 include/utf8cpp/utf8/core.h create mode 100644 include/utf8cpp/utf8/cpp11.h create mode 100644 include/utf8cpp/utf8/cpp17.h create mode 100644 include/utf8cpp/utf8/cpp20.h create mode 100644 include/utf8cpp/utf8/unchecked.h create mode 100644 src/audio/audio_recording.cpp create mode 100644 src/audio/audio_stream_source.cpp create mode 100644 src/proto/chat/chat.proto create mode 100644 src/tools/sensitive_filter.cpp create mode 100644 src/ui/chat_box.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c414dbb..d49f34a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,7 @@ target_link_libraries(${PROJECT_NAME} zstd::zstd OpenAL::OpenAL harfbuzz::harfbuzz + Opus::Opus $<$:ws2_32> ) diff --git a/assets/SensitiveLexicon.json b/assets/SensitiveLexicon.json new file mode 100644 index 0000000..92b5c0e --- /dev/null +++ b/assets/SensitiveLexicon.json @@ -0,0 +1,4 @@ +{ + "lastUpdateDate": "2025/07/25", + "words": ["福音会","中国教徒","统一教","观音法门","清海无上师","盘古","李洪志","志洪李","李宏志","轮功","法轮","轮法功","三去车仑","氵去车仑","发论工","法x功","法o功","法0功","法一轮一功","轮子功","车仑工力","法lun","fa轮","法lg","flg","fl功","falungong","大法弟子","大纪元","dajiyuan","明慧网","明慧周报","正见网","新唐人","伪火","退党","tuidang","退dang","超越红墙","自fen","真善忍","九评","9评","9ping","九ping","jiuping","藏字石","集体自杀","自sha","zi杀","suicide","titor","逢8必灾","逢八必灾","逢9必乱","逢九必乱","朱瑟里诺","根达亚文明","诺查丹玛斯","人类灭亡进程表","按照马雅历法","推背图","推bei图","济世灵文","诸世纪","电狗","电话定位器","电话拦截器","电话窃听","电话监","电话交友","电话追杀系统","电击枪","电鸡","电警棒","枪出售","枪的制","枪货到","枪决女犯","枪模","枪手","枪销售","枪械制","枪子弹","售步枪","售纯度","售单管","售弹簧刀","售防身","售狗子","售虎头","售火药","售假币","售健卫","售军用","售猎枪","售氯胺","售麻醉","售枪支","售热武","售三棱","售手枪","售五四","售一元硬","售子弹","售左轮","亚砷(酸)酐","亚砷酸钾","亚砷酸钠","亚硒酸","亚硒酸二钠","亚硒酸镁","亚硒酸钠","亚硒酸氢钠","亚硝酸乙酯","亚硝酰乙氧","氧化二丁基锡","氧化汞","氧化铊","氧化亚铊","氧氯化磷","原装弹","原子弹方法","原子弹清单","安眠酮","代血浆","普萘洛尔","呋塞米","西布曲明","胰岛素样生长因子","促红细胞生成素","地西泮","尼可刹米","甲睾酮","地奈德","莫达非尼","氯噻嗪","苯巴比妥","促性腺激素","泼尼松","麻黄草","雄烯二醇","地塞米松","新型毒品","杜冷丁","兴奋剂","海洛因","海luo因","鸦片","阿芙蓉","咖啡因","cocain","三唑仑","美沙酮","麻古","k粉","凯他敏","ketamine","冰毒","苯丙胺","大麻","爱他死","氯胺酮","甲基安非他明","吗啡","安非他命","腐败中国","三个呆婊","你办事我放心","社会主义灭亡","打倒中国","打倒共产党","打倒共产主义","打倒胡锦涛","打倒江泽民","打倒江主席","打倒李鹏","打倒罗干","打倒温家宝","打倒中共","打倒朱镕","抵制共产党","抵制共产主义","抵制胡锦涛","抵制江泽民","抵制江主席","抵制李鹏","抵制罗干","抵制温家宝","抵制中共","抵制朱镕基","灭亡中国","亡党亡国","粉碎四人帮","激流中国","特供","特贡","特共","zf大楼","殃视","贪污腐败","强制拆除","形式主义","政治风波","太子党","上海帮","北京帮","清华帮","红色贵族","权贵集团","河蟹社会","喝血社会","九风","9风","十七大","十7大","17da","九学","9学","四风","4风","双规","南街村","最淫官员","警匪","官匪","独夫民贼","官商勾结","城管暴力执法","强制捐款","毒豺","一党执政","一党专制","一党专政","专制政权","宪法法院","胡平","苏晓康","贺卫方","谭作人","焦国标","万润南","张志新","辛灝年","高勤荣","王炳章","高智晟","司马璐","刘晓竹","刘宾雁","魏京生","寻找林昭的灵魂","别梦成灰","谁是新中国","讨伐中宣部","异议人士","民运人士","启蒙派","选国家主席","民一主","min主","民竹","民珠","民猪","大赦国际","国际特赦","da选","投公","公头","宪政","平反","党章","维权","昝爱宗","宪章","08宪","08xz","抿主","敏主","人拳","人木又","人quan","renquan","中国人权","中国新民党","群体事件","群体性事件","上中央","去中央","讨说法","请愿","请命","公开信","联名上书","万人大签名","万人骚动","截访","上访","shangfang","信访","访民","集合","集会","组织集体","静坐","静zuo","jing坐","示威","示wei","游行","you行","油行","游xing","youxing","官逼民反","反party","反共","抗议","亢议","抵制","低制","底制","di制","抵zhi","dizhi","boycott","血书","焚烧中国国旗","baoluan","流血冲突","出现暴动","发生暴动","引起暴动","baodong","灭共","杀毙","罢工","霸工","罢考","罢餐","霸餐","罢参","罢饭","罢吃","罢食","罢课","罢ke","霸课","ba课","罢教","罢学","罢运","网特","网评员","网络评论员","五毛党","五毛们","5毛党","戒严","jieyan","jie严","戒yan","8的平方事件","知道64","八九年","贰拾年","2o年","20和谐年","贰拾周年","六四","六河蟹四","六百度四","六和谐四","陆四","陆肆","198964","5月35","89年春夏之交","64惨案","64时期","64运动","4事件","四事件","北京风波","学潮","学chao","xuechao","学百度潮","门安天","天按门","坦克压大学生","民主女神","历史的伤口","高自联","北高联","血洗京城","四二六社论","王丹","柴玲","沈彤","封从德","王超华","王维林","吾尔开希","吾尔开西","侯德健","阎明复","方励之","蒋捷连","丁子霖","辛灏年","蒋彦永","严家其","陈一咨","中华局域网","党的喉舌","互联网审查","当局严密封锁","新闻封锁","封锁消息","爱国者同盟","关闭所有论坛","网络封锁","金盾工程","gfw","无界浏览","无界网络","自由门","何清涟","中国的陷阱","汪兆钧","记者无疆界","境外媒体","维基百科","纽约时报","bbc中文网","华盛顿邮报","世界日报","东森新闻网","东森电视","星岛日报","wikipedia","youtube","googleblogger","美国广播公司","英国金融时报","自由亚洲","自由时报","中国时报","反分裂","威胁论","左翼联盟","钓鱼岛","保钓组织","主权","弓单","火乍","木仓","石肖","核蛋","步qiang","bao炸","爆zha","baozha","zha药","zha弹","炸dan","炸yao","zhadan","zhayao","hmtd","三硝基甲苯","六氟化铀","炸药配方","弹药配方","炸弹配方","皮箱炸弹","火药配方","人体炸弹","人肉炸弹","解放军","兵力部署","军转","军事社","8341部队","第21集团军","七大军区","7大军区","北京军区","沈阳军区","济南军区","成都军区","广州军区","南京军区","兰州军区","颜色革命","规模冲突","塔利班","基地组织","恐怖分子","恐怖份子","三股势力","印尼屠华","印尼事件","蒋公纪念歌","马英九","mayingjiu","李天羽","苏贞昌","林文漪","陈水扁","陈s扁","陈随便","阿扁","a扁","告全国同胞书","台百度湾","台完","台wan","taiwan","台弯","湾台","台湾国","台湾共和国","台军","台独","台毒","台du","taidu","twdl","一中一台","打台湾","两岸战争","攻占台湾","支持台湾","进攻台湾","占领台湾","统一台湾","收复台湾","登陆台湾","解放台湾","解放tw","解决台湾","光复民国","台湾独立","台湾问题","台海问题","台海危机","台海统一","台海大战","台海战争","台海局势","入联","入耳关","中华联邦","国民党","x民党","民进党","青天白日","闹独立","duli","fenlie","日本万岁","小泽一郎","劣等民族","汉人","汉维","维汉","维吾","吾尔","热比娅","伊力哈木","疆独","东突厥斯坦解放组织","东突解放组织","蒙古分裂分子","列确","阿旺晋美","藏人","臧人","zang人","藏民","藏m","达赖","赖达","dalai","哒赖","dl喇嘛","丹增嘉措","打砸抢","西独","藏独","葬独","臧独","藏毒","藏du","zangdu","支持zd","藏暴乱","藏青会","雪山狮子旗","拉萨","啦萨","啦沙","啦撒","拉sa","lasa","la萨","西藏","藏西","藏春阁","藏獨","藏独","藏独立","藏妇会","藏青会","藏字石","xizang","xi藏","x藏","西z","tibet","希葬","希藏","硒藏","稀藏","西脏","西奘","西葬","西臧","援藏","bjork","王千源","安拉","回教","回族","回回","回民","穆斯林","穆罕穆德","穆罕默德","默罕默德","伊斯兰","圣战组织","清真","清zhen","qingzhen","真主","阿拉伯","高丽棒子","韩国狗","满洲第三帝国","满狗","鞑子","江丑闻","江嫡系","江毒","江独裁","江蛤蟆","江核心","江黑心","江胡内斗","江祸心","江家帮","江绵恒","江派和胡派","江派人马","江泉集团","江人马","江三条腿","江氏集团","江氏家族","江氏政治局","江氏政治委员","江梳头","江太上","江戏子","江系人","江系人马","江宰民","江贼","江贼民","江主席","麻果丸","麻将透","麻醉弹","麻醉狗","麻醉枪","麻醉槍","麻醉药","麻醉藥","台独","台湾版假币","台湾独立","台湾国","台湾应该独立","台湾有权独立","天灭中共","中共帮凶","中共保命","中共裁","中共党文化","中共腐败","中共的血旗","中共的罪恶","中共帝国","中共独裁","中共封锁","中共封网","中共腐败","中共黑","中共黑帮","中共解体","中共近期权力斗争","中共恐惧","中共权力斗争","中共任用","中共退党","中共洗脑","中共邪教","中共政治游戏","中共邪毒素","安眠酮","代药物毒品类:血浆","普萘洛尔","呋塞米","西布曲明","testosterone","胰岛素样生长因子","促红细胞生成素","地西泮","尼可刹米","甲睾酮","adrenaline","erythropoietin","地奈德","莫达非尼","氯噻嗪","苯巴比妥","促性腺激素","泼尼松","麻黄草","雄烯二醇","地塞米松","tamoxifen","strychnine","androst","新型毒品","杜冷丁","兴奋剂","mdma","海洛因","海luo因","heroin","diamorphine","diacetylmorphine","鸦片","阿芙蓉","咖啡因","cocain","三唑仑","美沙酮","麻古","k粉","凯他敏","ketamine","冰毒","苯丙胺","cannabis","大麻","爱他死","氯胺酮","benzodiazepines","甲基安非他明","安非他命","吗啡","morphine","摇头丸","迷药","乖乖粉","narcotic","麻醉药","精神药品","针孔摄象","调查婚外情","信用卡提现","无抵押贷款","广告代理","原音铃声","借腹生子","代孕妈妈","代生孩子","代开发票","腾讯客服电话","销售热线","免费订购热线","低价出售","款到发货","回复可见","连锁加盟","加盟连锁","免费二级域名","免费使用","免费索取","蚁力神","婴儿汤","售肾","刻章办","买小车","套牌车","玛雅网","电脑传讯","视频来源","下载速度","高清在线","全集在线","在线播放","txt下载","六位qq","6位qq","位的qq","个qb","送qb","用刀横向切腹","完全自杀手册","四海帮","足球投注","地下钱庄","中国复兴党","阿波罗网","曾道人","六合彩","改卷内幕","替考试","隐形耳机","出售答案","考中答案","答an","da案","资金周转","救市","股市圈钱","崩盘","资金短缺","证监会","质押贷款","小额贷款","周小川","刘明康","尚福林","孔丹","汉芯造假","杨树宽","中印边界谈判结果","喂奶门","摸nai门","酒瓶门","脱裤门","75事件","乌鲁木齐","新疆骚乱","针刺","打针","食堂涨价","饭菜涨价","h1n1","瘟疫爆发","yangjia","y佳","yang佳","杨佳","杨j","袭警","杀警","武侯祠","川b26931","贺立旗","周正毅","px项目","骂四川","家l福","家le福","加了服","龙小霞","震其国土","yuce","提前预测","地震预测","隐瞒地震","李四光预测","蟾蜍迁徙","地震来得更猛烈","八级地震毫无预报","踩踏事故","聂树斌","万里大造林","陈相贵","张丹红","尹方明","李树菲","王奉友","零八奥运艰","惨奥","奥晕","凹晕","懊运","懊孕","奥孕","奥你妈的运","反奥","628事件","weng安","wengan","翁安","瓮安事件","化工厂爆炸","讨回工资","代办发票","代办各","代办文","代办学","代办制","代辦","代表烦","代开发票","代開","代考","代理发票","代理票据","代您考","代讨债","代写毕","代写论文","代孕","代追债","考后付款","考机构","考考邓","考联盟","考前答案","考前付","考前密卷","考前预测","考试,答案","考试,作弊器","考试包过","考试保","考试答案","考试机构","考试联盟","考试枪","考试作弊","考试作弊器","考研考中","考中答案","透视功能","透视镜","透视扑","透视器","透视眼睛","透视眼镜","透视药","透视仪","打死经过","打死人","打砸办公","打砸抢","安眠酮","代血浆","普萘洛尔","呋塞米","西布曲明","testosterone","胰岛素样生长因子","促红细胞生成素","地西泮","尼可刹米","甲睾酮","adrenaline","erythropoietin","地奈德","莫达非尼","氯噻嗪","苯巴比妥","促性腺激素","泼尼松","麻黄草","雄烯二醇","地塞米松","tamoxifen","strychnine","androst","新型毒品","杜冷丁","兴奋剂","mdma","海洛因","海luo因","heroin","diamorphine","diacetylmorphine","鸦片","阿芙蓉","咖啡因","cocain","三唑仑","美沙酮","麻古","k粉","凯他敏","ketamine","冰毒","苯丙胺","cannabis","大麻","爱他死","氯胺酮","benzodiazepines","甲基安非他明","安非他命","吗啡","KC短信","KC嘉年华","短信广告","短信群发","短信群发器","小6灵通","短信商务广告","段录定","无界浏览","无界浏览器","无界","无网界","无网界浏览","无帮国","KC提示","KC网站","UP8新势力","白皮书","UP新势力","移民","易达网络卡","安魂网","罢工","罢课","纽崔莱七折","手机复制","手机铃声","网关","神通加持法","全1球通","如6意通","清仓","灵动卡","答案卫星接收机","高薪养廉","考后付款","佳静安定片","航空母舰","航空售票","号码百事通","考前发放","成本价","诚信通手机商城","高利贷","联4通","黑庄","黑手党","黑车","联通贵宾卡","联总","联总这声传单","联总之声传单","高息贷款","高干子弟","恭喜你的号码","恭喜您的号码","高干子女","各个银行全称","各种发票","高官","高官互调","高官子女","喝一送一","卡号","复制","监听王","传单","旦科","钓鱼岛","钓鱼台","当官靠后台","党校安插亲信","传九促三","客户端非法字符","刻章","大麻树脂","大麻油","大法","大法弟子","dpp大法","fa lun","falu","发抡","发抡功","洗脑","下法轮","发轮","发伦","发伦功","发仑","发沦","发纶","发论","发论功","发论公","发正念","发囵","发愣","发瞟","罚抡","罚伦","罚仑","罚沦","罚纶","罚囵","筏抡","筏轮","筏伦","筏仑","筏沦","筏纶","筏论","筏囵","伐抡","伐轮","伐伦","伐仑","伐沦","伐论","伐囵","乏抡","乏轮","乏伦","乏仑","乏沦","乏纶","乏论","乏囵","阀抡","阀伦","阀仑","阀沦","阀纶","阀论","阀囵","法 轮 功","法*功","法.轮.功","法L功","法lun功","法功","法会","法抡","法抡功","法轮","法轮大法","法轮佛法","法轮功","法伦","法仑","法沦","法纶","法论","法十轮十功","法西斯","法院","法正","法谪","法谪功","法輪","法囵","法愣","珐.輪功","珐抡","珐轮","珐伦","珐仑","珐沦","五不","五不争鸣论坛","五出三进","五套功法","邝锦文","垡抡","垡轮","垡伦","垡仑","垡沦","垡纶","垡论","垡囵","茳澤民","荭志","闳志","闵维方","氵去","氵去车仑工力","转法轮","砝抡","砝轮","砝伦","砝仑","砝沦","砝纶","真、善、忍","真理教","真善美","真善忍","砝论","砝囵","泓志","屙民","珐纶","珐论","珐囵","falun","Falundafa","fa轮","Flg","弟子","地下教会","炼功","梦网洪志","轮大","抡功","轮功","伦功","摩门教","三水法轮","三唑仑","天皇","天怒","天葬","车臣","达赖","功法","讲法","基督","基督教","护法","回教","教徒","功友","大师","达赖喇嘛","虹志","鸿志","洪传","贯通两极法","光祖","洪吟","洪哲胜","洪志","宏法","观世音","宏志","弘志","古兰经","红志","车库","经文","车仑","穴海","协警","纳米比亚","专业调查","有华龙","jq的来","电信路","党鞭","第一夫人","黄巨","荡尽天下","家元自称玉皇大帝","主席李世民","何祚庥","刘刚","不要沉默","后勤集团","食堂涨价","发国难财","浪漫邂逅","红满堂","张小洋","炸学校","子宫","叫晶晶的女孩","回派","社会黑暗","国之母","国母","国姆","东方微点","震惊全球","nowto","chengdu","徐明","六月飞雪","暴力虐待","暴力袭击","天府广场","粮荒","洗脑班","李愚蠢","中国猪","台湾猪","进化不完全的生命体","震死他们","八九","八老","爱女人","爱液","按摩棒","拔出来","爆草","包二奶","暴干","暴奸","暴乳","爆乳","暴淫","屄","被插","被操","被干","逼奸","仓井空","插暴","操逼","操黑","操烂","肏你","肏死","操死","操我","厕奴","插比","插b","插逼","插进","插你","插我","插阴","潮吹","潮喷","成人dv","成人电影","成人论坛","成人小说","成人电","成人电影","成人卡通","成人聊","成人片","成人视","成人图","成人文","成人小","成人电影","成人论坛","成人色情","成人网站","成人文学","成人小说","艳情小说","成人游戏","吃精","赤裸","抽插","扌由插","抽一插","春药","大波","大力抽送","大乳","荡妇","荡女","盗撮","多人轮","发浪","放尿","肥逼","粉穴","封面女郎","风月大陆","干死你","干穴","肛交","肛门","龟头","裹本","国产av","好嫩","豪乳","黑逼","后庭","后穴","虎骑","花花公子","换妻俱乐部","黄片","几吧","鸡吧","鸡巴","鸡奸","寂寞男","寂寞女","妓女","激情","集体淫","奸情","叫床","脚交","金鳞岂是池中物","金麟岂是池中物","精液","就去日","巨屌","菊花洞","菊门","巨奶","巨乳","菊穴","开苞","口爆","口活","口交","口射","口淫","裤袜","狂操","狂插","浪逼","浪妇","浪叫","浪女","狼友","聊性","流淫","铃木麻","凌辱","漏乳","露b","乱交","乱伦","轮暴","轮操","轮奸","裸陪","买春","美逼","美少妇","美乳","美腿","美穴","美幼","秘唇","迷奸","密穴","蜜穴","蜜液","摸奶","摸胸","母奸","奈美","奶子","男奴","内射","嫩逼","嫩女","嫩穴","捏弄","女优","炮友","砲友","喷精","屁眼","品香堂","前凸后翘","强jian","强暴","强奸处女","情趣用品","情色","拳交","全裸","群交","惹火身材","人妻","人兽","日逼","日烂","肉棒","肉逼","肉唇","肉洞","肉缝","肉棍","肉茎","肉具","揉乳","肉穴","肉欲","乳爆","乳房","乳沟","乳交","乳头","三级片","骚逼","骚比","骚女","骚水","骚穴","色逼","色界","色猫","色盟","色情网站","色区","色色","色诱","色欲","色b","少年阿宾","少修正","射爽","射颜","食精","释欲","兽奸","兽交","手淫","兽欲","熟妇","熟母","熟女","爽片","爽死我了","双臀","死逼","丝袜","丝诱","松岛枫","酥痒","汤加丽","套弄","体奸","体位","舔脚","舔阴","调教","偷欢","偷拍","推油","脱内裤","文做","我就色","无码","舞女","无修正","吸精","夏川纯","相奸","小逼","校鸡","小穴","小xue","写真","性感妖娆","性感诱惑","性虎","性饥渴","性技巧","性交","性奴","性虐","性息","性欲","胸推","穴口","学生妹","穴图","亚情","颜射","阳具","杨思敏","要射了","夜勤病栋","一本道","一夜欢","一夜情","一ye情","阴部","淫虫","阴唇","淫荡","阴道","淫电影","阴阜","淫妇","淫河","阴核","阴户","淫贱","淫叫","淫教师","阴茎","阴精","淫浪","淫媚","淫糜","淫魔","淫母","淫女","淫虐","淫妻","淫情","淫色","淫声浪语","淫兽学园","淫书","淫术炼金士","淫水","淫娃","淫威","淫亵","淫样","淫液","淫照","阴b","应召","幼交","幼男","幼女","欲火","欲女","玉女心经","玉蒲团","玉乳","欲仙欲死","玉穴","援交","原味内衣","援助交际","张筱雨","招鸡","招妓","中年美妇","抓胸","自拍","自慰","作爱","18禁","99bb","a4u","a4y","adult","amateur","anal","a片","fuck","gay片","g点","g片","hardcore","h动画","h动漫","incest","porn","secom","sexinsex","sm女王","xiao77","xing伴侣","tokyohot","婊子养的","下体","a片","内裤","浑圆","咪咪","发情","刺激","白嫩","粉嫩","兽性","风骚","呻吟","sm","阉割","高潮","裸露","不穿","一丝不挂","脱光","干你","干死","我干","裙中性运动","乱奸","乱伦","乱伦类","乱伦小","伦理大","伦理电影","伦理毛","伦理片","裸聊","裸聊网","裸体写真","裸舞视","裸照","美女裸体","美女写真","美女上门","美艳少妇","妹按摩","妹上门","迷幻药","迷幻藥","迷昏口","迷昏药","迷昏藥","迷魂香","迷魂药","迷魂藥","迷奸粉","迷奸药","迷情粉","迷情水","迷情药","迷药","迷藥","谜奸药","骚妇","骚货","骚浪","骚女","骚嘴","色电影","色妹妹","色情表演","色情电影","色情服务","色情图片","色情小说","色情影片","色情表演","色情电影","色情服务","色情片","色视频","色小说","性伴侣","性服务","性福情","性感少","性伙伴","性交","性交视频","性交图片","性奴","性奴集中营","性虐","阴唇","阴道","阴蒂","阴户","阴间来电","阴茎","阴茎增大","阴茎助勃","阴毛","陰唇","陰道","陰戶","淫荡","淫荡美女","淫荡视频","淫荡照片","淫乱","淫靡","淫魔","淫魔舞","淫女","淫情女","淫肉","淫騷妹","淫兽","淫兽学","淫水","淫穴","morphine","摇头丸","迷药","乖乖粉","narcotic","麻醉药","精神药品","爱女人","爱液","按摩棒","拔出来","爆草","包二奶","暴干","暴奸","暴乳","爆乳","暴淫","屄","被操","被插","被干","逼奸","仓井空","插暴","操逼","操黑","操烂","肏你","肏死","操死","操我","厕奴","插比","插b","插逼","插进","插你","插我","插阴","潮吹","潮喷","成人电影","成人论坛","成人色情","成人网站","成人文学","成人小说","艳情小说","成人游戏","吃精","赤裸","抽插","扌由插","抽一插","春药","大波","大力抽送","大乳","荡妇","荡女","盗撮","多人轮","发浪","放尿","肥逼","粉穴","封面女郎","风月大陆","干死你","干穴","肛交","肛门","龟头","裹本","国产av","好嫩","豪乳","黑逼","后庭","后穴","虎骑","花花公子","换妻俱乐部","黄片","几吧","鸡吧","鸡巴","鸡奸","寂寞男","寂寞女","妓女","激情","集体淫","奸情","叫床","脚交","金鳞岂是池中物","金麟岂是池中物","精液","就去日","巨屌","菊花洞","菊门","巨奶","巨乳","菊穴","开苞","口爆","口活","口交","口射","口淫","裤袜","狂操","狂插","浪逼","浪妇","浪叫","浪女","狼友","聊性","流淫","铃木麻","凌辱","漏乳","露b","乱交","乱伦","轮暴","轮操","轮奸","裸陪","买春","美逼","美少妇","美乳","美腿","美穴","美幼","秘唇","迷奸","密穴","蜜穴","蜜液","摸奶","摸胸","母奸","奈美","奶子","男奴","内射","嫩逼","嫩女","嫩穴","捏弄","女优","炮友","砲友","喷精","屁眼","品香堂","前凸后翘","强jian","强暴","强奸处女","情趣用品","情色","拳交","全裸","群交","惹火身材","人妻","人兽","日逼","日烂","肉棒","肉逼","肉唇","肉洞","肉缝","肉棍","肉茎","肉具","揉乳","肉穴","肉欲","乳爆","乳房","乳沟","乳交","乳头","三级片","骚逼","骚比","骚女","骚水","骚穴","色逼","色界","色猫","色盟","色情网站","色区","色色","色诱","色欲","色b","少年阿宾","少修正","射爽","射颜","食精","释欲","兽奸","兽交","手淫","兽欲","熟妇","熟母","熟女","爽片","爽死我了","双臀","死逼","丝袜","丝诱","松岛枫","酥痒","汤加丽","套弄","体奸","体位","舔脚","舔阴","调教","偷欢","推油","脱内裤","文做","我就色","无码","舞女","无修正","吸精","夏川纯","相奸","小逼","校鸡","小穴","小xue","写真","性感妖娆","性感诱惑","性虎","性饥渴","性技巧","性交","性奴","性虐","性息","性欲","胸推","穴口","学生妹","穴图","亚情","颜射","阳具","杨思敏","要射了","夜勤病栋","一本道","一夜欢","一夜情","一ye情","阴部","淫虫","阴唇","淫荡","阴道","淫电影","阴阜","淫妇","淫河","阴核","阴户","淫贱","淫叫","淫教师","阴茎","阴精","淫浪","淫媚","淫糜","淫魔","淫母","淫女","淫虐","淫妻","淫情","淫色","淫声浪语","淫兽学园","淫书","淫术炼金士","淫水","淫娃","淫威","淫亵","淫样","淫液","淫照","阴b","应召","幼交","幼男","幼女","欲火","欲女","玉女心经","玉蒲团","玉乳","欲仙欲死","玉穴","援交","原味内衣","援助交际","张筱雨","招鸡","招妓","中年美妇","抓胸","自拍","自慰","作爱","18禁","99bb","a4u","a4y","adult","amateur","anal","a片","fuck","gay片","g点","g片","hardcore","h动画","h动漫","incest","porn","secom","sexinsex","sm女王","xiao77","xing伴侣","tokyohot","yin荡","腐败","贪污","gcd","共贪党","gongchandang","阿共","共一产一党","产党共","公产党","工产党","共c党","共x党","供产","共铲","共惨","供铲党","供铲谠","供铲裆","共残党","共残主义","共产主义的幽灵","拱铲","老共","中珙","中gong","gc党","贡挡","gong党","g产","狗产蛋","共残裆","恶党","邪党","共产专制","共产王朝","裆中央","土共","土g","共狗","g匪","共匪","仇共","共产党腐败","共产党专制","共产党的报应","共产党的末日","共产党专制","communistparty","症腐","政腐","政付","正府","政俯","政f","zhengfu","政zhi","挡中央","档中央","中国zf","中央zf","国wu院","中华帝国","gong和","大陆官方","北京政权","刘志军","张曙","刘志军","买别墅","玩女人","贪20亿","许宗衡","贪财物","李启红","贪腐财富","落马","高官名单","陈希同","贪污","玩忽职守","有期徒刑","陈良宇","受贿罪","滥用职权","有期徒刑","没收个人财产","成克杰","死刑","程维高","严重违纪","开除党籍","撤销职务","刘方仁","无期徒刑","倪献策","徇私舞弊","梁湘","以权谋私","撤职。","李嘉廷","死刑缓期","张国光","韩桂芝","宋平顺","自杀","黄瑶","双规","陈绍基","判处死刑","剥夺政治权利终身","没收个人全部财产","石兆彬","侯伍杰","王昭耀","剥夺政治权利","杜世成","沈图","叛逃美国","罗云光","起诉","张辛泰","李效时","边少斌","徐鹏航","违纪","收受股票","王乐毅","李纪周","郑光迪","田凤山。","邱晓华","郑筱萸","孙鹤龄","蓝田造假案","于幼军","留党察看","何洪达","朱志刚","杨汇泉","官僚主义","徐炳松","托乎提沙比尔","王宝森","经济犯罪","畏罪自杀。","陈水文","孟庆平","胡长清","朱川","许运鸿","丘广钟","刘知炳","丛福奎","王怀忠","巨额财产","来源不明罪","李达昌","刘长贵","王钟麓","阿曼哈吉","付晓光","自动辞","刘克田","吕德彬","刘维明","双开","刘志华","孙瑜","李堂堂","韩福才 青海","欧阳德 广东","韦泽芳 海南","铁英 北京","辛业江 海南","于飞 广东","姜殿武 河北","秦昌典 重庆","范广举 黑龙江","张凯广东","王厚宏海南","陈维席安徽","王有杰河南","王武龙江苏","米凤君吉林","宋勇辽宁","张家盟浙江","马烈孙宁夏","黄纪诚北京","常征贵州","王式惠重庆","周文吉","王庆录广西","潘广田山东","朱作勇甘肃","孙善武河南","宋晨光江西","梁春禄广西政协","鲁家善 中国交通","金德琴 中信","李大强 神华","吴文英 纺织","查克明 华能","朱小华光大","高严 国家电力","王雪冰","林孔兴","刘金宝","张恩照","陈同海","康日新","王益","张春江","洪清源","平义杰","李恩潮","孙小虹","陈忠","慕绥新","田凤岐","麦崇楷","柴王群","吴振汉","张秋阳","徐衍东","徐发 黑龙江","张宗海","丁鑫发","徐国健","李宝金","单平","段义和","荆福生","陈少勇","黄松有","皮黔生","王华元","王守业","刘连昆","孙晋美","邵松高","肖怀枢","刘广智","姬胜德","廖伯年"] +} diff --git a/assets/lang/en_US.json b/assets/lang/en_US.json index ad7571a..ba5f99f 100644 --- a/assets/lang/en_US.json +++ b/assets/lang/en_US.json @@ -17,11 +17,15 @@ "settings.distance": "Distance: {distance}", "settings.music_volume": "Music: {music}", "settings.sfx_volume": "SFX: {sfx}", + "settings.player_voice": "Player Voice: {volume}", "settings.fullscreen": "fullscreen: {state}", "settings.aniso": "Aniso: {aniso}", "settings.vsync": "V-Sync: {state}", "settings.language": "Language: {lang}", "settings.restart_game_info": "You need to restart the game to apply this changes.", + "settings.voice_input": "Voice Input: {type}", + "voice_input.ptt": "PTT", + "voice_input.always": "always", "common.on": "on", "common.off": "off", "hostgame.create_a_new_world": "Create A New World", @@ -30,5 +34,6 @@ "hostgame.create_world": "Create World", "joingame.join_a_world": "Join A World", "joingame.server_ip": "Server Ip", - "joingame.join_world": "Join World" + "joingame.join_world": "Join World", + "error.disable_voice": "Server Disable Voice Chat" } \ No newline at end of file diff --git a/assets/lang/zh_CN.json b/assets/lang/zh_CN.json index 2407686..a3004f4 100644 --- a/assets/lang/zh_CN.json +++ b/assets/lang/zh_CN.json @@ -17,11 +17,15 @@ "settings.distance": "距离:{distance}", "settings.music_volume": "音乐:{music}", "settings.sfx_volume": "音效:{sfx}", + "settings.player_voice": "玩家语音:{volume}", "settings.fullscreen": "全屏:{state}", "settings.aniso": "各向异性过滤:{aniso}", "settings.vsync": "垂直同步: {state}", "settings.language": "语言: {lang}", "settings.restart_game_info": "您需要重启游戏以应用这些更改。", + "settings.voice_input": "语音输入: {type}", + "voice_input.ptt": "按键通话", + "voice_input.always": "始终", "common.on": "开", "common.off": "关", "hostgame.create_a_new_world": "创建新世界", @@ -30,5 +34,6 @@ "hostgame.create_world": "创建世界", "joingame.join_a_world": "加入世界", "joingame.server_ip": "服务器IP", - "joingame.join_world": "加入世界" + "joingame.join_world": "加入世界", + "error.disable_voice": "服务器已禁用语音聊天" } \ No newline at end of file diff --git a/assets/fonts/OFL-1.1.txt b/assets/licenses/OFL-1.1.txt similarity index 100% rename from assets/fonts/OFL-1.1.txt rename to assets/licenses/OFL-1.1.txt diff --git a/assets/licenses/OPENAL_SOFT_LICENSE.txt b/assets/licenses/OPENAL_SOFT_LICENSE.txt new file mode 100644 index 0000000..8d5d000 --- /dev/null +++ b/assets/licenses/OPENAL_SOFT_LICENSE.txt @@ -0,0 +1,437 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/assets/sound/ambient/water/bubble001.ogg b/assets/sound/ambient/water/bubble001.ogg index af5354fc12e38b4e262e9dab0dddb84521dc2ec0..f3c32f5085e737821b5c2155f28141c37e3be23e 100644 GIT binary patch literal 8860 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 Q%NIwAM}KI6C!Wdw3+eC3H~;_u diff --git a/assets/texture/ui/microphone.png b/assets/texture/ui/microphone.png new file mode 100644 index 0000000000000000000000000000000000000000..60d09d71dbc8405261cc1c8e3d4b086554b366fa GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|oIPC}Lo9le z6BZc$;6G}?sLXhl<*L!dr|TYOGu*r2#$2%VN6dkaX5R}bHF}CO^a?apqLfdX@zt0l iamPB7b~X-Z9D}E;pUXO@geCw5tSOuT literal 0 HcmV?d00001 diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 7ff0006..12ba850 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -9,7 +9,7 @@ find_package(OpenAL REQUIRED) find_package(harfbuzz REQUIRED) find_package(Freetype REQUIRED) find_package(SDL3 REQUIRED) - +find_package(Opus REQUIRED) # Third-party libraries FetchContent_Declare( diff --git a/cmake/modules/FindOpus.cmake b/cmake/modules/FindOpus.cmake new file mode 100644 index 0000000..fa05ea2 --- /dev/null +++ b/cmake/modules/FindOpus.cmake @@ -0,0 +1,25 @@ +include(FindPackageHandleStandardArgs) + +find_path(OPUS_INCLUDE_DIR + NAMES opus/opus.h opus.h +) + +find_library(OPUS_LIBRARY + NAMES opus +) + +find_package_handle_standard_args( + Opus + REQUIRED_VARS + OPUS_INCLUDE_DIR + OPUS_LIBRARY +) + +if(Opus_FOUND) + add_library(Opus::Opus UNKNOWN IMPORTED) + + set_target_properties(Opus::Opus PROPERTIES + IMPORTED_LOCATION "${OPUS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${OPUS_INCLUDE_DIR}" + ) +endif() \ No newline at end of file diff --git a/include/Cubed/audio/audio_engine.hpp b/include/Cubed/audio/audio_engine.hpp index c800800..09b5a1c 100644 --- a/include/Cubed/audio/audio_engine.hpp +++ b/include/Cubed/audio/audio_engine.hpp @@ -1,7 +1,9 @@ #pragma once #include "Cubed/audio/audio_fade.hpp" +#include "Cubed/audio/audio_recording.hpp" #include "Cubed/audio/audio_source.hpp" +#include "Cubed/audio/audio_stream_source.hpp" #include "Cubed/audio/sound_manager.hpp" #include "Cubed/audio/source_pool.hpp" #include "Cubed/config.hpp" @@ -10,13 +12,15 @@ #include #include #include +#include #include #include namespace Cubed { -class ClientWorld; +class NetworkClient; class AudioEngine { + public: AudioEngine(Config& config); AudioEngine(const AudioEngine&) = delete; @@ -41,11 +45,23 @@ public: float& bgm_target_volume(); + void set_client(std::weak_ptr client); + + void send_voice(const std::array&); + void receive_voice(std::span opus, const glm::vec3& pos); + + AudioRecording& audio_recording(); + const AudioRecording& audio_recording() const; + private: using FadeMap = std::unordered_map; bool m_init{false}; ALCdevice* device{nullptr}; ALCcontext* context{nullptr}; + OpusEncoder* m_encoder{nullptr}; + OpusDecoder* m_decoder{nullptr}; + AudioRecording m_recording; + std::weak_ptr m_client; glm::vec3 listener_pos; std::unique_ptr m_bgm; FadeMap m_fade_map; @@ -56,6 +72,9 @@ private: bool m_underwater = false; float m_music_volume = 1.0f; float m_sfx_volume = 1.0f; + float m_player_voice_volume = 1.0f; + + std::unique_ptr m_voice_source; std::unique_ptr m_low_pass_filter; std::unique_ptr m_underwater_effect; std::unique_ptr m_underwater_slot; diff --git a/include/Cubed/audio/audio_recording.hpp b/include/Cubed/audio/audio_recording.hpp new file mode 100644 index 0000000..b5936ac --- /dev/null +++ b/include/Cubed/audio/audio_recording.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +namespace Cubed { +class AudioEngine; +class AudioRecording { +public: + static constexpr int SAMPLE_RATE = 48000; + static constexpr int FRAME_MS = 20; + static constexpr int FRAME_SAMPLES = SAMPLE_RATE * FRAME_MS / 1000; + AudioRecording(AudioEngine& engine); + AudioRecording(const AudioRecording&) = delete; + AudioRecording(AudioRecording&&) = delete; + AudioRecording& operator=(const AudioRecording&) = delete; + AudioRecording& operator=(AudioRecording&&) = delete; + ~AudioRecording(); + + void update(); + + void init(); + void start(); + void stop(); + bool is_recording() const; + +private: + AudioEngine& m_engine; + ALCdevice* m_capture = nullptr; + bool m_recording = false; + void send_voice(const std::array& pcm); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/audio/audio_source.hpp b/include/Cubed/audio/audio_source.hpp index 3c798ef..b4a09de 100644 --- a/include/Cubed/audio/audio_source.hpp +++ b/include/Cubed/audio/audio_source.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace Cubed { enum class AudioState { INITIAL, PLAYING, PAUSED, STOPPED }; @@ -21,8 +22,9 @@ public: void set_pitch(float pitch); void play(); void play_2d(const AudioBuffer& buffer); + void play_2d(std::unique_ptr buffer); void play_3d(const AudioBuffer& buffer, const glm::vec3& pos); - + void play_3d(std::unique_ptr buffer, const glm::vec3& pos); void stop(); void pause(); float duration() const; @@ -42,6 +44,8 @@ public: void clear_effect_slot(); private: + std::unique_ptr m_buffer; + ALuint m_source = 0; float m_target_volume = 1.0f; float m_duration = 0.0f; diff --git a/include/Cubed/audio/audio_stream_source.hpp b/include/Cubed/audio/audio_stream_source.hpp new file mode 100644 index 0000000..bd44568 --- /dev/null +++ b/include/Cubed/audio/audio_stream_source.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "Cubed/audio/audio_effect_slot.hpp" +#include "Cubed/audio/audio_filter.hpp" +#include "glm/ext/vector_float3.hpp" + +#include +#include +#include +#include +#include + +namespace Cubed { +class AudioStreamSource { +public: + static constexpr int NUM_BUFFERS = 4; + AudioStreamSource(); + AudioStreamSource(const AudioStreamSource&) = delete; + AudioStreamSource(AudioStreamSource&&) = delete; + AudioStreamSource& operator=(const AudioStreamSource&) = delete; + AudioStreamSource& operator=(AudioStreamSource&&) = delete; + ~AudioStreamSource(); + + void push_pcm(std::span pcm, ALsizei sample_rate); + + void stop(); + bool is_playing() const; + void reset(); + + void set_volume(float volume); + void set_pitch(float pitch); + void set_pos(glm::vec3 pos); + void set_filter(const AudioFilter& filter); + void clear_filter(); + void set_effect_slot(const AudioEffectSlot& slot); + void clear_effect_slot(); + +private: + ALuint m_source = 0; + std::array m_buffers{}; + std::vector m_free_buffers; + bool m_playing = false; + + void unqueue_processed(); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/audio/source_pool.hpp b/include/Cubed/audio/source_pool.hpp index 577f086..ff543b0 100644 --- a/include/Cubed/audio/source_pool.hpp +++ b/include/Cubed/audio/source_pool.hpp @@ -4,7 +4,7 @@ namespace Cubed { class SourcePool { private: - std::vector m_sources; + std::vector> m_sources; public: explicit SourcePool(size_t size); @@ -15,9 +15,10 @@ public: SourcePool& operator=(SourcePool&&) = delete; void update(); + [[nodiscard]] AudioSource* acquire(); - std::vector& sources(); + std::vector>& sources(); }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/config.hpp b/include/Cubed/config.hpp index 5503819..3ba918d 100644 --- a/include/Cubed/config.hpp +++ b/include/Cubed/config.hpp @@ -1,5 +1,4 @@ #pragma once -#include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/toml.utils.hpp" namespace Cubed { @@ -20,7 +19,9 @@ public: template T get(std::string_view key, T default_value) { - ASSERT(m_init); + if (!m_init) { + load_config(); + } if (auto* node = find_node(m_tbl, key)) { if (auto value = node->value()) return *value; @@ -32,7 +33,9 @@ public: } template void set(std::string_view key, T&& value) { - ASSERT(m_init); + if (!m_init) { + load_config(); + } auto pos = key.rfind('.'); toml::table* table; @@ -54,6 +57,14 @@ public: save_to_file(); } + std::string get(std::string_view key, const char* default_value) { + return get(key, std::string(default_value)); + } + + void set(std::string_view key, const char* val) { + set(key, std::string(val)); + } + private: toml::table m_tbl; bool m_init = false; diff --git a/include/Cubed/gameplay/chat_message.hpp b/include/Cubed/gameplay/chat_message.hpp new file mode 100644 index 0000000..c711d2d --- /dev/null +++ b/include/Cubed/gameplay/chat_message.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "Cubed/ui/color.hpp" + +#include +#include +namespace Cubed { +struct ChatMessage { + std::string player; + std::string text; + Color color; + bool system_msg = false; + uint64_t time = 0; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index adcb07e..844d2fe 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -2,6 +2,7 @@ #include "Cubed/audio/audio_engine.hpp" #include "Cubed/config.hpp" #include "Cubed/gameplay/block.hpp" +#include "Cubed/gameplay/chat_message.hpp" #include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/client_chunk.hpp" #include "Cubed/gameplay/client_player.hpp" @@ -78,6 +79,7 @@ public: void rendering_distance(int rendering_distance); int get_chunk_task_id() const; void start_client_thread(std::string_view uuid); + void receive_login_rsp(LoginRsp& rsp); void stop_client_thread(); void start_thread_pool(); @@ -99,9 +101,15 @@ public: int chunk_size() const; static AABB get_block_aabb(const glm::ivec3& pos); AudioEngine& get_audio(); + const AudioEngine& get_audio() const; Config& get_config(); WorldScene& world_scene(); void set_direct_exit(); + + void receive_chat_message(ChatMsg& msg); + void send_chat_message(ChatMessage& message); + void receive_voice_message(VoiceMsg& msg); + bool enable_voice_chat() const; template void register_ticktimer(std::string_view id, TickType threshold, Fn&& f) { m_ticktimers.emplace( @@ -110,6 +118,11 @@ public: } private: + struct VoiceMessage { + std::string data; + glm::vec3 pos; + }; + std::atomic m_is_pending_delete_queue_free{false}; std::mutex m_delete_vbo_mutex; std::mutex m_delete_vao_mutex; @@ -147,6 +160,8 @@ private: tbb::concurrent_queue> m_pending_upload_queue; tbb::concurrent_queue m_dirty_chunk_queue; tbb::concurrent_queue m_pending_sound; + tbb::concurrent_queue m_message_queue; + tbb::concurrent_queue m_voice_queue; std::deque m_dirty_queue; std::vector m_render_snapshots; @@ -163,6 +178,7 @@ private: std::atomic m_requesting_chunk{false}; std::atomic m_is_rebuilding{false}; std::atomic m_chunk_task_id{0}; + std::atomic m_voice_chat{true}; std::shared_ptr m_client; ChunkLoadStyle m_chunk_load_style{ChunkLoadStyle::CENTER}; diff --git a/include/Cubed/gameplay/network_client.hpp b/include/Cubed/gameplay/network_client.hpp index a59bcd3..ae569dd 100644 --- a/include/Cubed/gameplay/network_client.hpp +++ b/include/Cubed/gameplay/network_client.hpp @@ -21,6 +21,7 @@ public: bool is_connect_error() const; std::string get_error_string() const; void clear_error(); + ClientWorld& world(); private: struct Task { diff --git a/include/Cubed/gameplay/packet.hpp b/include/Cubed/gameplay/packet.hpp index 72cd8f7..1af675a 100644 --- a/include/Cubed/gameplay/packet.hpp +++ b/include/Cubed/gameplay/packet.hpp @@ -61,6 +61,8 @@ enum class PacketEnum : uint16_t { BLOCK_CHANGE_RSP = 3004, S2C_CLEAR_ALL_CHUNKS = 3005, UPDATE_TIME = 3006, + CHAT_MSG = 4001, + VOICE_MSG = 4002, PING = 9001, PONG = 9002 @@ -121,6 +123,12 @@ template <> constexpr uint16_t get_packet_id() { template <> constexpr uint16_t get_packet_id() { return std::to_underlying(PacketEnum::PLAYER_WATER_SOUND); } +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::CHAT_MSG); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::VOICE_MSG); +} template requires std::derived_from diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index 2c86e2e..8502570 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -10,7 +10,9 @@ #include "Cubed/gameplay/server_player.hpp" #include "Cubed/tools/priority_thread_pool.hpp" #include "Cubed/tools/recent_queue.hpp" +#include "Cubed/tools/sensitive_filter.hpp" #include "Cubed/tools/thread_pool.hpp" +#include "Cubed/ui/color.hpp" #include "world/block_change.pb.h" #include @@ -80,6 +82,8 @@ public: void handle_chunk_req(int task_id, const std::string& uuid, ChunkPos pos); void handle_block_change(const BlockChangeReq& req); + void handle_chat_message(ChatMsg& msg); + void handle_voice_message(VoiceMsg& msg); int chunk_size() const; template void register_timer(std::string_view id, TickType threshold, Fn&& f) { @@ -128,6 +132,9 @@ private: CaveCarver m_cave_carcer; RiverWorm m_river_worm; + std::atomic m_enable_filter{false}; + std::atomic m_voice_chat{true}; + SensitiveFilter m_filter; std::jthread m_gen_thread; std::jthread m_server_thread; @@ -189,5 +196,8 @@ private: std::atomic>& thread_pool, int threads); void send_server_stop(); + + void boardcast_message(const std::string& name, const std::string& message, + Color color = Color::WHITE, bool system_msg = false); }; } // namespace Cubed diff --git a/include/Cubed/scene/settings_scene.hpp b/include/Cubed/scene/settings_scene.hpp index ad7aafa..19322a8 100644 --- a/include/Cubed/scene/settings_scene.hpp +++ b/include/Cubed/scene/settings_scene.hpp @@ -8,6 +8,7 @@ struct SliderVariable { float mouse_sensitivity = 0.15f; float sfx = 1.0f; float music = 0.5f; + float player_voice = 1.0f; int rendering_distance = 24; }; diff --git a/include/Cubed/scene/world_scene.hpp b/include/Cubed/scene/world_scene.hpp index a48e6be..f789a76 100644 --- a/include/Cubed/scene/world_scene.hpp +++ b/include/Cubed/scene/world_scene.hpp @@ -9,6 +9,7 @@ #include "Cubed/ui/pasue_menu_ui_manager.hpp" #include "Cubed/ui/world_ui_manager.hpp" namespace Cubed { +enum class VoiceInputType { OFF, PTT, ALWAYS }; class SceneManager; class WorldScene : public Scene { public: @@ -33,14 +34,14 @@ public: bool pause() const; void set_pause(bool pause); void set_error(std::string_view error); + void set_mouse(bool pause); + void set_chatting(bool chatting, bool send); + // Not thread safe + void handle_chat_message(ChatMessage& message); + + bool is_recording() const; private: - bool handle_mouse_move_event(const MouseMoveEvent& e) override; - bool handle_mouse_button_event(const MouseButtonEvent& e) override; - bool handle_window_resize_event(const WindowResizeEvent& e) override; - bool handle_mouse_wheel_event(const MouseWheelEvent& e) override; - bool handle_key_event(const KeyEvent& e) override; - SceneManager& m_scene_manager; DevPanel m_dev_panel; Camera m_camera; @@ -50,9 +51,18 @@ private: bool m_paused = false; bool m_show_hud = true; bool m_show_dev_pannel = true; + bool m_chatting = false; PauseMenuUIManager m_pasue_menu; WorldUIManager m_hud_ui; ErrorUI m_error_ui; const Argument& m_argument; + VoiceInputType m_input_type; + bool handle_mouse_move_event(const MouseMoveEvent& e) override; + bool handle_mouse_button_event(const MouseButtonEvent& e) override; + bool handle_window_resize_event(const WindowResizeEvent& e) override; + bool handle_mouse_wheel_event(const MouseWheelEvent& e) override; + bool handle_key_event(const KeyEvent& e) override; + bool handle_text_input_event(const TextInputEvent&) override; + void load_config(); }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/tools/font.hpp b/include/Cubed/tools/font.hpp index 55d6d7f..27c823f 100644 --- a/include/Cubed/tools/font.hpp +++ b/include/Cubed/tools/font.hpp @@ -56,6 +56,7 @@ public: TextMesh vertices(const std::string& text); const Texture* text_texture(); static const std::string& font_path(); + static float text_width(const std::string& text); private: FT_Library m_ft; diff --git a/include/Cubed/tools/sensitive_filter.hpp b/include/Cubed/tools/sensitive_filter.hpp new file mode 100644 index 0000000..a980921 --- /dev/null +++ b/include/Cubed/tools/sensitive_filter.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +namespace Cubed { +class SensitiveFilter { +public: + SensitiveFilter(); + void load(const nlohmann::json& j); + std::string filter(std::string_view text); + +private: + void insert(const std::u32string& world); + void build(); + + struct Node { + std::unordered_map next; + int fail = 0; + size_t length = 0; + bool end = false; + }; + + std::vector trie{1}; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/tools/text_tools.hpp b/include/Cubed/tools/text_tools.hpp index 38b6869..b4d5a21 100644 --- a/include/Cubed/tools/text_tools.hpp +++ b/include/Cubed/tools/text_tools.hpp @@ -1,6 +1,10 @@ #pragma once +#include "Cubed/tools/cubed_assert.hpp" + #include #include +#include +#include namespace Cubed { inline std::string codepoint_to_utf8(uint32_t cp) { std::string out; @@ -38,4 +42,28 @@ inline void utf8_pop_back(std::string& str) { str.erase(i); } +inline std::vector split_utf8(const std::string& str, int size) { + ASSERT(size > 0); + if (!utf8::is_valid(str)) { + return {}; + } + std::vector blocks; + auto cur = str.begin(); + int chat_count = 0; + + for (auto it = str.begin(); it != str.end();) { + if (chat_count >= size) { + blocks.emplace_back(cur, it); + cur = it; + chat_count = 0; + } + utf8::next(it, str.end()); + ++chat_count; + if (it == str.end()) { + blocks.emplace_back(cur, it); + } + } + return blocks; +} + } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/tools/time_tools.hpp b/include/Cubed/tools/time_tools.hpp new file mode 100644 index 0000000..7d4add8 --- /dev/null +++ b/include/Cubed/tools/time_tools.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +namespace Cubed { +namespace Tools { +inline uint64_t get_time_ticks() { return SDL_GetTicks(); } +} // namespace Tools + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/button.hpp b/include/Cubed/ui/button.hpp index ec2a570..1d8b306 100644 --- a/include/Cubed/ui/button.hpp +++ b/include/Cubed/ui/button.hpp @@ -23,8 +23,8 @@ public: float width() const override; float height() const override; - Button& set_width(float width); - Button& set_height(float height); + Button& set_width(float width) override; + Button& set_height(float height) override; Button& set_background_image(const std::string& path, TextureManager& texture_manager); Button& set_default_image(TextureManager& texture_manager); @@ -54,8 +54,6 @@ private: "texture/ui/button001.png"; std::function m_clicked; - float m_width = NORMAL_BUTTON_WIDTH; - float m_height = NORMAL_BUTTON_HEIGHT; float m_scale = DEFAULT_SCALE; bool m_auto_scale = false; void on_render(Renderer& renderer) override; diff --git a/include/Cubed/ui/chat_box.hpp b/include/Cubed/ui/chat_box.hpp new file mode 100644 index 0000000..cbb47fb --- /dev/null +++ b/include/Cubed/ui/chat_box.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "Cubed/gameplay/chat_message.hpp" +#include "Cubed/ui/text_field.hpp" +#include "Cubed/ui/widget.hpp" + +#include + +namespace Cubed { +class ChatBox : public Widget { +public: + ChatBox(Widget* parent); + + void add_message(ChatMessage& message); + + ChatBox& set_scale(float scale); + ChatBox& set_text_scale(float scale); + // Only the width of TextField can be set; the height is calculated + // dynamically + ChatBox& set_show_lines(int lines); + ChatBox& set_spacing(float spacing); + ChatBox& clear_input(); + std::string& get_input_text(); + float width() const override; + float height() const override; + float text_label_width() const; + void set_text_field(std::unique_ptr text_field); + void set_typing(bool typing, bool finished); + template ChatBox& set_on_finish(F&& f) { + m_text_field->set_on_finish(std::forward(f)); + return *this; + } + +private: + static constexpr int MAX_MESSGAES_SUM = 50; + static constexpr float DISAPPEAR_TIME = 5.0f; + struct Line { + std::unique_ptr