Example #1
0
  void MapEventManager::CallEvents (const Time& dt)
  {
    while (!leaveEvents_.IsEmpty ())
    {
      MapEventContext* event = leaveEvents_.Dequeue (event);
      CallEvent (MapEventActionType::Leave, dt, *event);

      if (abortEvents_)
        return;
    }

    while (!enterEvents_.IsEmpty ())
    {
      MapEventContext* event = enterEvents_.Dequeue (event);
      CallEvent (MapEventActionType::Enter, dt, *event);

      if (abortEvents_)
        return;
    }

    for (auto& objectEvents : events_)
    {
      for (auto& eventTriggering : objectEvents.second)
      {
        for (MapEventContext* event : eventTriggering.second)
        {
          CallEvent (MapEventActionType::In, dt, *event);

          if (abortEvents_)
            return;
        }
      }
    }
  }
Example #2
0
void ItemHandleEvents::onMcfProgress(MCFCore::Misc::ProgressInfo& info)
{
	if (info.percent != m_LastProg.percent || info.flag != m_LastProg.flag)
	{
		m_LastProg = info;
		CallEvent(m_EventHistory, m_vHelperList, &Helper::ItemHandleHelperI::onMcfProgress, info);
	}
	else
	{
		CallEvent(m_vHelperList, &Helper::ItemHandleHelperI::onMcfProgress, info);
	}
}
Example #3
0
void CPickup::Callback_OnLeave ( CColShape& Shape, CElement& Element )
{
    if ( IS_PLAYER ( &Element ) )
    {
        CPlayer& Player = static_cast < CPlayer& > ( Element );

        // Matching interior
        if ( GetInterior () == Element.GetInterior () )
        {
            // Matching dimension
            if ( GetDimension () == Element.GetDimension () )
            {
                // Is he alive?
                if ( !Player.IsDead () )
                {
                    // Call the onPickupHit event
                    CLuaArguments Arguments;
                    Arguments.PushElement ( &Player );
                    CallEvent ( "onPickupLeave", Arguments );

                    CLuaArguments Arguments2;
                    Arguments2.PushElement ( this );       // pickup
                    Element.CallEvent ( "onPlayerPickupLeave", Arguments2 );            
                }
            }
        }
    }
}
Example #4
0
void CClientWebBrowser::Events_OnNavigate ( const SString& strURL, bool bIsBlocked )
{
    CLuaArguments Arguments;
    Arguments.PushString ( strURL );
    Arguments.PushBoolean ( bIsBlocked );
    CallEvent ( "onClientBrowserNavigate", Arguments, false );
}
void CClientWebBrowser::Events_OnLoadingStart ( const SString& strURL, bool bMainFrame )
{
    CLuaArguments Arguments;
    Arguments.PushString ( strURL );
    Arguments.PushBoolean ( bMainFrame );
    CallEvent ( "onClientBrowserLoadingStart", Arguments, false );
}
void CClientWebBrowser::Events_OnPopup ( const SString& strTargetURL, const SString& strOpenerURL )
{
    CLuaArguments Arguments;
    Arguments.PushString ( strTargetURL );
    Arguments.PushString ( strOpenerURL );
    CallEvent ( "onClientBrowserPopup", Arguments, false );
}
Example #7
0
void CElement::SetCustomData ( const char* szName, const CLuaArgument& Variable, CLuaMain* pLuaMain, bool bSynchronized, CPlayer* pClient, bool bTriggerEvent )
{
    assert ( szName );
    if ( strlen ( szName ) > MAX_CUSTOMDATA_NAME_LENGTH )
    {
        // Don't allow it to be set if the name is too long
        CLogger::ErrorPrintf ( "Custom data name too long (%s)\n", *SStringX ( szName ).Left ( MAX_CUSTOMDATA_NAME_LENGTH + 1 ) );
        return;
    }

    // Grab the old variable
    CLuaArgument oldVariable;
    const SCustomData * pData = m_pCustomData->Get ( szName );
    if ( pData )
    {
        oldVariable = pData->Variable;
    }

    // Set the new data
    m_pCustomData->Set ( szName, Variable, pLuaMain, bSynchronized );

    if ( bTriggerEvent )
    {
        // Trigger the onElementDataChange event on us
        CLuaArguments Arguments;
        Arguments.PushString ( szName );
        Arguments.PushArgument ( oldVariable );
        Arguments.PushArgument ( Variable );
        CallEvent ( "onElementDataChange", Arguments, pClient );
    }
}
Example #8
0
void CBaseListBox::OnMouse(float wx, float wy, bool lBtn, bool rBtn)
{
	if (!lBtn || !InControl(wx, wy) || GetFont() == 0)return;
	size_t k = (size_t)((bounds.GetClientTop() - wy)/GetFont()->Height()) + itemScroll;
	itemIndex = (k >= items.size())?items.size() - 1:k;
	CallEvent(UI_CHANGE);
}
Example #9
0
bool TestCase::OnBtnReturnClicked(IMsgBase* pMsg)
{
	// return to menu
	MsgCommon msgCommon(MI_USER_RETURN);
	CallEvent(msgCommon);
	return true;
}
Example #10
0
void kGUIMenuObj::OpenMenu(kGUIMenuColObj *menu,int x,int y)
{
	kGUIMenuEntryObj *me;

	/* this callback can be trapped to enable / disable menu entries */
	if(!m_depth)
		CallEvent(EVENT_ENTER);

	m_activeentry.SetEntry(m_depth++,menu);
	menu->SetEventHandler(0,0);
	menu->SetEventHandler(this,CALLBACKNAME(MenuEvent));
	menu->SetDrawPopRow(false);
	menu->Activate(x,y);
	Dirty();

	/* check to see if the first entry has a sub-menu */
	me=menu->GetCurrentEntry();
	if(me->GetSubMenu())
	{
		kGUICorners c;

		/* activate sub-menu */
		me->GetCorners(&c);
		OpenMenu(me->GetSubMenu(),c.rx-6,c.ty+2);
	}
}
Example #11
0
void CBoxHttpHost::OnStart(void)
{
	if(m_bIsStart)return;
	m_bIsStart = TRUE;

	CSingleLock l(&m_cs, TRUE);

	CallEvent(L"OnApplicationStart", NULL, NULL, TRUE);

	CBoxObject<CBoxHttpHost> pHost;
	CComVariant var;
	int i;
	int count = m_pContents->get_Count();

	for(i = 0; i < count; i ++)
	{
		var.Clear();
		m_pContents->GetValue(i, var);
		if(var.vt == VT_DISPATCH)
		{
			pHost = var.pdispVal;
			if(pHost != NULL)
				pHost->OnStart();
		}
	}
}
Example #12
0
//yea this is a mess with commented out commands
void ComPort1_Handler(){ //This is the interrupt handler
	unsigned char which,tmp;
	unsigned char tmpy[2];
	which=inportb(COM1_INTERRUPT_ID);
	tmp=which&0x06; //isolate the type of interrupt
	switch(tmp){
		case ID_MODEM_STATUS: //ignored
		//do nothing
		break;
		case  ID_TRANSMITTER_EMPTY: //ready to write
			ReadyWrite=TRUE; //sets the status to ready for writing
		break;
		case ID_RECEIVED_DATA: //theirs data in the port so go get it!
		Com1Count++;
		if(Com1Count==32){Com1Count=0;}
		ReadyRead=TRUE;
		Com1Buffer[Com1Count]=inportb(COM1_TRANSMIT_RECEIVE_BUFFER);
          CallEvent(COM1_READ,0,0);
		if(IgnoreReads || Com1ReadLength<=Com1ReadCounter){
			//ReadyRead=TRUE;
		}
		break;
		case ID_RECEIVER_STATUS: //ERROR!!
		which=inportb(COM1_LINE_STATUS);
		which=which&30; //isolates bits 1-4
		switch(which){
			case OVERRUN_ERROR: //buffer overrun
			Com1IgnoredReadBuffer=inportb(COM1_TRANSMIT_RECEIVE_BUFFER);
			Com1Error=Com1Error|OVERRUN_ERROR;
			break;
			case PARITY_ERROR:
			Com1Error=Com1Error|PARITY_ERROR;
			//do nothing I guess
			break;
			case FRAMING_ERROR:
			Com1Error=Com1Error|FRAMING_ERROR;
			//do nothing again
			break;
			case CONNECT_LOST:
			Com1Error=Com1Error|CONNECT_LOST;
			break;
		}
		CallEvent(COM1_ERROR,Com1Error,0);
		break;
	}

}
Example #13
0
void CClientWebBrowser::Events_OnLoadingFailed ( const SString& strURL, int errorCode, const SString& errorDescription )
{
    CLuaArguments Arguments;
    Arguments.PushString ( strURL );
    Arguments.PushNumber ( errorCode );
    Arguments.PushString ( errorDescription );
    CallEvent ( "onClientBrowserLoadingFailed", Arguments, false );
}
Example #14
0
void CClientWebBrowser::Events_OnResourceBlocked ( const SString& strURL, const SString& strDomain, unsigned char reason )
{
    CLuaArguments Arguments;
    Arguments.PushString ( strURL );
    Arguments.PushString ( strDomain );
    Arguments.PushNumber ( reason );
    CallEvent ( "onClientBrowserResourceBlocked", Arguments, false );
}
Example #15
0
void CClientMarker::Callback_OnLeave ( CClientColShape& Shape, CClientEntity& Entity )
{
    if ( IS_PLAYER ( &Entity ) )
    {
        // Call the marker hit event
        CLuaArguments Arguments;
        Arguments.PushElement ( &Entity );            // player that hit it
        Arguments.PushBoolean ( ( Shape.GetDimension () == Entity.GetDimension () ) ); // matching dimension?
        CallEvent ( "onClientMarkerLeave", Arguments, true );
    }
}
Example #16
0
////////////////////////////////////////////////////////////
//
// CClientSound::DistanceStreamOut
//
// Sound is now far enough away to not be heard, so can be deactivated
//
////////////////////////////////////////////////////////////
void CClientSound::DistanceStreamOut ( void )
{
    if ( m_pAudio )
    {
        m_pSoundManager->OnDistanceStreamOut ( this );
        Destroy ();

        // Call Stream Out event
        CLuaArguments Arguments;
        CallEvent ( "onClientElementStreamOut", Arguments, true );
    }
}
Example #17
0
////////////////////////////////////////////////////////////
//
// CClientSound::DistanceStreamIn
//
// Sound is now close enough to be heard, so must be activated
//
////////////////////////////////////////////////////////////
void CClientSound::DistanceStreamIn ( void )
{
    if ( !m_pAudio )
    {
        Create ();
        m_pSoundManager->OnDistanceStreamIn ( this );

        // Call Stream In event
        CLuaArguments Arguments;
        CallEvent ( "onClientElementStreamIn", Arguments, true );
    }
}
Example #18
0
void CBoxHttpHost::ResetSession(CBoxHttpSession* pItem)
{
	BYTE randstr[41];
	CString strSessionCookie;

	if(t_pHttpAccept != NULL)
	{
		CSingleLock l(&m_cs, TRUE);

		m_cacheSessions.Remove(pItem);

		CallEvent(L"OnSessionEnd", NULL, pItem);
		pItem->m_pContents->RemoveAll();

		RAND_seed(&pItem, sizeof(pItem));
		RAND_seed(randstr, sizeof(randstr));

		do
		{
			RAND_bytes(randstr, 40);
			for(int i = 0; i < 40; i ++)
				randstr[i] = (randstr[i] % 26) + 'A';
			randstr[40] = 0;

			strSessionCookie = randstr;
		}while(m_cacheSessions.Exists(strSessionCookie));

		pItem->m_strKey = strSessionCookie;
		m_cacheSessions.Add(strSessionCookie, pItem);

		l.Unlock();

		CallEvent(L"OnSessionStart", NULL, pItem);

		if(m_strSessionDomain.IsEmpty())
			t_pHttpAccept->AddHeader(_T("Set-Cookie"), m_strHostKey + _T('=') + strSessionCookie + _T("; path=/"));
		else t_pHttpAccept->AddHeader(_T("Set-Cookie"), m_strHostKey + _T('=') + strSessionCookie + _T("; domain=") + m_strSessionDomain + _T("; path=/"));
	}
}
Example #19
0
void Entity::addComponent(ComponentPtr component)
{
    std::type_index id = std::type_index(typeid(*component.get()));
    assert(componentMap.count(id) == 0);

    EntityPtr ptr = this->shared_from_this();

    component->attachEntity = ptr;
    this->components.push_back(component);

    this->componentMap[id] = component;

    CacheComponents();

    component->registerEvents();

    if (this->state >= EntityState::Awaked) {
        CallEvent(component.get(), Events::Awake);
        if (this->node.get() != nullptr && this->node->hasParent()) {
            CallEvent(component.get(), Events::Enter);
        }
    }
}
Example #20
0
void CBoxHttpHost::OnEnd(void)
{
	if(!m_bIsStart)return;
	m_bIsStart = FALSE;

	CSingleLock l(&m_cs, TRUE);

	CBoxObject<CBoxHttpHost> pHost;
	CComVariant var;
	int i;
	int count = m_pContents->get_Count();

	for(i = 0; i < count; i ++)
	{
		var.Clear();
		m_pContents->GetValue(i, var);
		if(var.vt == VT_DISPATCH)
		{
			pHost = var.pdispVal;
			if(pHost != NULL)
				pHost->OnEnd();
		}
	}

	CBoxHttpSession* pSession;

	while((pSession = m_cacheSessions.GetLast()) != NULL)
	{
		pSession->ExternalAddRef();
		m_cacheSessions.Remove(pSession);
		CallEvent(L"OnSessionEnd", NULL, pSession);
		pSession->m_pContents->RemoveAll();
		pSession->ExternalRelease();
	}

	CallEvent(L"OnApplicationEnd", NULL, NULL);
}
Example #21
0
void CBaseListBox::OnKeyDown(char key)
{
	switch(key)
	{
	case VK_UP:
		Prev();
		break;
	case VK_DOWN:
		Next();
		break;
	default:
		return;
	}
	CallEvent(UI_CHANGE);
}
Example #22
0
BOOL CBoxHttpHost::GetSession(CString& strSessionCookie, CBoxObject<CBoxHttpSession>& pSession, CBoxHttpContext *pContext)
{
	BYTE randstr[41];
	BOOL bSessionKey = FALSE;

	CSingleLock l(&m_cs, TRUE);

	if(!strSessionCookie.IsEmpty())
	{
		if(m_cacheSessions.Lookup(strSessionCookie, pSession))
		{
			pSession->m_tmLastTime = m_nNowTime;

			return TRUE;
		}
	}else
	{
		RAND_seed(&pSession, sizeof(pSession));
		RAND_seed(randstr, sizeof(randstr));

		do
		{
			RAND_bytes(randstr, 40);
			for(int i = 0; i < 40; i ++)
				randstr[i] = (randstr[i] % 26) + 'A';
			randstr[40] = 0;

			strSessionCookie = randstr;
		}while(m_cacheSessions.Exists(strSessionCookie));

		bSessionKey = TRUE;
	}

	pSession.CreateInstance();
	pSession->m_nTimeout = m_nSessionTimeout;
	pSession->m_pHost = this;
	pSession->m_tmLastTime = m_nNowTime;

	pSession->m_strKey = strSessionCookie;
	m_cacheSessions.Add(strSessionCookie, pSession);

	l.Unlock();

	CallEvent(L"OnSessionStart", pContext, pSession);

	return !bSessionKey;
}
Example #23
0
void CClientPickup::Callback_OnLeave ( CClientColShape& Shape, CClientEntity& Entity )
{
    if ( IS_PLAYER ( &Entity ) )
    {
        bool bMatchingDimensions = (GetDimension () == Entity.GetDimension ()); // Matching dimensions?

        // Call the pickup leave event (source = the pickup that was left)
        CLuaArguments Arguments;
        Arguments.PushElement ( &Entity ); // The element that left the pickup
        Arguments.PushBoolean ( bMatchingDimensions );
        CallEvent ( "onClientPickupLeave", Arguments, true);

        // Call the player pickup leave event (source = the player that left the pickup)
        CLuaArguments Arguments2;
        Arguments2.PushElement ( this ); // The pickup that was left (this)
        Arguments2.PushBoolean ( bMatchingDimensions );
        Entity.CallEvent ( "onClientPlayerPickupLeave", Arguments2, true );
    }
}
Example #24
0
void CPickup::Callback_OnCollision ( CColShape& Shape, CElement& Element )
{
    if ( IS_PLAYER ( &Element ) )
    {
        CPlayer& Player = static_cast < CPlayer& > ( Element );

        // Is he alive?
        if ( !Player.IsDead () )
        {
            // Matching interior
            if ( GetInterior () == Element.GetInterior () )
            {
                // Matching dimension
                if ( GetDimension () == Element.GetDimension () )
                {
                    // Call the onPickupHit event
                    CLuaArguments Arguments;
                    Arguments.PushElement ( &Player );
                    bool bContinue1 = CallEvent ( "onPickupHit", Arguments );

                    CLuaArguments Arguments2;
                    Arguments2.PushElement ( this );       // pickup
                    bool bContinue2 = Element.CallEvent ( "onPlayerPickupHit", Arguments2 );

                    if ( bContinue1 && bContinue2 )
                    {
                        // Does it still exist?
                        if ( !IsBeingDeleted () )
                        {
                            // Can we USE the pickup?
                            if ( CanUse ( Player ) )
                            {
                                // USE the pickup
                                Use ( Player );
                            }
                        }
                    }
                }
            }
        }
    }
}
Example #25
0
void CMarker::Callback_OnLeave ( CColShape& Shape, CElement& Element )
{
    // Matching interior?
    if ( GetInterior () == Element.GetInterior () )
    {
        // Call the marker hit event
        CLuaArguments Arguments;
        Arguments.PushElement ( &Element ); // Hit element
        Arguments.PushBoolean ( GetDimension () == Element.GetDimension () );        // Matching dimension?
        CallEvent ( "onMarkerLeave", Arguments );

        if ( IS_PLAYER ( &Element ) )
        {
            CLuaArguments Arguments2;
            Arguments2.PushElement ( this );        // marker
            Arguments2.PushBoolean ( GetDimension () == Element.GetDimension () );        // Matching dimension?
            Element.CallEvent ( "onPlayerMarkerLeave", Arguments2 );
        }
    }
}
Example #26
0
void CElement::DeleteCustomData ( const char* szName )
{
    // Grab the old variable
    SCustomData * pData = m_pCustomData->Get ( szName );
    if ( pData )
    {
        CLuaArgument oldVariable;
        oldVariable = pData->Variable;

        // Delete the custom data
        m_pCustomData->Delete ( szName );

        // Trigger the onElementDataChange event on us
        CLuaArguments Arguments;
        Arguments.PushString ( szName );
        Arguments.PushArgument ( oldVariable );
        Arguments.PushArgument ( CLuaArgument() );  // Use nil as the new value to indicate the data has been removed
        CallEvent ( "onElementDataChange", Arguments );
    }
}
Example #27
0
void CElement::SetCustomData ( const char* szName, const CLuaArgument& Variable, CLuaMain* pLuaMain, bool bSynchronized )
{
    assert ( szName );

    // Grab the old variable
    CLuaArgument oldVariable;
    const SCustomData * pData = m_pCustomData->Get ( szName );
    if ( pData )
    {
        oldVariable = pData->Variable;
    }

    // Set the new data
    m_pCustomData->Set ( szName, Variable, pLuaMain, bSynchronized );

    // Trigger the onElementDataChange event on us
    CLuaArguments Arguments;
    Arguments.PushString ( szName );
    Arguments.PushArgument ( oldVariable  );
    CallEvent ( "onElementDataChange", Arguments );
}
Example #28
0
bool kGUIDividerObj::UpdateInput(void)
{
	kGUICorners c;

	GetCorners(&c);
	if(kGUI::MouseOver(&c)==true)
	{
		kGUI::SetTempMouseCursor(MOUSECURSOR_ADJUSTVERT);

		if(this!=kGUI::GetActiveObj() && kGUI::GetMouseClickLeft()==true)
		{
			kGUI::PushActiveObj(this);
			SetCurrent();
		}
	}

	if(this==kGUI::GetActiveObj())
	{
		int dy;
		if(kGUI::GetMouseReleaseLeft()==true)
		{
			kGUI::PopActiveObj();
			return(true);
		}

		dy=kGUI::GetMouseDY();
		if(dy)
		{
			kGUIEvent e;

			e.m_value[0].i=dy;
			CallEvent(EVENT_AFTERUPDATE,&e);
		}
		kGUI::SetTempMouseCursor(MOUSECURSOR_ADJUSTVERT);

		return(true);
	}
	else
		return(false);
}
Example #29
0
void CBoxHttpHost::ClearSession(__int64 nNowTime)
{
	CSingleLock l(&m_cs, TRUE);
	CBoxHttpSession* pSession;

	m_nNowTime = nNowTime;
	nNowTime -= (__int64)600000000 * m_nSessionTimeout;

	while((pSession = m_cacheSessions.GetLast()) != NULL && pSession->m_tmLastTime < nNowTime)
		if(pSession->m_dwRef > 1)
		{
			pSession->m_tmLastTime = m_nNowTime;
			m_cacheSessions.Update(pSession);
		}else
		{
			pSession->ExternalAddRef();
			m_cacheSessions.Remove(pSession);
			CallEvent(L"OnSessionEnd", NULL, pSession);
			pSession->m_pContents->RemoveAll();
			pSession->ExternalRelease();
		}

	CBoxObject<CBoxHttpHost> pHost;
	CComVariant var;
	int i;
	int count = m_pContents->get_Count();

	for(i = 0; i < count; i ++)
	{
		var.Clear();
		m_pContents->GetValue(i, var);
		if(var.vt == VT_DISPATCH)
		{
			pHost = var.pdispVal;
			if(pHost != NULL)
				pHost->ClearSession(m_nNowTime);
		}
	}
}
Example #30
0
////////////////////////////////////////////////////////////
//
// CClientSound::Process3D
//
// Update position and velocity and pass on the BASS for processing.
// m_pAudio->DoPulse needs to be called for non-3D sounds also.
//
////////////////////////////////////////////////////////////
void CClientSound::Process3D ( const CVector& vecPlayerPosition, const CVector& vecCameraPosition, const CVector& vecLookAt )
{
    // Update 3D things if required
    if ( m_b3D )
    {
        // Update our position and velocity if we're attached
        CClientEntity* pAttachedToEntity = GetAttachedTo ();
        if ( pAttachedToEntity )
        {
            GetPosition( m_vecPosition );
            DoAttaching ();
            CVector vecVelocity;
            if ( CStaticFunctionDefinitions::GetElementVelocity ( *pAttachedToEntity, vecVelocity ) )
                SetVelocity ( vecVelocity );
            // Update our spatial data position
            UpdateSpatialData ();
        }
    }
    // If the sound isn't active, we don't need to process it
    // Moved after 3D updating as the streamer didn't know the position changed if a sound isn't streamed in when attached.
    if ( !m_pAudio )
        return;

    m_pAudio->DoPulse ( vecPlayerPosition, vecCameraPosition, vecLookAt );


    // Trigger script events for things
    SSoundEventInfo eventInfo;
    while ( m_pAudio->GetQueuedEvent ( eventInfo ) )
    {
        if ( eventInfo.type == SOUND_EVENT_FINISHED_DOWNLOAD )
        {
            CLuaArguments Arguments;
            Arguments.PushNumber ( eventInfo.dNumber );
            CallEvent ( "onClientSoundFinishedDownload", Arguments, true );
            OutputDebugLine ( SString ( "[ClientSound] onClientSoundFinishedDownload %f", eventInfo.dNumber ) );
        }
        else
        if ( eventInfo.type == SOUND_EVENT_CHANGED_META )
        {
            CLuaArguments Arguments;
            Arguments.PushString ( eventInfo.strString );
            CallEvent ( "onClientSoundChangedMeta", Arguments, true );
            OutputDebugLine ( SString ( "[ClientSound] onClientSoundChangedMeta %s", *eventInfo.strString ) );
        }
        else
        if ( eventInfo.type == SOUND_EVENT_STREAM_RESULT )
        {
            // Call onClientSoundStream LUA event
            CLuaArguments Arguments;
            Arguments.PushBoolean ( eventInfo.bBool );
            Arguments.PushNumber ( eventInfo.dNumber );
            if ( !eventInfo.strString.empty () )
                Arguments.PushString ( eventInfo.strString );
            CallEvent ( "onClientSoundStream", Arguments, true );
            OutputDebugLine ( SString ( "[ClientSound] onClientSoundStream %d %f %s", eventInfo.bBool, eventInfo.dNumber, *eventInfo.strString ) );
        }
        else
        if ( eventInfo.type == SOUND_EVENT_BEAT )
        {
            CLuaArguments Arguments;
            Arguments.PushNumber ( eventInfo.dNumber );
            CallEvent ( "onClientSoundBeat", Arguments, true );
        }
    }
}