void netCallbacksHandler(CNetMessage &msgin) { // nldebug("NET: %"NL_I64"u Received message type %hu size %u", CTime::getLocalTime(), (uint16)msgin.type(), msgin.length()); switch(msgin.type()) { SWITCH_CASE(Chat); SWITCH_CASE(Error); SWITCH_CASE(Login); SWITCH_CASE(Logout); SWITCH_CASE(OpenClose); SWITCH_CASE(Update); SWITCH_CASE(UpdateOne); SWITCH_CASE(FullUpdate); SWITCH_CASE(UpdateElement); SWITCH_CASE(Ready); SWITCH_CASE(EverybodyReady); SWITCH_CASE(RequestDownload); SWITCH_CASE(RequestCRCKey); SWITCH_CASE(DisplayText); SWITCH_CASE(SessionState); SWITCH_CASE(StartSession); SWITCH_CASE(EndSession); SWITCH_CASE(EditMode); SWITCH_CASE(EnableElement); SWITCH_CASE(ExecLua); SWITCH_CASE(CollideWhenFly); SWITCH_CASE(TimeArrival); default: nlwarning("Received an unknown message type %hu", (uint16)msgin.type()); break; } }
bool CNetClient::OnInGame(void *context, CFsmEvent* event) { // TODO: should split each of these cases into a separate method CNetClient* client = (CNetClient*)context; CNetMessage* message = (CNetMessage*)event->GetParamRef(); if (message) { if (message->GetType() == NMT_SIMULATION_COMMAND) { CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message); client->m_ClientTurnManager->OnSimulationMessage(simMessage); } else if (message->GetType() == NMT_SYNC_ERROR) { CSyncErrorMessage* syncMessage = static_cast<CSyncErrorMessage*> (message); client->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected, syncMessage->m_PlayerNames); } else if (message->GetType() == NMT_END_COMMAND_BATCH) { CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message); client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength); } } return true; }
static void cbStartSession(CNetMessage &msgin) { float timebeforestart, timeout; string levelName, str1, str2; vector<uint8> ranks; vector<uint8> eids; msgin.serial(timebeforestart, timeout, levelName, str1, str2); msgin.serialCont(ranks); msgin.serialCont(eids); nlinfo("NET: cbStartSession timebeforestart=%f timeout=%f levelName='%s' str1='%s' str2='%s'", timebeforestart, timeout, levelName.c_str(), str1.c_str(), str2.c_str()); nlassert(ranks.size()==eids.size()); for(uint32 i=0;i<eids.size();i++) { if(CEntityManager::getInstance().exist(eids[i])) CEntityManager::getInstance()[eids[i]].rank(ranks[i]); } CMtpTarget::getInstance().startSession(timebeforestart / 1000.0f, timeout / 1000.0f, levelName, str1, str2); /* msgin.serial (mtpTarget::getInstance().Interface2d.LevelName); msgin.serial (mtpTarget::getInstance().Interface2d.FullLevelName); msgin.serial (mtpTarget::getInstance().Interface2d.string1); msgin.serial (mtpTarget::getInstance().Interface2d.string2); msgin.serial (mtpTarget::getInstance().Interface2d.Author); mtpTarget::getInstance().startSession(timebeforestart / 1000, timeout / 1000, mtpTarget::getInstance().Interface2d.LevelName); */ }
void CNetClientSession::Poll() { PROFILE3("net client poll"); ENSURE(m_Host && m_Server); m_FileTransferer.Poll(); ENetEvent event; while (enet_host_service(m_Host, &event, 0) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: { ENSURE(event.peer == m_Server); // Report the server address char hostname[256] = "(error)"; enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname)); LOGMESSAGE("Net client: Connected to %s:%u", hostname, (unsigned int)event.peer->address.port); m_Client.HandleConnect(); break; } case ENET_EVENT_TYPE_DISCONNECT: { ENSURE(event.peer == m_Server); LOGMESSAGE("Net client: Disconnected"); m_Client.HandleDisconnect(event.data); return; } case ENET_EVENT_TYPE_RECEIVE: { CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, m_Client.GetScriptInterface()); if (msg) { LOGMESSAGE("Net client: Received message %s of size %lu from server", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength()); m_Client.HandleMessage(msg); delete msg; } enet_packet_destroy(event.packet); break; } case ENET_EVENT_TYPE_NONE: break; } } }
float serialIn8_8fp(CNetMessage &msgin) { uint8 sx; uint8 rdx; msgin.serial(sx); msgin.serial(rdx); return convert8_8fp(sx,rdx); }
static void cbUpdateList(CNetMessage &msgin) { CEntityManager::getInstance().updateListId.clear(); while(msgin.getPos() < (sint32)msgin.length()) { uint8 eid; msgin.serial(eid); CEntityManager::getInstance().updateListId.push_back(eid); } }
static void cbChat(CNetMessage &msgin) { nlinfo("NET: cbChat"); string msg; while(msgin.getPos() < (sint32)msgin.length()) { msgin.serial(msg); CChatTask::getInstance().addLine(msg); if(SessionFile) fprintf(SessionFile, "%hu CH %s\n",0, msg.c_str()); } }
void handleClients() { CNetMessage* msg; int channel; if(udpclient->Receive(msg, channel)) { if(msg->getType()=='h') { hero_pos pos; msg->UnLoadByte(pos.x, pos.y, pos.id); if(pos.id!=myId) { int oriX = heroGroup[pos.id]->getX(); int oriY = heroGroup[pos.id]->getY(); heroGroup[pos.id]->setCoords(pos.x, pos.y); int frame = heroGroup[pos.id]->getFrame(); if (pos.y<oriY) { frame++; if (frame>2) frame = 0; heroGroup[pos.id]->setAnimFrame(frame+6); } else if (pos.x>oriX) { frame++; if (frame>2) frame = 0; heroGroup[pos.id]->setAnimFrame(frame+3); } else if (pos.y>oriY) { frame++; if (frame>2) frame = 0; heroGroup[pos.id]->setAnimFrame(frame); } else if (pos.x<oriX) { frame++; if (frame>2) frame = 0; heroGroup[pos.id]->setAnimFrame(frame+9); } heroGroup[pos.id]->setFrame(frame); } } else if(msg->getType()=='b') { int bx, by, lvl; msg->UnLoadByte(bx, by, lvl); Bomb* newbomb=new Bomb(bx, by, 4000, SDL_GetTicks(), lvl); bombGroup.push_back(newbomb); } } delete msg; }
bool CNetServerWorker::OnInGame(void* context, CFsmEvent* event) { // TODO: should split each of these cases into a separate method CNetServerSession* session = (CNetServerSession*)context; CNetServerWorker& server = session->GetServer(); CNetMessage* message = (CNetMessage*)event->GetParamRef(); if (message->GetType() == (uint)NMT_SIMULATION_COMMAND) { CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message); // Ignore messages sent by one player on behalf of another player // unless cheating is enabled bool cheatsEnabled = false; ScriptInterface& scriptInterface = server.GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue settings(cx); scriptInterface.GetProperty(server.m_GameAttributes.get(), "settings", &settings); if (scriptInterface.HasProperty(settings, "CheatsEnabled")) scriptInterface.GetProperty(settings, "CheatsEnabled", cheatsEnabled); PlayerAssignmentMap::iterator it = server.m_PlayerAssignments.find(session->GetGUID()); // When cheating is disabled, fail if the player the message claims to // represent does not exist or does not match the sender's player name if (!cheatsEnabled && (it == server.m_PlayerAssignments.end() || it->second.m_PlayerID != simMessage->m_Player)) return true; // Send it back to all clients immediately server.Broadcast(simMessage); // Save all the received commands if (server.m_SavedCommands.size() < simMessage->m_Turn + 1) server.m_SavedCommands.resize(simMessage->m_Turn + 1); server.m_SavedCommands[simMessage->m_Turn].push_back(*simMessage); // TODO: we shouldn't send the message back to the client that first sent it } else if (message->GetType() == (uint)NMT_SYNC_CHECK) { CSyncCheckMessage* syncMessage = static_cast<CSyncCheckMessage*> (message); server.m_ServerTurnManager->NotifyFinishedClientUpdate(session->GetHostID(), session->GetUserName(), syncMessage->m_Turn, syncMessage->m_Hash); } else if (message->GetType() == (uint)NMT_END_COMMAND_BATCH) { CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message); server.m_ServerTurnManager->NotifyFinishedClientCommands(session->GetHostID(), endMessage->m_Turn); } return true; }
static void cbEditMode(CNetMessage &msgin) { uint8 editMode; msgin.serial(editMode); nlinfo("NET: cbEditMode editMode=%hu", (uint16)editMode); // TODO: what is the goal of this function??? }
/** * @brief Enviar un paquete encapsulado en un objeto del tipo CNetMessage * * @param sData Estructura de donde se sacarán los datos a enviar. * * @return Si se ha podido enviar algo, devolverá true. En caso contrario, devolverá false (llamada no bloqueante). */ bool CClientSocket::Send (CNetMessage& sData) { //check if there is a socket if (m_Socket == NULL) return false; charbuf buf; int len; //Check if the instance can send bytes, if it can, unload the number of bytes specified by NumToLoad() virtual function while ((len = sData.NumToUnLoad()) > 0) { sData.UnLoadBytes (buf); if (SDLNet_TCP_Send(m_Socket, (void *)buf, len) < len) { std::cerr << "SDLNet_TCP_Send: " << SDLNet_GetError() << std::endl; return false; } } return true; }
static void cbSessionState(CNetMessage &msgin) { string sn; msgin.serial(sn); nlinfo("NET: cbSessionState sn='%s'", sn.c_str()); // TODO: what is the goal of this function??? }
/** * @brief Recibir un paquete encapsulado en un objeto del tipo CNetMessage * * @param rData Estructura donde se van a almacenar los datos recibidos. * * @return Si se ha recibido algo, devolverá true. En caso de no recibir nada, devolverá false (llamada no bloqueante). * * Una vez recibido el mensaje, se cambia su estado a FULL. */ bool CClientSocket::Receive(CNetMessage& rData) { //Firstly, check if there is a socket if (m_Socket == NULL) return false; charbuf buf; //Check if the instance can receive bytes, if it can, load the number of bytes specified by NumToLoad() virtual function while (rData.NumToLoad() > 0) { if (SDLNet_TCP_Recv(m_Socket, buf, rData.NumToLoad()) > 0) { rData.LoadBytes (buf, rData.NumToLoad()); } else { return false; } } rData.finish(); return true; }
static void cbExecLua(CNetMessage &msgin) { string luaCode; msgin.serial(luaCode); nlinfo("NET: cbExecLua luaCode='%s'", luaCode.c_str()); if(CLevelManager::getInstance().levelPresent()) CLevelManager::getInstance().currentLevel().execLuaCode(luaCode); }
static void cbRequestCRCKey(CNetMessage &msgin) { string fn; CHashKey hashKey; msgin.serial(fn, hashKey); nlinfo("NET: cbRequestCRCKey fn='%s' hashKey='%s'", fn.c_str(), hashKey.toString().c_str()); CResourceManagerLan::getInstance().receivedCRC(fn); }
static void cbEnableElement(CNetMessage &msgin) { uint8 elementId; bool enabled; msgin.serial(elementId, enabled); nlinfo("NET: cbEnableElement"); if(CLevelManager::getInstance().levelPresent()) CLevelManager::getInstance().currentLevel().getModule(elementId)->enabled(enabled); }
static void cbRequestDownload(CNetMessage &msgin) { string res; vector<uint8> buf; bool eof = false; uint32 fileSize = 0xFFFFFFFF; msgin.serial(res); nlinfo("NET: cbRequestDownload res = '%s'", res.c_str()); bool receivedError = res.find("FILE:")!=0; if(!receivedError) { msgin.serial(fileSize); msgin.serialCont(buf); msgin.serial(eof); } CResourceManagerLan::getInstance().receivedBlock(res, buf, eof, fileSize, receivedError); }
float serialOut8_8fp(CNetMessage &msgout,float x,uint8 &rdx,uint8 &rsx) { float mx; uint8 dx; uint8 sx = computeMantis8_8(x,mx,dx); //sint8 dx = (sint8)(x*mx); msgout.serial(sx); msgout.serial(dx); rsx = sx; rdx = dx; return convert8_8fp(rsx,rdx); }
static void cbReady(CNetMessage &msgin) { // called when one player is ready uint8 eid; msgin.serial(eid); nlinfo("NET: cbReady eid=%hu", (uint16)eid); // check if the player exists if(!CEntityManager::getInstance().exist(eid)) { nlwarning("The eid doesn't exist"); return; } CEntityManager::getInstance()[eid].ready(true); }
static void cbDisplayText(CNetMessage &msgin) { float x, y, s; CRGBA col; double duration; string message; msgin.serial(x, y, s, message, col, duration); nlinfo("NET: cbDisplayText x=%f y=%f s=%f col=(%d,%d,%d), duration=%f message='%s'", x, y, s, col.R, col.G, col.B, duration, message.c_str()); CHudTask::getInstance().addMessage(CHudMessage(x,y,s,message,col,duration)); }
static void cbOpenClose(CNetMessage &msgin) { uint8 eid; msgin.serial(eid); nlinfo("NET: cbOpenClose eid=%hu (%s)", (uint16)eid,CEntityManager::getInstance()[eid].name().c_str()); // check if the player exists if(!CEntityManager::getInstance().exist(eid)) { nlwarning("The eid doesn't exist"); return; } //CEntityManager::getInstance()[eid].addOpenCloseKey = true; CEntityManager::getInstance()[eid].swapOpenClose(); if(SessionFile) fprintf(SessionFile, "%hu OC\n", (uint16)eid); }
bool CNetServerWorker::OnInGame(void* context, CFsmEvent* event) { // TODO: should split each of these cases into a separate method CNetServerSession* session = (CNetServerSession*)context; CNetServerWorker& server = session->GetServer(); CNetMessage* message = (CNetMessage*)event->GetParamRef(); if (message->GetType() == (uint)NMT_SIMULATION_COMMAND) { CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message); // Send it back to all clients immediately server.Broadcast(simMessage); // Save all the received commands if (server.m_SavedCommands.size() < simMessage->m_Turn + 1) server.m_SavedCommands.resize(simMessage->m_Turn + 1); server.m_SavedCommands[simMessage->m_Turn].push_back(*simMessage); // TODO: we should do some validation of ownership (clients can't send commands on behalf of opposing players) // TODO: we shouldn't send the message back to the client that first sent it } else if (message->GetType() == (uint)NMT_SYNC_CHECK) { CSyncCheckMessage* syncMessage = static_cast<CSyncCheckMessage*> (message); server.m_ServerTurnManager->NotifyFinishedClientUpdate(session->GetHostID(), syncMessage->m_Turn, syncMessage->m_Hash); } else if (message->GetType() == (uint)NMT_END_COMMAND_BATCH) { CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message); server.m_ServerTurnManager->NotifyFinishedClientCommands(session->GetHostID(), endMessage->m_Turn); } return true; }
static void cbEndSession(CNetMessage &msgin) { nlinfo("NET: cbEndSession"); while(msgin.getPos() < (sint32)msgin.length()) { uint8 eid; sint32 currentScore, totalScore; msgin.serial(eid, currentScore, totalScore); //CEntity *entity = mtpTarget::getInstance().World.getEntityById(id); //nlassert(entity!=0); //entity->setScore(currentScore, totalScore); if(CEntityManager::getInstance().exist(eid)) { CEntityManager::getInstance()[eid].currentScore(currentScore); CEntityManager::getInstance()[eid].totalScore(totalScore); } } mtpTarget::getInstance().endSession(); if(SessionFile) { fclose (SessionFile); SessionFile = 0; if(CMtpTarget::getInstance().moveReplay()) { string markedReplayDir = "replay/marked/"; if(!NLMISC::CFile::isDirectory(markedReplayDir)) NLMISC::CFile::createDirectory(markedReplayDir); string newFilename = markedReplayDir + NLMISC::CFile::getFilename(CMtpTarget::getInstance().sessionFileName()); NLMISC::CFile::moveFile(newFilename.c_str(),CMtpTarget::getInstance().sessionFileName().c_str()); } CMtpTarget::getInstance().moveReplay(false); } }
static void cbLogin(CNetMessage &msgin) { bool self; uint8 eid; string name; sint32 totalScore; CRGBA color; string texture; bool spec; bool oc = false; string trace = "trace"; string mesh = "pingoo"; msgin.serial(self, eid, name, totalScore); msgin.serial(color, texture, spec); string levelName; float timeBeforeTimeout; if(self) { msgin.serial(levelName, timeBeforeTimeout); } //TODO remove size test when server version will be 6 if(CNetworkTask::getInstance().networkVersion>=6) { msgin.serial(oc); msgin.serial(trace); msgin.serial(mesh); } nlinfo("NET: cbLogin self=%d eid=%hu name='%s' totalScore='%d' color=(%d,%d,%d) texture='%s' spec=%d oc=%d trace='%s' mesh='%s'", self, (uint16)eid, name.c_str(), totalScore, color.R, color.G, color.B, texture.c_str(), spec, oc, trace.c_str(), mesh.c_str()); nldebug("player list.size = %d", CEntityManager::getInstance().size()); CEntityManager::getInstance().add(eid, name, totalScore, color, texture, spec, self, trace, mesh); if(oc) CEntityManager::getInstance()[eid].swapOpenClose(); if(self) { CMtpTarget::getInstance().displayTutorialInfo(totalScore<=CConfigFileTask::getInstance().configFile().getVar("MinTotalScoreToHideTutorial").asInt()); nlinfo("levelName='%s' timeBeforeTimeout=%f", levelName.c_str(), timeBeforeTimeout); if (!levelName.empty()) CMtpTarget::getInstance().startSession(0, timeBeforeTimeout/1000.0f, levelName, "", "", true); else CMtpTarget::getInstance().timeBeforeTimeout(timeBeforeTimeout/1000.0f); } CGuiCustom::getInstance().onLogin(name); }
static void cbTimeArrival(CNetMessage &msgin) { uint8 eid; float time; msgin.serial(eid, time); nlinfo("NET: cbTimeArrival eid=%hu, time=%f", (uint16)eid, time); // check if the player exists if(!CEntityManager::getInstance().exist(eid)) { nlwarning("The eid doesn't exist"); return; } if(CMtpTarget::getInstance().controler().getControledEntity()==eid) { // it's my arrival C3DTask::getInstance().EnableExternalCamera = true; } }
static void cbLogout(CNetMessage &msgin) { uint8 eid; msgin.serial(eid); nlinfo("NET: cbLogout eid=%hu", (uint16)eid); // check if the player exists if(!CEntityManager::getInstance().exist(eid)) { nlwarning("The eid doesn't exist"); return; } // if it's my eid, it means that i have to disconnect because i was kicked out from the server if(CMtpTarget::getInstance().controler().getControledEntity()==eid) { CMtpTarget::getInstance().error("You have been kicked"); //nlerror("You have been kicked"); } CGuiCustom::getInstance().onLogout(CEntityManager::getInstance()[eid].name()); CEntityManager::getInstance().remove(eid); }
static void cbError(CNetMessage &msgin) { string msg; msgin.serial(msg); nlinfo("NET: cbError msg='%s'", msg.c_str()); #ifdef NL_OS_WINDOWS if(msg.find("bad client version") != string::npos) { // the client is not up to date, ask the user to update if(MessageBox(NULL, "Your client is not up to date. You *must* download a new version from the mtp target web site.\r\nDo you want to automatically quit and go to the download page?", "Warning", MB_ICONWARNING|MB_YESNO) == IDYES) { openURL("http://mtptarget.free.fr/download.php"); exit(0); } } #endif CMtpTarget::getInstance().error(msg); }
static void cbCollideWhenFly(CNetMessage &msgin) { uint8 eid; CVector pos; msgin.serial(eid, pos); nlinfo("NET: cbCollideWhenFly eid=%hu, pos=(%f,%f,%f)", (uint16)eid, pos.x, pos.y, pos.z); // check if the player exists if(!CEntityManager::getInstance().exist(eid)) { nlwarning("The eid doesn't exist"); return; } if(CMtpTarget::getInstance().controler().getControledEntity()==eid) { // it's my collide when fly C3DTask::getInstance().EnableExternalCamera = true; } CEntityManager::getInstance()[eid].addCrashEventKey = CCrashEvent(true,pos); if(SessionFile) fprintf(SessionFile, "%hu CE %f %f %f\n", (uint16)eid,pos.x,pos.y,pos.z); }
static void cbUpdateElement(CNetMessage &msgin) { uint8 elementType, elementId, selectedBy; CVector pos, eulerRot; msgin.serial(elementType, elementId, selectedBy, pos, eulerRot); nlinfo("NET: cbUpdateElement"); switch(elementType) { case CEditableElementCommon::Module: if(CLevelManager::getInstance().levelPresent()) CLevelManager::getInstance().currentLevel().updateModule(elementId,pos,eulerRot,selectedBy); break; case CEditableElementCommon::StartPosition: if(CLevelManager::getInstance().levelPresent()) CLevelManager::getInstance().currentLevel().updateStartPoint(elementId,pos,eulerRot,selectedBy); break; default: nlwarning("Unknown elemen type %hu", (uint16)elementType); break; } }
CNetMessage* CNetMessageFactory::CreateMessage(const void* pData, size_t dataSize, ScriptInterface& scriptInterface) { CNetMessage* pNewMessage = NULL; CNetMessage header; // Figure out message type header.Deserialize((const u8*)pData, (const u8*)pData + dataSize); switch (header.GetType()) { case NMT_GAME_SETUP: pNewMessage = new CGameSetupMessage(scriptInterface); break; case NMT_PLAYER_ASSIGNMENT: pNewMessage = new CPlayerAssignmentMessage; break; case NMT_FILE_TRANSFER_REQUEST: pNewMessage = new CFileTransferRequestMessage; break; case NMT_FILE_TRANSFER_RESPONSE: pNewMessage = new CFileTransferResponseMessage; break; case NMT_FILE_TRANSFER_DATA: pNewMessage = new CFileTransferDataMessage; break; case NMT_FILE_TRANSFER_ACK: pNewMessage = new CFileTransferAckMessage; break; case NMT_JOIN_SYNC_START: pNewMessage = new CJoinSyncStartMessage; break; case NMT_REJOINED: pNewMessage = new CRejoinedMessage; break; case NMT_KICKED: pNewMessage = new CKickedMessage; break; case NMT_LOADED_GAME: pNewMessage = new CLoadedGameMessage; break; case NMT_SERVER_HANDSHAKE: pNewMessage = new CSrvHandshakeMessage; break; case NMT_SERVER_HANDSHAKE_RESPONSE: pNewMessage = new CSrvHandshakeResponseMessage; break; case NMT_CLIENT_HANDSHAKE: pNewMessage = new CCliHandshakeMessage; break; case NMT_AUTHENTICATE: pNewMessage = new CAuthenticateMessage; break; case NMT_AUTHENTICATE_RESULT: pNewMessage = new CAuthenticateResultMessage; break; case NMT_GAME_START: pNewMessage = new CGameStartMessage; break; case NMT_END_COMMAND_BATCH: pNewMessage = new CEndCommandBatchMessage; break; case NMT_SYNC_CHECK: pNewMessage = new CSyncCheckMessage; break; case NMT_SYNC_ERROR: pNewMessage = new CSyncErrorMessage; break; case NMT_CHAT: pNewMessage = new CChatMessage; break; case NMT_READY: pNewMessage = new CReadyMessage; break; case NMT_SIMULATION_COMMAND: pNewMessage = new CSimulationMessage(scriptInterface); break; default: LOGERROR("CNetMessageFactory::CreateMessage(): Unknown message type '%d' received", header.GetType()); break; } if (pNewMessage) pNewMessage->Deserialize((const u8*)pData, (const u8*)pData + dataSize); return pNewMessage; }