component TCModelCommon { TCViewModel m_ViewModel; list m_TextChatPreviewList; int m_MaxMessgaeCount; // テキストチャットのレイテンシ計測用 int m_DebugSendTime; float lastSendTime; // スタンプの前回送信時間 public TCModelCommon() { m_ViewModel = system.Layer_GetComponentByName("textchat_common"); m_TextChatPreviewList = new list(); m_MaxMessgaeCount = 20; } public void Exit() { ClearTextChatPreview(); m_TextChatPreviewList.Clear(); } public void Close() { } public void DoSharedSendProcess() { // Gtag送信 hsSendGtag("SendTextChat", hsGetCurrentWorldId()); // デバッグモードならTextChat APIの自身のメッセージの送受信時間を測る if(hsSystemIsDebugMode()) { m_DebugSendTime = hsSystemGetTime(); } } public bool DoReceiveSharedProcess(ref TCMessageGUIManager Data, bool IsShowChatBallon) { Player SelfPlayer = hsPlayerGet(); string SelfUserCode = SelfPlayer.GetCustomState("UserCode"); string userCode = Data.GetVketID(); // UserCodeかSelfUserCodeが空の場合は不正なデータなので追加処理を行わない if(userCode.IsEmpty() || SelfUserCode.IsEmpty()) return false; // 自身のメッセージ通知を処理 if(userCode == SelfUserCode && IsShowChatBallon) { bool isMessageTypeText = Data.GetMessageContentData().GetMessageTypeID() == TC_MESSAGE_TYPE_ID_TEXT; string messageText = Data.GetMessageContentData().GetMessageText(); // 全体チャットの文章かチェック bool isGlobalChat = Data.GetMessageContentData().GetPrivateFlag() == false; // 全体チャット向けの自分の発言なら if(isMessageTypeText && isGlobalChat) { // チャットバルーンを表示 SelfPlayer.ShowChatBalloon(messageText); // チャットバルーンを表示するためにTextChat APIで伏字処理されたメッセージを送る hsNetSendCustomData("SendChatBallon", messageText); } // テキストチャットのエンジン機能を呼ぶためにmsgコマンドを発行 hel_skyway_receive_data("data", SelfPlayer.GetID(), "msg:" + Comma2Special(Colon2Special(messageText)) + ":" + "false"); hel_skyway_send_data("msg:" + Comma2Special(Colon2Special(messageText)) + ":" + "false"); // デバッグモードならTextChat APIの自身のメッセージの送受信時間を測る if(hsSystemIsDebugMode()) { int CurrentTime = hsSystemGetTime(); int SendTime = m_DebugSendTime; float DeltaTime = float(CurrentTime - SendTime) / 1000.0; hsSystemOutputLog("[HS] TextChat Latency: %f(s)\n" % DeltaTime); } } // 受信したメッセージがブロックユーザーによるものならメッセージリストにもバルーンにも表示しない { LayerBundle layer = hsLayerGet("prof_option_layer"); if(layer !== null) { Func IsBlockedUserCode = system.Layer_GetComponentMethod(layer.GetCurrent()._GetInstanceID(), "ProfRejector", "IsBlockedUserCode"); Func IsBlockerUserCode = system.Layer_GetComponentMethod(layer.GetCurrent()._GetInstanceID(), "ProfRejector", "IsBlockerUserCode"); Func IsTemporaryBlockedUserCode = system.Layer_GetComponentMethod(layer.GetCurrent()._GetInstanceID(), "ProfRejector", "IsTemporaryBlockedUserCode"); Func IsTemporaryBlockerUserCode = system.Layer_GetComponentMethod(layer.GetCurrent()._GetInstanceID(), "ProfRejector", "IsTemporaryBlockerUserCode"); if(!IsBlockedUserCode.IsEmpty() && !IsBlockerUserCode.IsEmpty() && !IsTemporaryBlockedUserCode.IsEmpty() && !IsTemporaryBlockerUserCode.IsEmpty()) { if(IsBlockedUserCode(userCode) || IsBlockerUserCode(userCode) || IsTemporaryBlockedUserCode(userCode) || IsTemporaryBlockerUserCode(userCode)) { return false; } } } } // データを整理 bool isSelfMessage = !userCode.IsEmpty() && (userCode == SelfUserCode); Data.SetSelfMessageFlag(isSelfMessage); // テキストチャットプレビュー用の表示リストに追加 _TextChatPreviewListAdd(Data.GetMessageContentData()); // テキストチャットプレビューの表示更新 DrawTextChatPreview(); return true; } public void OnReceiveCustomData(string id, string type, string data) { if(type == "SendChatBallon") { // チャットバルーンを表示する string peerid = id; Player player = hsPlayerGetByID(peerid); string user_code = player.GetCustomState("UserCode"); // 受信したメッセージがブロックユーザーによるものならメッセージリストにもバルーンにも表示しない { LayerBundle layer = hsLayerGet("prof_option_layer"); if(layer !== null) { Func IsBlockedUserCode = system.Layer_GetComponentMethod(layer.GetCurrent()._GetInstanceID(), "ProfRejector", "IsBlockedUserCode"); Func IsBlockerUserCode = system.Layer_GetComponentMethod(layer.GetCurrent()._GetInstanceID(), "ProfRejector", "IsBlockerUserCode"); Func IsTemporaryBlockedUserCode = system.Layer_GetComponentMethod(layer.GetCurrent()._GetInstanceID(), "ProfRejector", "IsTemporaryBlockedUserCode"); if(!IsBlockedUserCode.IsEmpty() && !IsBlockerUserCode.IsEmpty() && !IsTemporaryBlockedUserCode.IsEmpty()) { if(IsBlockedUserCode(user_code) || IsBlockerUserCode(user_code) || IsTemporaryBlockedUserCode(user_code) ) { return; } } } } player.ShowChatBalloon(data); } } public void DrawTextChatPreview(){ //名前欄は最大7文字 const int NAME_TEXT_MAX_LENGTH = 7; string layerName = "HUD"; if(hsSystemIsVRMode()) layerName = "hud_webxr"; for(int i = 0; i < m_TextChatPreviewList.Count(); i++){ string chatMessage = _RemoveLinefeed(m_TextChatPreviewList[i].GetMessageText()); string chatSendUserName = m_TextChatPreviewList[i].GetSenderName(); bool isDM = m_TextChatPreviewList[i].GetPrivateFlag(); int namePreviewTextLength = NAME_TEXT_MAX_LENGTH; if(chatSendUserName.Length() < NAME_TEXT_MAX_LENGTH) namePreviewTextLength = chatSendUserName.Length(); string nameText = "%s : " % _AddEllipsisToOutRangeText(chatSendUserName, NAME_TEXT_MAX_LENGTH); //個別テキチャかどうか if(isDM){ hsCanvasResetToggleDefault("Toggle_preview_chat_%s" % string(i)); hsCanvasResetToggleDefault("Toggle_preview_dm_%s" % string(i)); hsCanvasToggleChange("Toggle_preview_dm_%s" % string(i)); hsCanvasSetGUIText(layerName,"preview_dm_%s" % string(i),"%s%s" % nameText % chatMessage); } else{ hsCanvasResetToggleDefault("Toggle_preview_dm_%s" % string(i)); hsCanvasResetToggleDefault("Toggle_preview_chat_%s" % string(i)); hsCanvasToggleChange("Toggle_preview_chat_%s" % string(i)); hsCanvasSetGUIText(layerName,"preview_chat_%s" % string(i),"%s%s" % nameText % chatMessage); } } } public void ClearTextChatPreview() { for(int i = 0; i < m_TextChatPreviewList.Count(); i++){ bool isDM = m_TextChatPreviewList[i].GetPrivateFlag(); //個別テキチャかどうか if(isDM) { hsCanvasResetToggleDefault("Toggle_preview_chat_%s" % string(i)); hsCanvasResetToggleDefault("Toggle_preview_dm_%s" % string(i)); } else { hsCanvasResetToggleDefault("Toggle_preview_dm_%s" % string(i)); hsCanvasResetToggleDefault("Toggle_preview_chat_%s" % string(i)); } } } public void FetchProfileFromMessage(string UserCode, string UserType) { if(!UserCode.IsEmpty() && !UserType.IsEmpty()) { Player SelfPlayer = hsPlayerGet(); string SelfUserCode = SelfPlayer.GetCustomState("UserCode"); string SelfUserType = SelfPlayer.GetCustomState("UserType"); // クリックしたアイコンが自分の物ならスキップ if(UserCode == SelfUserCode) return; LayerBundle layer = hsLayerGet("other_profile"); if(layer !== null) { Func FindUserDataAndShowProfileFromUserID = system.Layer_GetComponentMethod(layer.GetCurrent()._GetInstanceID(), "ProfileModel", "FindUserDataAndShowProfileFromUserID"); if(!FindUserDataAndShowProfileFromUserID.IsEmpty()) FindUserDataAndShowProfileFromUserID(UserCode, UserType, (SelfUserType == "login" && SelfUserCode != UserCode)); } } } // skywayへの送信データ生成 public JsVal GetSharedSendParam(string message, bool IsEmote) { if(IsEmptyMessage(message)) return makeJsNull(); string spatiumCode = hsNetGetSpatiumCode(); string worldId = hsGetCurrentWorldId(); string roomId = hel_skyway_get_roomid(); Player SelfPlayer = hsPlayerGet(); string name = SelfPlayer.GetName(); string SelfUserCode = SelfPlayer.GetCustomState("UserCode"); string SelfUserType = SelfPlayer.GetCustomState("UserType"); Vector3 PositionVector = SelfPlayer.GetPos(); string pos = "%f,%f,%f" % PositionVector.x % PositionVector.y % PositionVector.z; string modifiedPos = _GetPositionForSpaceChat(pos); JsVal sendVal = makeJsObj(); sendVal.AddProperty("spatiumCode").SetType(JS_STR).SetStr(spatiumCode); sendVal.AddProperty("worldId").SetType(JS_STR).SetStr(worldId); sendVal.AddProperty("roomId").SetType(JS_STR).SetStr(roomId); sendVal.AddProperty("pos").SetType(JS_STR).SetStr(modifiedPos); sendVal.AddProperty("isLoggedIn").SetType(JS_BOOL).SetBool(!SelfUserCode.IsEmpty() && SelfUserType == "login"); //エモートの場合 if(IsEmote) { // 連打の制限 float elapsedTimeAfterSending = hsSystemGetTime() - lastSendTime; const int sendingLimitInterval = 1000; if(elapsedTimeAfterSending < sendingLimitInterval) return makeJsNull(); int pursedIndex = message.ToInt(); Layer layer = hsLayerGet("action_palette").GetCurrent(); if(layer === null) { return makeJsNull(); } Func DataFromActionPaletteWithServer = system.Layer_GetComponentMethod(layer._GetInstanceID(), "ActionPaletteWithServer", "GetIDFromEmoteIndex"); int emoteId = -1; if(DataFromActionPaletteWithServer.IsEmpty() == true){ return makeJsNull(); } emoteId = DataFromActionPaletteWithServer(pursedIndex); int emoteVersion = _hsEmoteGetCurrentVersion(); sendVal.AddProperty("emoteId").SetType(JS_NUM).SetNum(emoteId); sendVal.AddProperty("emoteVersion").SetType(JS_NUM).SetNum(emoteVersion); } //テキストチャットの場合 else { sendVal.AddProperty("message").SetType(JS_STR).SetStr(Comma2Special(Colon2Special(message))); } return sendVal; } string _GetPositionForSpaceChat(string position) { // 空間チャットアクティビティが無効・存在しない場合はそのまま返す string IsSpaceChatValid = hel_get_cookie_onEmptyStr("CookieKeySpacechatValidity"); if(IsSpaceChatValid != "true") return position; // ポスト制限中の場合は空にする(= 吹き出しを表示しない) string IsSpaceChatPostValid = hel_get_cookie_onEmptyStr("CookieKeySpacechatPostValidity"); if(IsSpaceChatPostValid == "false") return ""; return position; } // 改行を削除して一行にする string _RemoveLinefeed(string src) { if(src.IsEmpty()) return src; string adjustmentText = ""; list splits = src.Split("\n"); for(int i = 0; i < splits.Count(); i++) { adjustmentText += splits[i]; } return adjustmentText; } //テキストチャットプレビューに表示するメッセージデータを入れる。 void _TextChatPreviewListAdd(TCMessageGUIData messageData){ // もしメッセージタイプがテキスト以外の場合は弾く if(messageData.GetMessageTypeID() != TC_MESSAGE_TYPE_ID_TEXT) return; if(m_TextChatPreviewList.Count() == 3){ m_TextChatPreviewList.RemoveLast(); } m_TextChatPreviewList.Insert(0,messageData); } // 一定以上の文字数を超えた際に記号などを最後尾に追加する。 string _AddEllipsisToOutRangeText(string text,int maxTextLength){ string ellipsisText = "..."; int ellipsisTextLength = 1; //空文字の場合はそのまま返す if(text.IsEmpty()) return text; //最大文字数以下はテキストをそのまま返す if(text.Length() <= maxTextLength) return text; int MaxTextInEllipsisTextLength = maxTextLength - ellipsisTextLength; string okText; for(int i = 0; i < text.Length(); i++){ string checkText = text.SubString(0,i); if(checkText.Length() >= MaxTextInEllipsisTextLength){ okText = checkText; break; } } return "%s%s" % okText % ellipsisText; } } // スペースを取り除く // 空白と改行だけのメッセージを送れないようにする bool IsEmptyMessage(string src) { if(src.IsEmpty()) return true; for(int i = 0; i < src.Length(); i++) { string val = src.SubString(i, 1); if(val != "\n" && val != " ") { return false; // 空白も改行もない完全なEmptyである } } return true; } // Helper Function /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // "," => "<&comma&>" string Comma2Special(string src) { string dst = src; while(dst.IndexOf(",") != -1) { int Index = dst.IndexOf(","); dst = dst.SubString(0, Index) + "<&comma&>" + dst.SubString(Index + 1, dst.Length() - Index); } return dst; } // "<&comma&>" => "," string Comma2Original(string src) { string dst = src; while(dst.IndexOf("<&comma&>") != -1) { int Index = dst.IndexOf("<&comma&>"); dst = dst.SubString(0, Index) + "," + dst.SubString(Index + 9, dst.Length() - Index); } return dst; } // ":" => ":" string Colon2Special(string src) { string dst = src; while(dst.IndexOf(":") != -1) { int Index = dst.IndexOf(":"); dst = dst.SubString(0, Index) + ":" + dst.SubString(Index + 1, dst.Length() - Index); } return dst; } // ":" => ":" string Colon2Original(string src) { string dst = src; while(dst.IndexOf(":") != -1) { int Index = dst.IndexOf(":"); dst = dst.SubString(0, Index) + ":" + dst.SubString(Index + 7, dst.Length() - Index); } return dst; }