From 361fd475625f10f6fc5d0fb6950192dec2583ca5 Mon Sep 17 00:00:00 2001 From: Jessica James Date: Tue, 3 Jan 2017 21:52:14 -0500 Subject: [PATCH] Updated Jupiter and refactored as necessary. --- Bot/IRC_Bot.cpp | 68 ++++++++++++-------------- ChannelRelay/ChannelRelay.cpp | 34 +++++-------- Configs/IRC.Core.ini | 86 +++++++++++++++++---------------- CoreCommands/CoreCommands.cpp | 15 +++--- ExtraCommands/ExtraCommands.cpp | 32 ++++++------ Jupiter | 2 +- RenX.Commands/RenX_Commands.cpp | 41 ++++++++-------- RenX.Core/RenX_Server.cpp | 42 ++++++++-------- 8 files changed, 157 insertions(+), 163 deletions(-) diff --git a/Bot/IRC_Bot.cpp b/Bot/IRC_Bot.cpp index 9acf922..a9846dd 100644 --- a/Bot/IRC_Bot.cpp +++ b/Bot/IRC_Bot.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2013-2016 Jessica James. + * Copyright (C) 2013-2017 Jessica James. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -166,49 +166,45 @@ void IRC_Bot::setCommandAccessLevels(IRCCommand *in_command) set_command_access_levels(this->getPrimaryConfigSection()); } -void IRC_Bot::OnChat(const Jupiter::ReadableString &channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString &message) +void IRC_Bot::OnChat(const Jupiter::ReadableString &in_channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString &message) { - int chanIndex = this->getChannelIndex(channel); - if (chanIndex >= 0) + Channel *channel = this->getChannel(in_channel); + if (channel != nullptr && channel->getType() >= 0) { - Channel *chan = this->getChannel(chanIndex); - if (chan->getType() >= 0) - { - Jupiter::ReferenceString msg = message; - while (msg.isNotEmpty() && isspace(msg[0])) - msg.shiftRight(1); + Jupiter::ReferenceString msg = message; + while (msg.isNotEmpty() && isspace(msg[0])) + msg.shiftRight(1); - if (IRC_Bot::commandPrefix.size() <= msg.size()) + if (IRC_Bot::commandPrefix.size() <= msg.size()) + { + bool matchesPrefix = true; + size_t i; + for (i = 0; i != IRC_Bot::commandPrefix.size(); i++) { - bool matchesPrefix = true; - size_t i; - for (i = 0; i != IRC_Bot::commandPrefix.size(); i++) + if (toupper(msg.get(0)) != toupper(IRC_Bot::commandPrefix[i])) { - if (toupper(msg.get(0)) != toupper(IRC_Bot::commandPrefix[i])) - { - matchesPrefix = false; - break; - } - msg.shiftRight(1); + matchesPrefix = false; + break; } + msg.shiftRight(1); + } - if (matchesPrefix) + if (matchesPrefix) + { + Jupiter::ReferenceString command = Jupiter::ReferenceString::getWord(msg, 0, WHITESPACE);; + Jupiter::ReferenceString parameters = Jupiter::ReferenceString::gotoWord(msg, 1, WHITESPACE); + IRCCommand *cmd = IRC_Bot::getCommand(command); + if (cmd != nullptr) { - Jupiter::ReferenceString command = Jupiter::ReferenceString::getWord(msg, 0, WHITESPACE);; - Jupiter::ReferenceString parameters = Jupiter::ReferenceString::gotoWord(msg, 1, WHITESPACE); - IRCCommand *cmd = IRC_Bot::getCommand(command); - if (cmd != nullptr) - { - IRCCommand::active_server = this; - int command_access = cmd->getAccessLevel(chan); - if (command_access < 0) - this->sendNotice(nick, "Error: Command disabled."_jrs); - else if (Jupiter::IRC::Client::getAccessLevel(channel, nick) < command_access) - this->sendNotice(nick, "Access Denied."_jrs); - else - cmd->trigger(this, channel, nick, parameters); - IRCCommand::active_server = IRCCommand::selected_server; - } + IRCCommand::active_server = this; + int command_access = cmd->getAccessLevel(channel); + if (command_access < 0) + this->sendNotice(nick, "Error: Command disabled."_jrs); + else if (Jupiter::IRC::Client::getAccessLevel(*channel, nick) < command_access) + this->sendNotice(nick, "Access Denied."_jrs); + else + cmd->trigger(this, in_channel, nick, parameters); + IRCCommand::active_server = IRCCommand::selected_server; } } } diff --git a/ChannelRelay/ChannelRelay.cpp b/ChannelRelay/ChannelRelay.cpp index a54425d..195c5ee 100644 --- a/ChannelRelay/ChannelRelay.cpp +++ b/ChannelRelay/ChannelRelay.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2015-2016 Jessica James. + * Copyright (C) 2015-2017 Jessica James. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -53,33 +53,23 @@ void ChannelRelayPlugin::OnChat(Jupiter::IRC::Client *server, const Jupiter::Rea int type = chan->getType(); if (ChannelRelayPlugin::types.contains(type)) { - Jupiter::IRC::Client::Channel *tchan; - Jupiter::IRC::Client *tserver; unsigned int count = server->getChannelCount(); unsigned int serverCount = serverManager->size(); char prefix = chan->getUserPrefix(nick); - auto str = prefix == 0 ? "<"_jrs + nick + "> "_jrs + message : "<"_js + prefix + nick + "> "_jrs + message; - while (count != 0) + Jupiter::String str = prefix == 0 + ? "<"_jrs + nick + "> "_jrs + message + : "<"_js + prefix + nick + "> "_jrs + message; + + Jupiter::IRC::Client *irc_server; + + auto relay_channels_callback = [irc_server, type, chan, &str](Jupiter::IRC::Client::ChannelTableType::Bucket::Entry &in_entry) { - tchan = server->getChannel(--count); - if (tchan->getType() == type && chan != tchan) - server->sendMessage(tchan->getName(), str); - } + if (in_entry.value.getType() == type && &in_entry.value != chan) + irc_server->sendMessage(in_entry.value.getName(), str); + }; while (serverCount != 0) - { - tserver = serverManager->getServer(--serverCount); - if (tserver != server) - { - count = tserver->getChannelCount(); - while (count != 0) - { - tchan = tserver->getChannel(--count); - if (tchan->getType() == type) - tserver->sendMessage(tchan->getName(), str); - } - } - } + serverManager->getServer(--serverCount)->getChannels().callback(relay_channels_callback); } } } diff --git a/Configs/IRC.Core.ini b/Configs/IRC.Core.ini index 211b864..8cd5029 100644 --- a/Configs/IRC.Core.ini +++ b/Configs/IRC.Core.ini @@ -14,19 +14,11 @@ Servers=CnCIRC ; therefore recommended to have some sort of default ; value for all possible settings. ; -; Channel Configuring: +; Configuring Channels: ; Channels are given "types", which can determine various things such ; as auto-parting. A Channel of type -1 will automatically part itself. -; Channels can be configured in two ways: Active and Passive. -; Active channels are joined automatically. Passive channels are not, -; but can still be configured. -; -; Active Channels: -; Channel.Integer=String (Format: #Example [Password]) -; Channel.Integer.Type=Integer (-1 is auto-part) -; -; Passive Channels: -; Channel.String.Type=Integer +; Channels can also be confirmed to be automatically joined, by setting +; the AutoJoin setting to True. ; ; Client address and port: ; The client address and port settings are entirely optional. These are @@ -72,29 +64,29 @@ PrintOutput=1 Channel.Type=-1 Prefix=! -; [[Commands]] -; You can modify the access requirements for any command here. -; Values set here will be set across all servers that do not have -; server-specific values set. -; -; To disable a command, set its access requirement to -1. -; -; Syntax: CommandTrigger=AccessLevel -; + ; [[Commands]] + ; You can modify the access requirements for any command here. + ; Values set here will be set across all servers that do not have + ; server-specific values set. + ; + ; To disable a command, set its access requirement to -1. + ; + ; Syntax: CommandTrigger=AccessLevel + ; -[[Commands]] -rehash=4 -select=4 -deselect=4 -raw=5 -ircmsg=5 -join=3 -part=3 -debuginfo=4 -exit=5 -ircconnect=5 -ircdisconnect=5 -plugin=5 + [[Commands]] + rehash=4 + select=4 + deselect=4 + raw=5 + ircmsg=5 + join=3 + part=3 + debuginfo=4 + exit=5 + ircconnect=5 + ircdisconnect=5 + plugin=5 ; [(ServerName)] ; @@ -106,18 +98,30 @@ plugin=5 [CnCIRC] ; CnCIRC includes the Renegade X IRC server. :) Hostname=irc.cncirc.net -Channel.1=#RenX-IRC -Channel.1.Type=1 -Channel.2=#RenX-IRC.Admin -Channel.2.Type=2 SASL.Password=your_NickServ_Password + [[Channels]] + + [[[#RenX-IRC]]] + AutoJoin=True + Type=1 + + [[[#RenX-IRC.Admin]]] + AutoJoin=True + Type=2 + [CT] Hostname=irc.ConstructiveTyranny.com -Channel.1=#RenX-IRC -Channel.1.Type=1 -Channel.2=#RenX-IRC.Admin -Channel.2.Type=2 RawData.1=PRIVMSG NickServ :IDENTIFY your_NickServ_Password + [[Channels]] + + [[[#RenX-IRC]]] + AutoJoin=True + Type=1 + + [[[#RenX-IRC.Admin]]] + AutoJoin=True + Type=2 + ;EOF \ No newline at end of file diff --git a/CoreCommands/CoreCommands.cpp b/CoreCommands/CoreCommands.cpp index 5384e30..1893937 100644 --- a/CoreCommands/CoreCommands.cpp +++ b/CoreCommands/CoreCommands.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2014-2016 Jessica James. + * Copyright (C) 2014-2017 Jessica James. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -70,18 +70,17 @@ void HelpIRCCommand::create() this->addTrigger("help"_jrs); } -void HelpIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString ¶meters) +void HelpIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &in_channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString ¶meters) { - int cIndex = source->getChannelIndex(channel); - if (cIndex >= 0) + Jupiter::IRC::Client::Channel *channel = source->getChannel(in_channel); + if (channel != nullptr) { - Jupiter::IRC::Client::Channel *chan = source->getChannel(cIndex); - int access = source->getAccessLevel(channel, nick); + int access = source->getAccessLevel(*channel, nick); if (parameters == nullptr) { for (int i = 0; i <= access; i++) { - Jupiter::ArrayList cmds = source->getAccessCommands(chan, i); + Jupiter::ArrayList cmds = source->getAccessCommands(channel, i); if (cmds.size() != 0) { Jupiter::StringL &triggers = source->getTriggers(cmds); @@ -96,7 +95,7 @@ void HelpIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &cha IRCCommand *cmd = source->getCommand(Jupiter::ReferenceString::getWord(parameters, 0, WHITESPACE)); if (cmd) { - int command_access = cmd->getAccessLevel(chan); + int command_access = cmd->getAccessLevel(channel); if (command_access < 0) source->sendNotice(nick, "Error: Command disabled."_jrs); diff --git a/ExtraCommands/ExtraCommands.cpp b/ExtraCommands/ExtraCommands.cpp index 86d4baf..a8c6fde 100644 --- a/ExtraCommands/ExtraCommands.cpp +++ b/ExtraCommands/ExtraCommands.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2014-2016 Jessica James. + * Copyright (C) 2014-2017 Jessica James. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -252,28 +252,30 @@ Jupiter::GenericCommand::ResponseLine *DebugInfoGenericCommand::trigger(const Ju else return new Jupiter::GenericCommand::ResponseLine("Error: No IRC server is currently selected."_jrs, GenericCommand::DisplayType::PublicError); - Jupiter::IRC::Client::Channel *chan; - Jupiter::IRC::Client::User *user; Jupiter::GenericCommand::ResponseLine *ret = new Jupiter::GenericCommand::ResponseLine("Prefixes: "_jrs + server->getPrefixes(), GenericCommand::DisplayType::PublicSuccess); Jupiter::GenericCommand::ResponseLine *line = new Jupiter::GenericCommand::ResponseLine("Prefix Modes: "_jrs + server->getPrefixModes(), GenericCommand::DisplayType::PublicSuccess); ret->next = line; line->next = new Jupiter::GenericCommand::ResponseLine(Jupiter::StringS::Format("Outputting data for %u channels...", server->getChannelCount()), GenericCommand::DisplayType::PublicSuccess); line = line->next; - for (unsigned int index = 0; index < server->getChannelCount(); ++index) + + auto debug_callback = [&line](Jupiter::IRC::Client::ChannelTableType::Bucket::Entry &in_entry) { - chan = server->getChannel(index); - if (chan != nullptr) + line->next = new Jupiter::GenericCommand::ResponseLine(Jupiter::StringS::Format("Channel %.*s - Type: %d", in_entry.value.getName().size(), in_entry.value.getName().ptr(), in_entry.value.getType()), GenericCommand::DisplayType::PublicSuccess); + line = line->next; + + auto debug_user_callback = [&line, &in_entry](Jupiter::IRC::Client::Channel::UserTableType::Bucket::Entry &in_user_entry) { - line->next = new Jupiter::GenericCommand::ResponseLine(Jupiter::StringS::Format("Channel %.*s - Type: %d", chan->getName().size(), chan->getName().ptr(), chan->getType()), GenericCommand::DisplayType::PublicSuccess); + Jupiter::IRC::Client::User *user = in_user_entry.value.getUser(); + line->next = new Jupiter::GenericCommand::ResponseLine(Jupiter::StringS::Format("User %.*s!%.*s@%.*s (prefix: %c) of channel %.*s (of %u shared)", user->getNickname().size(), user->getNickname().ptr(), user->getUsername().size(), user->getUsername().ptr(), user->getHostname().size(), user->getHostname().ptr(), in_entry.value.getUserPrefix(in_user_entry.value) ? in_entry.value.getUserPrefix(in_user_entry.value) : ' ', in_entry.value.getName().size(), in_entry.value.getName().ptr(), user->getChannelCount()), GenericCommand::DisplayType::PublicSuccess); line = line->next; - for (unsigned int j = 0; j != chan->getUserCount(); ++j) - { - user = chan->getUser(j)->getUser(); - line->next = new Jupiter::GenericCommand::ResponseLine(Jupiter::StringS::Format("User %.*s!%.*s@%.*s (prefix: %c) of channel %.*s (of %u shared)", user->getNickname().size(), user->getNickname().ptr(), user->getUsername().size(), user->getUsername().ptr(), user->getHostname().size(), user->getHostname().ptr(), chan->getUserPrefix(j) ? chan->getUserPrefix(j) : ' ', chan->getName().size(), chan->getName().ptr(), user->getChannelCount()), GenericCommand::DisplayType::PublicSuccess); - line = line->next; - } - } - } + }; + + in_entry.value.getUsers().callback(debug_user_callback); + }; + + for (unsigned int index = 0; index < server->getChannelCount(); ++index) + server->getChannels().callback(debug_callback); + return ret; } diff --git a/Jupiter b/Jupiter index 8fbb123..9e5ce1f 160000 --- a/Jupiter +++ b/Jupiter @@ -1 +1 @@ -Subproject commit 8fbb12310ea9be59420f59406e3d3054686d2a2c +Subproject commit 9e5ce1fce6a422c92ca444fbfd1704f5ffdf6033 diff --git a/RenX.Commands/RenX_Commands.cpp b/RenX.Commands/RenX_Commands.cpp index 3e7e464..dacd01a 100644 --- a/RenX.Commands/RenX_Commands.cpp +++ b/RenX.Commands/RenX_Commands.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2014-2016 Jessica James. + * Copyright (C) 2014-2017 Jessica James. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -3191,31 +3191,30 @@ void ModRequestGameCommand::trigger(RenX::Server *source, RenX::PlayerInfo *play unsigned int channelCount; unsigned int messageCount = 0; Jupiter::String &fmtName = RenX::getFormattedPlayerName(player); - Jupiter::StringL msg = Jupiter::StringL::Format(IRCCOLOR "12[%.*s Request] " IRCCOLOR IRCBOLD "%.*s" IRCBOLD IRCCOLOR "07 has requested assistance in-game; please look in ", staff_word.size(), staff_word.ptr(), fmtName.size(), fmtName.ptr()); - Jupiter::StringS msg2 = Jupiter::StringS::Format(IRCCOLOR "12[%.*s Request] " IRCCOLOR IRCBOLD "%.*s" IRCBOLD IRCCOLOR "07 has requested assistance in-game!" IRCCOLOR, staff_word.size(), staff_word.ptr(), fmtName.size(), fmtName.ptr()); - for (size_t server_index = 0; server_index < serverCount; ++server_index) + Jupiter::StringL user_message = Jupiter::StringL::Format(IRCCOLOR "12[%.*s Request] " IRCCOLOR IRCBOLD "%.*s" IRCBOLD IRCCOLOR "07 has requested assistance in-game; please look in ", staff_word.size(), staff_word.ptr(), fmtName.size(), fmtName.ptr()); + Jupiter::StringS channel_message = Jupiter::StringS::Format(IRCCOLOR "12[%.*s Request] " IRCCOLOR IRCBOLD "%.*s" IRCBOLD IRCCOLOR "07 has requested assistance in-game!" IRCCOLOR, staff_word.size(), staff_word.ptr(), fmtName.size(), fmtName.ptr()); + + auto alert_message_callback = [this, source, server, &user_message, &channel_message, &messageCount](Jupiter::IRC::Client::ChannelTableType::Bucket::Entry &in_entry) { - server = serverManager->getServer(server_index); - channelCount = server->getChannelCount(); - for (unsigned int b = 0; b < channelCount; b++) + auto alert_message_user_callback = [server, &in_entry, &user_message, &messageCount](Jupiter::IRC::Client::Channel::UserTableType::Bucket::Entry &in_user_entry) { - channel = server->getChannel(b); - if (source->isLogChanType(channel->getType())) + if (in_entry.value.getUserPrefix(in_user_entry.value) != 0 && in_user_entry.value.getNickname().equals(server->getNickname()) == false) { - server->sendMessage(channel->getName(), msg2); - msg += channel->getName(); - for (unsigned int c = 0; c < channel->getUserCount(); c++) - { - if (channel->getUserPrefix(c) != 0 && channel->getUser(c)->getNickname().equals(server->getNickname()) == false) - { - server->sendMessage(channel->getUser(c)->getUser()->getNickname(), msg); - messageCount++; - } - } - msg -= channel->getName().size(); + server->sendMessage(in_user_entry.value.getUser()->getNickname(), user_message); + ++messageCount; } + }; + + if (source->isAdminLogChanType(in_entry.value.getType())) + { + server->sendMessage(in_entry.value.getName(), channel_message); + + user_message += in_entry.value.getName(); + in_entry.value.getUsers().callback(alert_message_user_callback); + user_message -= in_entry.value.getName().size(); } - } + }; + source->sendMessage(player, Jupiter::StringS::Format("A total of %u %.*ss have been notified of your assistance request.", messageCount, staff_word.size(), staff_word.ptr())); } diff --git a/RenX.Core/RenX_Server.cpp b/RenX.Core/RenX_Server.cpp index 8263576..e3cf56e 100644 --- a/RenX.Core/RenX_Server.cpp +++ b/RenX.Core/RenX_Server.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2014-2016 Jessica James. + * Copyright (C) 2014-2017 Jessica James. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -625,28 +625,32 @@ void RenX::Server::banCheck(RenX::PlayerInfo *player) { unsigned int serverCount = serverManager->size(); IRC_Bot *server; - Jupiter::IRC::Client::Channel *channel; - unsigned int channelCount; Jupiter::String &fmtName = RenX::getFormattedPlayerName(player); - Jupiter::StringL msg = Jupiter::StringL::Format(IRCCOLOR "04[Alert] " IRCCOLOR IRCBOLD "%.*s" IRCBOLD IRCCOLOR " is marked for monitoring by %.*s for: \"%.*s\". Please keep an eye on them in ", fmtName.size(), fmtName.ptr(), last_to_expire[6]->banner.size(), last_to_expire[6]->banner.ptr(), last_to_expire[6]->reason.size(), last_to_expire[6]->reason.ptr()); - Jupiter::StringS msg2 = Jupiter::StringS::Format(IRCCOLOR "04[Alert] " IRCCOLOR IRCBOLD "%.*s" IRCBOLD IRCCOLOR " is marked for monitoring by %.*s for: \"%.*s\"." IRCCOLOR, fmtName.size(), fmtName.ptr(), last_to_expire[6]->banner.size(), last_to_expire[6]->banner.ptr(), last_to_expire[6]->reason.size(), last_to_expire[6]->reason.ptr()); - for (unsigned int a = 0; a < serverCount; a++) + Jupiter::StringL user_message = Jupiter::StringL::Format(IRCCOLOR "04[Alert] " IRCCOLOR IRCBOLD "%.*s" IRCBOLD IRCCOLOR " is marked for monitoring by %.*s for: \"%.*s\". Please keep an eye on them in ", fmtName.size(), fmtName.ptr(), last_to_expire[6]->banner.size(), last_to_expire[6]->banner.ptr(), last_to_expire[6]->reason.size(), last_to_expire[6]->reason.ptr()); + Jupiter::StringS channel_message = Jupiter::StringS::Format(IRCCOLOR "04[Alert] " IRCCOLOR IRCBOLD "%.*s" IRCBOLD IRCCOLOR " is marked for monitoring by %.*s for: \"%.*s\"." IRCCOLOR, fmtName.size(), fmtName.ptr(), last_to_expire[6]->banner.size(), last_to_expire[6]->banner.ptr(), last_to_expire[6]->reason.size(), last_to_expire[6]->reason.ptr()); + + auto alert_message_callback = [this, server, &user_message, &channel_message](Jupiter::IRC::Client::ChannelTableType::Bucket::Entry &in_entry) { - server = serverManager->getServer(a); - channelCount = server->getChannelCount(); - for (unsigned int b = 0; b < channelCount; b++) + auto alert_message_user_callback = [server, &in_entry, &user_message](Jupiter::IRC::Client::Channel::UserTableType::Bucket::Entry &in_user_entry) { - channel = server->getChannel(b); - if (this->isAdminLogChanType(channel->getType())) - { - server->sendMessage(channel->getName(), msg2); - msg += channel->getName(); - for (unsigned int c = 0; c < channel->getUserCount(); c++) - if (channel->getUserPrefix(c) != 0 && channel->getUser(c)->getNickname().equals(server->getNickname()) == false) - server->sendMessage(channel->getUser(c)->getUser()->getNickname(), msg); - msg -= channel->getName().size(); - } + if (in_entry.value.getUserPrefix(in_user_entry.value) != 0 && in_user_entry.value.getNickname().equals(server->getNickname()) == false) + server->sendMessage(in_user_entry.value.getUser()->getNickname(), user_message); + }; + + if (this->isAdminLogChanType(in_entry.value.getType())) + { + server->sendMessage(in_entry.value.getName(), channel_message); + + user_message += in_entry.value.getName(); + in_entry.value.getUsers().callback(alert_message_user_callback); + user_message -= in_entry.value.getName().size(); } + }; + + for (size_t server_index = 0; server_index < serverManager->size(); ++server_index) + { + server = serverManager->getServer(server_index); + server->getChannels().callback(alert_message_callback); } } }