mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-10-31 04:32:06 +00:00 
			
		
		
		
	VERY rough WIP
This commit is contained in:
		| @@ -19,6 +19,8 @@ | ||||
| #include "StringifiedEnum.h" | ||||
| #include "eGameMasterLevel.h" | ||||
| #include "ChatPackets.h" | ||||
| #include "ChatWebAPI.h" | ||||
| #include "json.hpp" | ||||
|  | ||||
| void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { | ||||
| 	//Get from the packet which player we want to do something with: | ||||
| @@ -428,6 +430,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) { | ||||
| 	CINSTREAM_SKIP_HEADER; | ||||
| 	LWOOBJID playerID; | ||||
| 	inStream.Read(playerID); | ||||
| 	LOG("Got a message from player %llu", playerID); | ||||
|  | ||||
| 	const auto& sender = Game::playerContainer.GetPlayerData(playerID); | ||||
| 	if (!sender || sender.GetIsMuted()) return; | ||||
| @@ -439,13 +442,25 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) { | ||||
| 	inStream.Read(channel); | ||||
| 	inStream.Read(size); | ||||
| 	inStream.IgnoreBytes(77); | ||||
|  | ||||
| 	LOG("message size: %u", size);	 | ||||
| 	LUWString message(size); | ||||
| 	inStream.Read(message); | ||||
|  | ||||
| 	LOG("Got a message from (%s) via [%s]: %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str()); | ||||
|  | ||||
| 	LOG("Got message %s from (%s) via [%s]: %s", message.GetAsString().c_str(), sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str()); | ||||
| 	switch (channel) { | ||||
| 	case eChatChannel::LOCAL: { | ||||
| 		// Send to connected websockets | ||||
| 		nlohmann::json data; | ||||
| 		data["action"] = "chat"; | ||||
| 		data["playerName"] = sender.playerName; | ||||
| 		data["message"] = message.GetAsString(); | ||||
| 		auto& zoneID = data["zone_id"]; | ||||
| 		zoneID["map_id"] = sender.zoneID.GetMapID(); | ||||
| 		zoneID["instance_id"] = sender.zoneID.GetInstanceID(); | ||||
| 		zoneID["clone_id"] = sender.zoneID.GetCloneID(); | ||||
| 		Game::chatwebapi.SendWSMessage(data.dump()); | ||||
| 		break; | ||||
| 	} | ||||
| 	case eChatChannel::TEAM: { | ||||
| 		auto* team = Game::playerContainer.GetTeam(playerID); | ||||
| 		if (team == nullptr) return; | ||||
|   | ||||
| @@ -39,6 +39,7 @@ namespace Game { | ||||
| 	Game::signal_t lastSignal = 0; | ||||
| 	std::mt19937 randomEngine; | ||||
| 	PlayerContainer playerContainer; | ||||
| 	ChatWebAPI chatwebapi; | ||||
| } | ||||
|  | ||||
| void HandlePacket(Packet* packet); | ||||
| @@ -94,8 +95,7 @@ int main(int argc, char** argv) { | ||||
|  | ||||
| 	// seyup the chat api web server | ||||
| 	bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1"; | ||||
| 	ChatWebAPI chatwebapi; | ||||
| 	if (web_server_enabled && !chatwebapi.Startup()){ | ||||
| 	if (web_server_enabled && !Game::chatwebapi.Startup()){ | ||||
| 		// if we want the web api and it fails to start, exit | ||||
| 		LOG("Failed to start web server, shutting down."); | ||||
| 		Database::Destroy("ChatServer"); | ||||
| @@ -168,7 +168,7 @@ int main(int argc, char** argv) { | ||||
|  | ||||
| 		//Check and handle web requests: | ||||
| 		if (web_server_enabled) { | ||||
| 			chatwebapi.ReceiveRequests(); | ||||
| 			Game::chatwebapi.ReceiveRequests(); | ||||
| 		} | ||||
|  | ||||
| 		//Push our log every 30s: | ||||
| @@ -207,6 +207,7 @@ int main(int argc, char** argv) { | ||||
| } | ||||
|  | ||||
| void HandlePacket(Packet* packet) { | ||||
| 	LOG("Received packet with ID: %i", packet->data[0]); | ||||
| 	if (packet->length < 1) return; | ||||
| 	if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { | ||||
| 		LOG("A server has disconnected, erasing their connected players from the list."); | ||||
|   | ||||
| @@ -28,7 +28,8 @@ typedef struct mg_http_message mg_http_message; | ||||
|  | ||||
| namespace { | ||||
| 	const char* json_content_type = "Content-Type: application/json\r\n"; | ||||
| 	std::map<std::pair<eHTTPMethod, std::string>, WebAPIHTTPRoute> Routes {}; | ||||
| 	std::map<std::pair<eHTTPMethod, std::string>, HTTPRoute> HTTPRoutes {}; | ||||
| 	std::map<std::string, WSAction> WSactions {}; | ||||
| } | ||||
|  | ||||
| bool ValidateAuthentication(const mg_http_message* http_msg) { | ||||
| @@ -47,19 +48,19 @@ bool ValidateJSON(std::optional<json> data, HTTPReply& reply) { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void HandlePlayersRequest(HTTPReply& reply, std::string body) { | ||||
| void HandleHTTPPlayersRequest(HTTPReply& reply, std::string body) { | ||||
| 	const json data = Game::playerContainer; | ||||
| 	reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK; | ||||
| 	reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump(); | ||||
| } | ||||
|  | ||||
| void HandleTeamsRequest(HTTPReply& reply, std::string body) { | ||||
| void HandleHTTPTeamsRequest(HTTPReply& reply, std::string body) { | ||||
| 	const json data = Game::playerContainer.GetTeamContainer(); | ||||
| 	reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK; | ||||
| 	reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump(); | ||||
| } | ||||
|  | ||||
| void HandleAnnounceRequest(HTTPReply& reply, std::string body) { | ||||
| void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) { | ||||
| 	auto data = GeneralUtils::TryParse<json>(body); | ||||
| 	if (!ValidateJSON(data, reply)) return; | ||||
|  | ||||
| @@ -80,7 +81,7 @@ void HandleAnnounceRequest(HTTPReply& reply, std::string body) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void HandleInvalidRoute(HTTPReply& reply) { | ||||
| void HandleHTTPInvalidRoute(HTTPReply& reply) { | ||||
| 	reply.status = eHTTPStatusCode::NOT_FOUND; | ||||
| 	reply.message = "{\"error\":\"Invalid Route\"}"; | ||||
| } | ||||
| @@ -105,13 +106,17 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms | ||||
| 		// convert body from cstring to std string | ||||
| 		std::string body(http_msg->body.buf, http_msg->body.len); | ||||
|  | ||||
| 		// Special case for websocket | ||||
| 		if (uri == "/ws" && method == eHTTPMethod::GET) { | ||||
| 			mg_ws_upgrade(connection, const_cast<mg_http_message*>(http_msg), NULL); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		const auto routeItr = Routes.find({method, uri}); | ||||
|  | ||||
| 		if (routeItr != Routes.end()) { | ||||
| 		const auto routeItr = HTTPRoutes.find({method, uri}); | ||||
| 		if (routeItr != HTTPRoutes.end()) { | ||||
| 			const auto& [_, route] = *routeItr; | ||||
| 			route.handle(reply, body); | ||||
| 		} else HandleInvalidRoute(reply); | ||||
| 		} else HandleHTTPInvalidRoute(reply); | ||||
| 	} else { | ||||
| 		reply.status = eHTTPStatusCode::UNAUTHORIZED; | ||||
| 		reply.message = "{\"error\":\"Unauthorized\"}"; | ||||
| @@ -119,23 +124,93 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms | ||||
| 	mg_http_reply(connection, static_cast<int>(reply.status), json_content_type, reply.message.c_str()); | ||||
| } | ||||
|  | ||||
| void HandleWSAnnounce(json data){ | ||||
| 	auto check = JSONUtils::CheckRequiredData(data, { "title", "message" }); | ||||
| 	if (!check.empty()) { | ||||
| 		LOG("Received invalid websocket message: %s", check.c_str()); | ||||
| 	} else { | ||||
| 		ChatPackets::Announcement announcement; | ||||
| 		announcement.title = data["title"]; | ||||
| 		announcement.message = data["message"]; | ||||
| 		announcement.Send(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void HandleRequests(mg_connection* connection, int request, void* request_data) { | ||||
| 	switch (request) { | ||||
| void HandleWSChat(json data) { | ||||
| 	auto check = JSONUtils::CheckRequiredData(data, { "user", "message" }); | ||||
| 	if (!check.empty()) { | ||||
| 		LOG("Received invalid websocket message: %s", check.c_str()); | ||||
| 	} else { | ||||
| 		const auto user = data["user"].get<std::string>(); | ||||
| 		const auto message = data["message"].get<std::string>(); | ||||
| 		LOG("EXTERNAL Chat message from %s: %s", user.c_str(), message.c_str()); | ||||
| 		//TODO: Send chat message to  corret world server to broadcast to players | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void HandleWSMessage(mg_connection* connection, const mg_ws_message* ws_msg) { | ||||
| 	std::string reply = "{\"status\":\"Error\"}"; | ||||
| 	if (!ws_msg) { | ||||
| 		LOG("Received invalid websocket message"); | ||||
| 		return; | ||||
| 	} else { | ||||
| 		LOG("Received websocket message: %.*s", static_cast<uint32_t>(ws_msg->data.len), ws_msg->data.buf); | ||||
| 		auto data = GeneralUtils::TryParse<json>(std::string(ws_msg->data.buf, ws_msg->data.len)); | ||||
| 		if (data) { | ||||
| 			const auto& good_data = data.value(); | ||||
| 			auto check = JSONUtils::CheckRequiredData(good_data, { "action" }); | ||||
| 			if (!check.empty()) { | ||||
| 				LOG("Received invalid websocket message: %s", check.c_str()); | ||||
| 				reply = "{\"status\":\"no action\"}"; | ||||
| 			} else { | ||||
| 				const auto action = good_data["action"].get<std::string>(); | ||||
| 				const auto actionItr = WSactions.find(action); | ||||
| 				if (actionItr != WSactions.end()) { | ||||
| 					const auto& [_, action] = *actionItr; | ||||
| 					action.handle(good_data); | ||||
| 					reply = "{\"status\":\"OK\"}"; | ||||
| 				} else { | ||||
| 					LOG("Received invalid websocket action: %s", action.c_str()); | ||||
| 					reply = "{\"status\":\"invalid action\"}"; | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			LOG("Received invalid websocket message: %.*s", static_cast<uint32_t>(ws_msg->data.len), ws_msg->data.buf); | ||||
| 		} | ||||
| 	} | ||||
| 	mg_ws_send(connection, reply.c_str(), reply.size(), WEBSOCKET_OP_TEXT); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| void HandleMessages(mg_connection* connection, int message, void* message_data) { | ||||
| 	switch (message) { | ||||
| 		case MG_EV_HTTP_MSG: | ||||
| 			HandleHTTPMessage(connection, static_cast<mg_http_message*>(request_data)); | ||||
| 			HandleHTTPMessage(connection, static_cast<mg_http_message*>(message_data)); | ||||
| 			break; | ||||
| 		case MG_EV_WS_MSG: | ||||
| 			HandleWSMessage(connection, static_cast<mg_ws_message*>(message_data)); | ||||
| 			break; | ||||
| 		default: | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ChatWebAPI::RegisterHTTPRoutes(WebAPIHTTPRoute route) { | ||||
| 	auto [_, success] = Routes.try_emplace({ route.method, route.path }, route); | ||||
| void ChatWebAPI::RegisterHTTPRoute(HTTPRoute route) { | ||||
| 	auto [_, success] = HTTPRoutes.try_emplace({ route.method, route.path }, route); | ||||
| 	if (!success) { | ||||
| 		LOG_DEBUG("Failed to register route %s", route.path.c_str()); | ||||
| 		LOG_DEBUG("Failed to register HTTP route %s", route.path.c_str()); | ||||
| 	} else { | ||||
| 		LOG_DEBUG("Registered route %s", route.path.c_str()); | ||||
| 		LOG_DEBUG("Registered HTTP route %s", route.path.c_str()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ChatWebAPI::RegisterWSAction(WSAction action) { | ||||
| 	auto [_, success] = WSactions.try_emplace(action.action, action); | ||||
| 	if (!success) { | ||||
| 		LOG_DEBUG("Failed to register WS action %s", action.action.c_str()); | ||||
| 	} else { | ||||
| 		LOG_DEBUG("Registered WS action %s", action.action.c_str()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -158,7 +233,7 @@ bool ChatWebAPI::Startup() { | ||||
| 	LOG("Starting web server on %s", listen_address.c_str()); | ||||
|  | ||||
| 	// Create HTTP listener | ||||
| 	if (!mg_http_listen(&mgr, listen_address.c_str(), HandleRequests, NULL)) { | ||||
| 	if (!mg_http_listen(&mgr, listen_address.c_str(), HandleMessages, NULL)) { | ||||
| 		LOG("Failed to create web server listener on %s", listen_port.c_str()); | ||||
| 		return false; | ||||
| 	} | ||||
| @@ -167,23 +242,36 @@ bool ChatWebAPI::Startup() { | ||||
|  | ||||
| 	// API v1 routes | ||||
| 	std::string v1_route = "/api/v1/"; | ||||
| 	RegisterHTTPRoutes({ | ||||
| 	RegisterHTTPRoute({ | ||||
| 		.path = v1_route + "players", | ||||
| 		.method = eHTTPMethod::GET, | ||||
| 		.handle = HandlePlayersRequest | ||||
| 		.handle = HandleHTTPPlayersRequest | ||||
| 	}); | ||||
|  | ||||
| 	RegisterHTTPRoutes({ | ||||
| 	RegisterHTTPRoute({ | ||||
| 		.path = v1_route + "teams", | ||||
| 		.method = eHTTPMethod::GET, | ||||
| 		.handle = HandleTeamsRequest | ||||
| 		.handle = HandleHTTPTeamsRequest | ||||
| 	}); | ||||
|  | ||||
| 	RegisterHTTPRoutes({ | ||||
| 	RegisterHTTPRoute({ | ||||
| 		.path = v1_route + "announce", | ||||
| 		.method = eHTTPMethod::POST, | ||||
| 		.handle = HandleAnnounceRequest | ||||
| 		.handle = HandleHTTPAnnounceRequest | ||||
| 	}); | ||||
|  | ||||
| 	// WebSocket Actions | ||||
| 	RegisterWSAction({ | ||||
| 		.action = "announce", | ||||
| 		.handle = HandleWSAnnounce | ||||
| 	}); | ||||
|  | ||||
| 	RegisterWSAction({ | ||||
| 		.action = "chat", | ||||
| 		.handle = HandleWSChat | ||||
| 	}); | ||||
|  | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @@ -191,6 +279,14 @@ void ChatWebAPI::ReceiveRequests() { | ||||
| 	mg_mgr_poll(&mgr, 15); | ||||
| } | ||||
|  | ||||
| void ChatWebAPI::SendWSMessage(const std::string& message) { | ||||
| 	for (struct mg_connection *wc = mgr.conns; wc != NULL; wc = wc->next) { | ||||
| 		if (wc->is_websocket) { | ||||
| 			mg_ws_send(wc, message.c_str(), message.size(), WEBSOCKET_OP_TEXT); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #ifdef DARKFLAME_PLATFORM_WIN32 | ||||
| #pragma pop_macro("DELETE") | ||||
| #endif | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include <functional> | ||||
|  | ||||
| #include "mongoose.h" | ||||
| #include "json_fwd.hpp" | ||||
| #include "eHTTPStatusCode.h" | ||||
|  | ||||
| enum class eHTTPMethod; | ||||
| @@ -15,22 +16,28 @@ struct HTTPReply { | ||||
| 	std::string message = "{\"error\":\"Not Found\"}"; | ||||
| }; | ||||
|  | ||||
| struct WebAPIHTTPRoute { | ||||
| struct HTTPRoute { | ||||
| 	std::string path; | ||||
| 	eHTTPMethod method; | ||||
| 	std::function<void(HTTPReply&, const std::string&)> handle; | ||||
| }; | ||||
|  | ||||
| struct WSAction { | ||||
| 	std::string action; | ||||
| 	std::function<void(nlohmann::json)> handle; | ||||
| }; | ||||
|  | ||||
| class ChatWebAPI { | ||||
| public: | ||||
| 	ChatWebAPI(); | ||||
| 	~ChatWebAPI(); | ||||
| 	void ReceiveRequests(); | ||||
| 	void RegisterHTTPRoutes(WebAPIHTTPRoute route); | ||||
| 	void RegisterHTTPRoute(HTTPRoute route); | ||||
| 	void RegisterWSAction(WSAction action); | ||||
| 	void SendWSMessage(const std::string& message); | ||||
| 	bool Startup(); | ||||
| private: | ||||
| 	mg_mgr mgr; | ||||
|  | ||||
| }; | ||||
|  | ||||
| #endif // __CHATWEBAPI_H__ | ||||
|   | ||||
| @@ -12,6 +12,8 @@ | ||||
| #include "ChatPackets.h" | ||||
| #include "dConfig.h" | ||||
| #include "MessageType/Chat.h" | ||||
| #include "json.hpp" | ||||
| #include "ChatWebAPI.h" | ||||
|  | ||||
| void PlayerContainer::Initialize() { | ||||
| 	m_MaxNumberOfBestFriends = | ||||
| @@ -58,7 +60,17 @@ void PlayerContainer::InsertPlayer(Packet* packet) { | ||||
| 	m_PlayerCount++; | ||||
|  | ||||
| 	LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); | ||||
|  | ||||
| 	// Send to connected websockets | ||||
| 	nlohmann::json wsdata; | ||||
| 	wsdata["action"] = "character_update"; | ||||
| 	wsdata["type"] = "add"; | ||||
| 	wsdata["playerName"] = data.playerName; | ||||
| 	wsdata["playerID"] = data.playerID; | ||||
| 	auto& zoneID = wsdata["zone_id"]; | ||||
| 	zoneID["map_id"] = data.zoneID.GetMapID(); | ||||
| 	zoneID["instance_id"] = data.zoneID.GetInstanceID(); | ||||
| 	zoneID["clone_id"] = data.zoneID.GetCloneID(); | ||||
| 	Game::chatwebapi.SendWSMessage(wsdata.dump()); | ||||
| 	Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID()); | ||||
| 	m_PlayersToRemove.erase(playerId); | ||||
| } | ||||
| @@ -113,6 +125,13 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	nlohmann::json wsdata; | ||||
| 	wsdata["action"] = "character_update"; | ||||
| 	wsdata["type"] = "remove"; | ||||
| 	wsdata["playerName"] = player.playerName; | ||||
| 	wsdata["playerID"] = player.playerID; | ||||
| 	Game::chatwebapi.SendWSMessage(wsdata.dump()); | ||||
|  | ||||
| 	m_PlayerCount--; | ||||
| 	LOG("Removed user: %llu", playerID); | ||||
| 	m_Players.erase(playerID); | ||||
|   | ||||
| @@ -15,6 +15,7 @@ struct SystemAddress; | ||||
| class EntityManager; | ||||
| class dZoneManager; | ||||
| class PlayerContainer; | ||||
| class ChatWebAPI; | ||||
|  | ||||
| namespace Game { | ||||
| 	using signal_t = volatile std::sig_atomic_t; | ||||
| @@ -32,6 +33,8 @@ namespace Game { | ||||
| 	extern dZoneManager* zoneManager; | ||||
| 	extern PlayerContainer playerContainer; | ||||
| 	extern std::string projectVersion; | ||||
| 	extern ChatWebAPI chatwebapi; | ||||
|  | ||||
|  | ||||
| 	inline bool ShouldShutdown() { | ||||
| 		return lastSignal != 0; | ||||
|   | ||||
| @@ -54,7 +54,6 @@ void ChatPackets::SendChatMessage(const SystemAddress& sysAddr, char chatChannel | ||||
| 		bitStream.Write<uint16_t>(message[i]); | ||||
| 	} | ||||
| 	bitStream.Write<uint16_t>(0); | ||||
|  | ||||
| 	SEND_PACKET_BROADCAST; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1306,7 +1306,6 @@ void HandlePacket(Packet* packet) { | ||||
| 			ChatPackets::SendMessageFail(packet->systemAddress); | ||||
| 		} else { | ||||
| 			auto chatMessage = ClientPackets::HandleChatMessage(packet); | ||||
|  | ||||
| 			// TODO: Find a good home for the logic in this case. | ||||
| 			User* user = UserManager::Instance()->GetUser(packet->systemAddress); | ||||
| 			if (!user) { | ||||
| @@ -1328,6 +1327,25 @@ void HandlePacket(Packet* packet) { | ||||
| 			std::string sMessage = GeneralUtils::UTF16ToWTF8(chatMessage.message); | ||||
| 			LOG("%s: %s", playerName.c_str(), sMessage.c_str()); | ||||
| 			ChatPackets::SendChatMessage(packet->systemAddress, chatMessage.chatChannel, playerName, user->GetLoggedInChar(), isMythran, chatMessage.message); | ||||
|  | ||||
| 			CBITSTREAM; | ||||
| 			BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GENERAL_CHAT_MESSAGE); | ||||
|  | ||||
| 			bitStream.Write(user->GetLoggedInChar()); | ||||
| 			bitStream.Write<uint32_t>(chatMessage.message.size()); | ||||
| 			bitStream.Write(chatMessage.chatChannel); | ||||
| 			bitStream.Write<uint32_t>(chatMessage.message.size()); | ||||
|  | ||||
| 			for (uint32_t i = 0; i < 77; ++i) { | ||||
| 				bitStream.Write<uint8_t>(0); | ||||
| 			} | ||||
|  | ||||
| 			for (uint32_t i = 0; i < chatMessage.message.size(); ++i) { | ||||
| 				bitStream.Write<uint16_t>(chatMessage.message[i]); | ||||
| 			} | ||||
| 			bitStream.Write<uint16_t>(0); | ||||
| 			Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE_ORDERED, 0, Game::chatSysAddr, false); | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		break; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Aaron Kimbre
					Aaron Kimbre