// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" #include "game_share/action_factory.h" #include "game_share/action_position.h" #include "game_share/action_sint64.h" #include "game_share/mode_and_behaviour.h" #include "game_share/entity_types.h" #include "distance_prioritizer.h" #include "client_host.h" #include "vision_provider.h" #include #include "frontend_service.h" #ifdef TEST_LOST_PACKET #include #endif using namespace std; using namespace CLFECOMMON; using namespace NLMISC; using namespace NLNET; // Test feature to simulate a lost packet when sending a new sheet. Works with 1 client only. #ifdef TEST_LOST_PACKET CVariable TestPacketLost( "test", "TestPacketLost", "", true, 0, 1 ); TGameCycle TestPacketLostTimer = 0; TCLEntityId TestPacketLostSlot = 0; #endif TClientId verbosePropertiesSent = INVALID_CLIENT; // TODO-Minimal visual bandwith /*sint32 NbMinimalVisualBits; void cbNbMinimalVisualBytesChanged( IVariable& var ); CVariable NbMinimalVisualBytes( "fe", "NbMinimalVisualBytes", "Min number of bytes for vision per msg", 50, 0, true, cbNbMinimalVisualBytesChanged, true ); void cbNbMinimalVisualBytesChanged( IVariable& var ) { NbMinimalVisualBits = NbMinimalVisualBytes.get() * 8; }*/ /// Init void CDistancePrioritizer::init( CVisionArray *va, CVisionProvider *vp, CHistory *hy ) { _VisionArray = va; _VisionProvider = vp; _History = hy; SortSpreader.init(); for ( sint i=0; i!=NB_VISUAL_PROPERTIES; ++i ) _DistThresholdTable[i] = -1; _VisualPropertyTreeRoot = (TVPNodeServer*)NewNode(); sint nbProp = _VisualPropertyTreeRoot->buildTree(); // Check that the last property matches the number of properties-1 if ( nbProp != NB_VISUAL_PROPERTIES ) { nlwarning( "Found %hu properties in the sheets, the prioritizer knows %hu properties", NB_VISUAL_PROPERTIES, nbProp ); } // Init distance thresholds CLFECOMMON::initThresholdTable( _DistThresholdTable ); } /* * Calculate the priorities */ void CDistancePrioritizer::calculatePriorities() { // Sort if ( SortSpreader.mustProcessNow() ) { THostMap::iterator icm; sint clientmapindex, outerBoundIndex; SortSpreader.getProcessingBounds( icm, clientmapindex, outerBoundIndex ); while ( clientmapindex < outerBoundIndex ) { CClientHost *clienthost = GETCLIENTA(icm); // Prioritize only at the opposite time of sending for a particular client if ( ! clienthost->whenToSend() ) { // Update priorities updatePriorityOfEntitiesSeenByClient( clienthost->clientId() ); // Sort entities by decreasing priority sortEntitiesOfClient( clienthost->clientId() ); } ++clientmapindex; ++icm; } SortSpreader.endProcessing( icm ); } SortSpreader.incCycle(); } /* #define arbitrateDiscreetProperty( name ) \ get##name##node()->BranchHasPayload = entityIsWithinDistanceThreshold( PROPERTY_##name ) && discreetPropertyHasChanged( PROPERTY_##name, (TYPE_##name*)NULL ) #define arbitrateDiscreetPropertyWithoutThreshold( name ) \ get##name##node()->BranchHasPayload = discreetPropertyHasChanged( PROPERTY_##name, (TYPE_##name*)NULL ) #define arbitrateTargetList( name ) \ get##name##node()->BranchHasPayload = targetListHasChanged( PROPERTY_##name, (TYPE_##name*)NULL ) */ #ifdef STORE_MIRROR_VP_IN_CLASS #define arbitrateDiscreetProperty( entry, name ) \ GET_VP_NODE(name)->BranchHasPayload = \ entityIsWithinDistanceThreshold( PROPERTY_##name ) \ && discreetPropertyHasChanged( entry.Properties[PROPERTY_##name], sentity->VP_##name, PROPERTY_##name, (TYPE_##name*)NULL ) #define arbitrateDiscreetPropertyWithoutThreshold( entry, name ) \ GET_VP_NODE(name)->BranchHasPayload = \ discreetPropertyHasChanged( entry.Properties[PROPERTY_##name], sentity->VP_##name, PROPERTY_##name, (TYPE_##name*)NULL ) #else // STORE_MIRROR_VP_IN_CLASS #define arbitrateDiscreetProperty( entry, name ) \ GET_VP_NODE(name)->BranchHasPayload = \ entityIsWithinDistanceThreshold( PROPERTY_##name ) \ && discreetPropertyHasChanged( entry.Properties[PROPERTY_##name], PROPERTY_##name, (TYPE_##name*)NULL ) #define arbitrateDiscreetPropertyWithoutThreshold( entry, name ) \ GET_VP_NODE(name)->BranchHasPayload = \ discreetPropertyHasChanged( entry.Properties[PROPERTY_##name], PROPERTY_##name, (TYPE_##name*)NULL ) #define arbitrateTargetList( entry, name ) \ #endif // STORE_MIRROR_VP_IN_CLASS #define arbitrateNeverSendProperty( name ) \ GET_VP_NODE(name)->BranchHasPayload = false #define arbitrateTargetList( entry, name ) \ GET_VP_NODE(name)->BranchHasPayload = \ targetListHasChanged( entry.Properties[PROPERTY_##name], PROPERTY_##name, (TYPE_##name*)NULL ) #define DECLARE_AP(name) CActionSint64 *ap = CActionFactory::getInstance()->getVolatilePropAction( TVPNodeServer::PrioContext.Slot, PROPERTY_##name ) #define DECLARE_AP_INDEX(index) CActionSint64 *ap = CActionFactory::getInstance()->getVolatilePropAction( TVPNodeServer::PrioContext.Slot, index ) #define REMOVE_AP() /* #define DECLARE_AP(name) CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_##name ) #define DECLARE_AP_INDEX(index) CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, index ) #define REMOVE_AP() CActionFactory::getInstance()->remove( (CAction*&)ap ) */ /* * */ void CDistancePrioritizer::fillOutBox( CClientHost& client, TOutBox& outbox ) { H_AUTO(FillOutBox) TClientId clientId = client.clientId(); initDispatchingCycle( clientId ); #ifdef NL_DEBUG client.MoveNumber = 0; #endif if (!client.entityIndex().isValid()) return; // initialize common context part TVPNodeServer::PrioContext.Prioritizer = this; TVPNodeServer::PrioContext.ClientHost = &client; TVPNodeServer::PrioContext.ClientId = clientId; CMirrorPropValueRO targetIndexOfClient( TheDataset, client.entityIndex(), DSPropertyTARGET_ID ); // TODO-Minimal visual bandwith /*sint32 initialPosInBit = outbox.getPosInBit();*/ sint32 currentPosInBit; while ( true ) { currentPosInBit = outbox.getPosInBit(); // Browse the seen entities, sorted by distance TCLEntityId slot = getNextEntityToStudy( clientId ); if ( slot == INVALID_SLOT ) { // Exit when all the pairs have been successfully filled #ifdef TEST_LOST_PACKET if ( (TestPacketLostTimer!=0) && (CTickEventHandler::getGameCycle() >= TestPacketLostTimer) ) { nldebug( "Negative ack for %hu %hu", client.clientId(), TestPacketLostSlot ); _History->_PacketHistory.negativeAck( client.clientId(), TestPacketLostSlot, PROPERTY_SHEET, 0 ); TestPacketLostTimer = 0; } #endif return; } // TODO-Minimal visual bandwith // Always allow to write a minimal amount of visual properties /*if ( currentPosInBit - initialPosInBit >= NbMinimalVisualBits )*/ { // Don't fill if the free space is lower than 32 bits (checked once per pair, not once per property) if ( currentPosInBit + 32 > client.getCurrentThrottle() ) { // Exit when the size limit has been reached before all the pairs have been filled #ifdef NL_DEBUG uint nbRemainingPairs = (uint)_PrioritizedEntitiesByClient[clientId].size() - _CurrentEntitiesToStudy[clientId]; if ( nbRemainingPairs > 0 ) LOG_WHAT_IS_SENT( "%u: C%hu S%hu: %u pairs remaining", CTickEventHandler::getGameCycle(), clientId, (uint16)slot, nbRemainingPairs ); LOG_WHAT_IS_SENT( "C%hu: outbox full (%d bits)", clientId, currentPosInBit ); #endif #ifdef TEST_LOST_PACKET if ( (TestPacketLostTimer!=0) && (CTickEventHandler::getGameCycle() >= TestPacketLostTimer) ) { nldebug( "Negative ack for %hu %hu", client.clientId(), TestPacketLostSlot ); _History->_PacketHistory.negativeAck( client.clientId(), TestPacketLostSlot, PROPERTY_SHEET, 0 ); TestPacketLostTimer = 0; } #endif return; } } // Get the entity corresponding to the client/slot pair TPairState& pairState = _VisionArray->getPairState( clientId, slot ); CEntity* sentity = NULL; TEntityIndex entityIndex = pairState.EntityIndex; TVPNodeServer::PrioContext.EntityIndex = entityIndex; if ( entityIndex.isValid() ) sentity = TheEntityContainer->getEntity( entityIndex ); if ( pairState.associationSuppressed() ) { // Pure unassociation case: we must send an empty block serialSlotHeader( client, NULL, pairState, slot, outbox ); uint32 bits = 0; outbox.serialAndLog2( bits, 2 ); // 2 bits for pos & other properties //nldebug( "Pure unassociation of C%hu S%hu", clientId, (uint16)slot ); // The first time, sentity is non-null. If this is a resending (after a neg-ack), it's null. if ( sentity ) _VisionProvider->postRemovePair( clientId, slot ); } else { if ( sentity == NULL ) continue; H_BEFORE(OneSlotArbitrate) H_BEFORE(OneSlotArbitrateInit) // Initialize the context TVPNodeServer::PrioContext.Slot = slot; TVPNodeServer::PrioContext.EntityIndex = entityIndex; TVPNodeServer::PrioContext.Sentity = sentity; //TVPNodeServer::PrioContext.PairState = &pairState; TVPNodeServer::PrioContext.DistanceCE = pairState.DistanceCE; TVPNodeServer::PrioContext.Timestamp = 0; TVPNodeServer::PrioContext.IsTarget = (targetIndexOfClient() == entityIndex); TVPNodeServer::PrioContext.PositionAlreadySent = (_History->getMileage( clientId, slot ) != 0); TVPNodeServer::PrioContext.ZCache = sentity->z(entityIndex); // setup Z cache for all later mirror access to entity->z() const CPropertyHistory::CEntityEntry& entry = _History->getEntityEntry(TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.Slot); // Debug display //nlinfo( "Preparing a block to client %hu (%s) for slot %hu", clientId, client.eId().toString().c_str(), (uint16)slot ); // Continuous properties uint8 seenEntityType = TheDataset.getEntityId( entityIndex ).getType(); H_AFTER(OneSlotArbitrateInit) bool thetaIntMode = false; // Arbitrate all discreet properties switch (seenEntityType) { default: arbitrateAllDiscreetProperties(entry); break; case RYZOMID::npc: arbitrateNPCDiscreetProperties(entry); break; case RYZOMID::creature: arbitrateCreatureDiscreetProperties(entry); break; case RYZOMID::forageSource: arbitrateForageSourceDiscreetProperties(entry); thetaIntMode = true; break; } H_BEFORE(OneSlotArbitratePropagate) // Propagate back BranchHasPayload flags //_VisualPropertyTreeRoot->propagateBackBranchHasPayload(); TVPNodeServer::fastPropagateBackBranchHasPayload(); H_AFTER(OneSlotArbitratePropagate) H_AFTER(OneSlotArbitrate) // ******** Fill the buffer with the properties ******** // Fill the header if ( _VisualPropertyTreeRoot->BranchHasPayload ) { H_AUTO(OneSlotFill) //nldebug( "Filling for C%hu, pass %u, BEFORE HEADER: bitpos: %d", clientId, ++i, outbox.getPosInBit() ); //nlinfo( "C%hu S%hu AB%hu", clientId, (uint16)slot, (uint32)pairState.AssociationChangeBits ); serialSlotHeader( client, sentity, pairState, slot, outbox ); //nldebug( "AFTER HEADER: pos: %d", outbox.getPosInBit() ); // Fill the position if required //TVPNodeServer *currentNode = _VisualPropertyTreeRoot; outbox.serialBitAndLog( GET_VP_NODE(POSITION)->BranchHasPayload ); if ( GET_VP_NODE(POSITION)->BranchHasPayload ) { //CActionPosition *ap = (CActionPosition*)(CActionFactory::getInstance()->createByPropIndex( slot, PROPERTY_POSITION )); //ap->PropertyCode = PROPERTY_POSITION; CActionPosition* ap = CActionFactory::getInstance()->getVolatilePositionAction(slot); // When the mode is transmitted or the entity is not in local mode (first bit of Z), transmit the *absolute* position if ( (! (TVPNodeServer::PrioContext.ZCache & 0x1)) || (GET_VP_NODE(POSITION)->BranchHasPayload && GET_VP_NODE(MODE)->BranchHasPayload) ) { ap->Position[0] = sentity->X(); ap->Position[1] = sentity->Y(); //ap->Position[2] = sentity->z( entityIndex ); ap->Position[2] = TVPNodeServer::PrioContext.ZCache; ap->IsRelative = false; LOG_WHAT_IS_SENT( "%u: C%hu S%hu: Filling ABSPOS: %d %d %d m", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, sentity->posXm( entityIndex ), sentity->posYm( entityIndex ), sentity->posZm( entityIndex ) ); } else { CMirrorPropValueRO propLX( TheDataset, entityIndex, DSPropertyLocalX ); CMirrorPropValueRO propLY( TheDataset, entityIndex, DSPropertyLocalY ); CMirrorPropValueRO propLZ( TheDataset, entityIndex, DSPropertyLocalZ ); ap->Position[0] = propLX(); ap->Position[1] = propLY(); ap->Position[2] = propLZ(); ap->IsRelative = true; LOG_WHAT_IS_SENT( "%u: C%hu S%hu: Filling RELPOS: %d %d %d mm", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, ap->Position[0], ap->Position[1], ap->Position[2] ); } CActionFactory::getInstance()->packFast( ap, outbox ); _History->storePosition( clientId, client.sendNumber(), ap, sentity->Mileage, TVPNodeServer::PrioContext.IsTarget, TVPNodeServer::PrioContext.Timestamp ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); ++client.NbActionsSentAtCycle; } // Fill the orientation if required //currentNode = currentNode->B; TVPNodeServer *currentNode = _VisualPropertyTreeRoot->B; outbox.serialBitAndLog( currentNode->BranchHasPayload ); if ( currentNode->BranchHasPayload ) { outbox.serialBitAndLog( currentNode->A->BranchHasPayload ); if ( currentNode->A->BranchHasPayload ) { //CActionSint64 *ap = (CActionSint64*)(CActionFactory::getInstance()->createByPropIndex( slot, PROPERTY_ORIENTATION )); //ap->PropertyCode = PROPERTY_ORIENTATION; // useless: already set by createByPropIndex DECLARE_AP(ORIENTATION); CMirrorPropValueRO prop( TheDataset, entityIndex, DSPropertyORIENTATION ); LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu ORIENT (P%hu) : %.1f", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, prop() ); uint32 value = ( thetaIntMode ? (uint32)(prop()) : *((uint32*)&(prop())) ); ap->setAndPackValue( value, outbox ); _History->store( clientId, client.sendNumber(), ap ); // TODO: send less bits for forage source entity //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++client.NbActionsSentAtCycle; } // Fill the required discreet properties if required currentNode->B->fillDiscreetProperties( outbox ); //nlinfo("end fill"); //TVPNodeServer::fastFillDiscreetProperties( outbox ); //nlinfo("end fastfill"); } } #ifdef NL_DEBUG if ( _VisualPropertyTreeRoot->A->BranchHasPayload ) { //CClientEntityIdTranslator::CEntityInfo& info = client.IdTranslator.getInfo( slot ); // CHANGED BEN //if ( info.AssociationState == CClientEntityIdTranslator::CEntityInfo::AwaitingAssAck ) // CHANGED BEN if ( _VisionArray->getAssociationState(clientId, slot) == TPairState::AwaitingAssAck ) { nlwarning( "C%hu S%hu: Sending position but sheet id not sent", clientId, (uint16)slot ); client.displaySlotProperties( slot ); } } //if ( (verbosePropertiesSent==9999) || (verbosePropertiesSent==TVPNodeServer::PrioContext.ClientId) ) //{ // nldebug( "To C%hu", clientId ); // outbox.displayStream(); //} #endif // ******** End of iteration ******** //_VisualPropertyTreeRoot->displayStatesOfTreeLeaves(); //nldebug( "C%hu S%hu: end of pair block at bitpos %d", clientId, (uint16)slot, outbox.getPosInBit() ); } // Reset the priority of the pair, even if nothing was filled if ( slot != 0 ) pairState.resetPrio(); // note: nothing can remain to be filled, as we allow to exceed the size limit } } /* * Test the criterion for the position of the entity 'slot' seen by 'clientId' */ bool CDistancePrioritizer::positionHasChangedEnough() { // TEMP: do not send if local mode (because the local position is useless for the mektoub) if ( TVPNodeServer::PrioContext.ZCache & 0x1 ) return false; uint32 lastSentMileage = _History->getMileage( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.Slot ); if ( lastSentMileage != 0 ) { // Calculate difference distance between current and lastsent mileage (unsigned to allow mileage overflow) if ( (TVPNodeServer::PrioContext.Sentity->Mileage - lastSentMileage) * _DistanceDeltaRatio > (uint32)(TVPNodeServer::PrioContext.DistanceCE) ) { #ifdef NL_DEBUG ++(TVPNodeServer::PrioContext.ClientHost->MoveNumber); #endif return true; } else { if (verbosePropertiesSent==0) { nldebug("Ignoring move: Mileage=%u, lastSentMileage=%u, _DistanceDeltaRatio=%u, DistanceCE=%u", (uint32) TVPNodeServer::PrioContext.Sentity->Mileage, (uint32) lastSentMileage, (uint32) _DistanceDeltaRatio, (uint32) TVPNodeServer::PrioContext.DistanceCE ); } return false; } } else { // Not sent yet return true; } } /* * Test the criterion for thetaIntMode */ bool CDistancePrioritizer::thetaIntModehasChanged(const CPropertyHistory::CPropertyEntry& entry) { if ( ! entityIsWithinDistanceThreshold( PROPERTY_ORIENTATION ) ) return false; if ( entry.HasValue ) { CMirrorPropValueRO currentTheta( TheDataset, TVPNodeServer::PrioContext.EntityIndex, DSPropertyORIENTATION ); return ( (uint32)currentTheta != (uint32)entry.LastSent ); } else { return TVPNodeServer::PrioContext.Sentity->propertyIsInitialized( PROPERTY_ORIENTATION, DSPropertyORIENTATION, TVPNodeServer::PrioContext.EntityIndex, (TYPE_ORIENTATION*)NULL ); } } /* * Test the criterion for the orientation of the entity 'slot' seen by 'clientId' + initialized */ bool CDistancePrioritizer::orientationHasChangedEnough(const CPropertyHistory::CPropertyEntry& entry, float angleRatio ) { if ( ! entityIsWithinDistanceThreshold( PROPERTY_ORIENTATION ) ) return false; if ( entry.HasValue ) { CMirrorPropValueRO currentAngle( TheDataset, TVPNodeServer::PrioContext.EntityIndex, DSPropertyORIENTATION ); /* // Orientation is a float angle in radian const float& oldangle = *((float*)&(entry.LastSent)); float deltaAngle = (float)fabs( (float)(currentAngle() - oldangle) ); deltaAngle = (float)fmod( deltaAngle+(2*Pi), (2*Pi) ); //nldebug( "getDelta(theta) : dA=%g", deltaAngle ); return ( deltaAngle > (float)Pi/angleRatio ); // the orientation is useful only when the pos does not change */ float oldangle; entry.getValue(oldangle); float deltaAngle = (float)( Pi - fabs(fmod(currentAngle()-oldangle+4*Pi, 2*Pi)-Pi) ); // deltaAngle is in [0, 2*Pi] //nldebug( "getDelta(theta) : dA=%g", deltaAngle ); return ( deltaAngle*angleRatio > (float)Pi ); // the orientation is useful only when the pos does not change } else { // Not sent yet => always sent theta, even if it's zero (anyway, it's unlikely to be exactly 0.0: remind that this initial float is determined, for bots, by leveldesigners with the mouse) return true; } } /* * Test the criterion for the specified property of the entity 'slot' seen by 'clientId' */ inline bool CDistancePrioritizer::entityIsWithinDistanceThreshold( TPropIndex propIndex ) { // Compare distance with the threshold //nldebug( "C%hu - slot %hu - prop %hu: DISTANCE=%d THRESHOL=%d", TVPNodeServer::PrioContext.ClientHost->clientId(), (uint16)TVPNodeServer::PrioContext.Slot, propIndex, TVPNodeServer::PrioContext.DistanceCE, TheEntityTranslator->getDistThreshold( propertyid ) ); return ( TVPNodeServer::PrioContext.DistanceCE < getDistThreshold( propIndex ) ); } /* * Special arbitrate case for BEHAVIOUR property * Threshold in this case should be the same than the target list threshold when * the behaviour is a range attack or projectile behaviour */ #ifndef NL_DEBUG inline #endif void CDistancePrioritizer::arbitrateDiscreetBehaviourProperty(const CPropertyHistory::CEntityEntry& entry, CEntity* sentity) { #ifdef STORE_MIRROR_VP_IN_CLASS const CMirrorPropValueRO& propBehav = sentity->VP_BEHAVIOUR; #else CMirrorPropValueRO propBehav( TheDataset, TVPNodeServer::PrioContext.EntityIndex, DSPropertyBEHAVIOUR ); #endif if (!discreetPropertyHasChanged( entry.Properties[PROPERTY_BEHAVIOUR], propBehav, PROPERTY_BEHAVIOUR, (TYPE_BEHAVIOUR*)NULL )) { GET_VP_NODE(BEHAVIOUR)->BranchHasPayload = false; return; } const MBEHAV::CBehaviour &behav = propBehav(); TPropIndex refDistanceProperty; switch(behav.Behaviour) { case MBEHAV::CAST_OFF_SUCCESS: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_OFF_LINK: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_CUR_SUCCESS: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_CUR_LINK: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_MIX_SUCCESS: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_MIX_LINK: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::RANGE_ATTACK: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_ACID: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_BLIND: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_COLD: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_ELEC: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_FEAR: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_FIRE: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_HEALHP: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_MAD: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_POISON: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_ROOT: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_ROT: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_SHOCK: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_SLEEP: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_SLOW: refDistanceProperty = PROPERTY_TARGET_LIST; break; case MBEHAV::CAST_STUN: refDistanceProperty = PROPERTY_TARGET_LIST; // valid distance should be the same than the target list // because target list and behaviour are sent together break; default: refDistanceProperty = PROPERTY_BEHAVIOUR; break; } // GET_VP_NODE(BEHAVIOUR)->BranchHasPayload = entityIsWithinDistanceThreshold(refDistanceProperty); } /* * Fill the BranchHasPayload flags of the tree, using the rules deciding if a discreet property need to be sent */ void CDistancePrioritizer::arbitrateAllDiscreetProperties(const CPropertyHistory::CEntityEntry& entry) { H_AUTO(arbitrateAllDiscreetProperties); CEntity* sentity = TVPNodeServer::PrioContext.Sentity; arbitrateDiscreetPropertyWithoutThreshold( entry, SHEET ); arbitrateDiscreetBehaviourProperty( entry, sentity ); // Don't limit to the distance threshold when triggering sending of the name of the target if ( TVPNodeServer::PrioContext.IsTarget ) arbitrateDiscreetPropertyWithoutThreshold( entry, NAME_STRING_ID ); else arbitrateDiscreetProperty( entry, NAME_STRING_ID ); //arbitrateDiscreetProperty( entry, TARGET_ID ); // now done in fillOutBox() in "mode switch" arbitrateDiscreetProperty( entry, CONTEXTUAL ); arbitrateDiscreetPropertyWithoutThreshold( entry, MODE ); arbitrateDiscreetProperty( entry, VPA ); arbitrateDiscreetProperty( entry, VPB ); arbitrateDiscreetProperty( entry, VPC ); arbitrateDiscreetPropertyWithoutThreshold( entry, ENTITY_MOUNTED_ID ); arbitrateDiscreetPropertyWithoutThreshold( entry, RIDER_ENTITY_ID ); arbitrateTargetList( entry, TARGET_LIST ); arbitrateDiscreetProperty( entry, VISUAL_FX ); arbitrateDiscreetProperty( entry, GUILD_SYMBOL ); arbitrateDiscreetProperty( entry, GUILD_NAME_ID ); arbitrateDiscreetProperty( entry, EVENT_FACTION_ID ); arbitrateDiscreetProperty( entry, PVP_MODE ); arbitrateDiscreetProperty( entry, PVP_CLAN ); arbitrateNeverSendProperty( OWNER_PEOPLE ); arbitrateDiscreetProperty( entry, OUTPOST_INFOS ); if (TVPNodeServer::PrioContext.Slot != 0) { arbitrateCommonPosAndMode(entry); arbitrateDiscreetProperty( entry, TARGET_ID ); arbitrateDiscreetProperty( entry, BARS ); } else { arbitrateSlot0PosAndMode(entry); arbitrateDiscreetPropertyWithoutThreshold( entry, TARGET_ID ); // no need of threshold // never send BARS for User Player (no need since sent with the USER:BARS message) arbitrateNeverSendProperty( BARS ); } } /* * Fill the BranchHasPayload flags of the tree, using the rules deciding if a discreet property need to be sent */ void CDistancePrioritizer::arbitrateNPCDiscreetProperties(const CPropertyHistory::CEntityEntry& entry) { H_AUTO(arbitrateNPCDiscreetProperties); CEntity* sentity = TVPNodeServer::PrioContext.Sentity; arbitrateDiscreetPropertyWithoutThreshold( entry, SHEET ); arbitrateDiscreetBehaviourProperty(entry, sentity); // Don't limit to the distance threshold when triggering sending of the name of the target if ( TVPNodeServer::PrioContext.IsTarget ) arbitrateDiscreetPropertyWithoutThreshold( entry, NAME_STRING_ID ); else arbitrateDiscreetProperty( entry, NAME_STRING_ID ); arbitrateDiscreetProperty( entry, TARGET_ID ); // NPC can never be in Slot 0 // Specific distance for NPCs' contextual property GET_VP_NODE(CONTEXTUAL)->BranchHasPayload = TVPNodeServer::PrioContext.DistanceCE < THRESHOLD_CONTEXTUAL_NPC && discreetPropertyHasChanged( entry.Properties[PROPERTY_CONTEXTUAL], sentity->VP_CONTEXTUAL, PROPERTY_CONTEXTUAL, (TYPE_CONTEXTUAL*)NULL ); arbitrateDiscreetPropertyWithoutThreshold( entry, MODE ); arbitrateDiscreetProperty( entry, BARS ); arbitrateDiscreetProperty( entry, VPA ); arbitrateDiscreetProperty( entry, VPB ); arbitrateDiscreetProperty( entry, VPC ); arbitrateDiscreetPropertyWithoutThreshold( entry, ENTITY_MOUNTED_ID ); arbitrateNeverSendProperty( RIDER_ENTITY_ID ); arbitrateTargetList( entry, TARGET_LIST ); arbitrateDiscreetProperty( entry, VISUAL_FX ); arbitrateDiscreetProperty( entry, GUILD_SYMBOL ); arbitrateDiscreetProperty( entry, GUILD_NAME_ID ); arbitrateNeverSendProperty( EVENT_FACTION_ID ); arbitrateNeverSendProperty( PVP_MODE ); arbitrateNeverSendProperty( PVP_CLAN ); arbitrateNeverSendProperty( OWNER_PEOPLE ); arbitrateDiscreetProperty( entry, OUTPOST_INFOS ); arbitrateCommonPosAndMode(entry); } /* * Fill the BranchHasPayload flags of the tree, using the rules deciding if a discreet property need to be sent */ void CDistancePrioritizer::arbitrateCreatureDiscreetProperties(const CPropertyHistory::CEntityEntry& entry) { H_AUTO(arbitrateCreatureDiscreetProperties); CEntity* sentity = TVPNodeServer::PrioContext.Sentity; arbitrateDiscreetPropertyWithoutThreshold( entry, SHEET ); arbitrateDiscreetBehaviourProperty(entry, sentity); // Don't limit to the distance threshold when triggering sending of the name of the target if ( TVPNodeServer::PrioContext.IsTarget ) arbitrateDiscreetPropertyWithoutThreshold( entry, NAME_STRING_ID ); else arbitrateDiscreetProperty( entry, NAME_STRING_ID ); arbitrateDiscreetProperty( entry, TARGET_ID ); // Creature can never be in Slot 0 arbitrateDiscreetProperty( entry, CONTEXTUAL ); arbitrateDiscreetPropertyWithoutThreshold( entry, MODE ); arbitrateDiscreetProperty( entry, BARS ); arbitrateNeverSendProperty( VPA ); arbitrateDiscreetProperty( entry, VPB ); arbitrateNeverSendProperty( VPC ); arbitrateNeverSendProperty( ENTITY_MOUNTED_ID ); arbitrateDiscreetPropertyWithoutThreshold( entry, RIDER_ENTITY_ID ); arbitrateTargetList( entry, TARGET_LIST ); arbitrateNeverSendProperty( VISUAL_FX ); arbitrateNeverSendProperty( GUILD_SYMBOL ); arbitrateNeverSendProperty( GUILD_NAME_ID ); arbitrateNeverSendProperty( EVENT_FACTION_ID ); arbitrateNeverSendProperty( PVP_MODE ); arbitrateNeverSendProperty( PVP_CLAN ); arbitrateDiscreetProperty( entry, OWNER_PEOPLE ); arbitrateNeverSendProperty( OUTPOST_INFOS ); arbitrateCommonPosAndMode(entry); } /* * Fill the BranchHasPayload flags of the tree, using the rules deciding if a discreet property need to be sent */ void CDistancePrioritizer::arbitrateForageSourceDiscreetProperties(const CPropertyHistory::CEntityEntry& entry) { H_AUTO(arbitrateForageSourceDiscreetProperties); CEntity* sentity = TVPNodeServer::PrioContext.Sentity; arbitrateDiscreetPropertyWithoutThreshold( entry, SHEET ); arbitrateNeverSendProperty( BEHAVIOUR ); arbitrateDiscreetProperty( entry, NAME_STRING_ID ); arbitrateDiscreetProperty( entry, TARGET_ID ); // ForageSource can never be in Slot 0 arbitrateDiscreetProperty( entry, CONTEXTUAL ); arbitrateNeverSendProperty( MODE ); arbitrateDiscreetProperty( entry, BARS ); arbitrateNeverSendProperty( VPA ); arbitrateNeverSendProperty( VPB ); arbitrateNeverSendProperty( VPC ); arbitrateNeverSendProperty( ENTITY_MOUNTED_ID ); arbitrateNeverSendProperty( RIDER_ENTITY_ID ); arbitrateTargetList( entry, TARGET_LIST ); arbitrateDiscreetProperty( entry, VISUAL_FX ); arbitrateNeverSendProperty( GUILD_SYMBOL ); arbitrateNeverSendProperty( GUILD_NAME_ID ); arbitrateNeverSendProperty( EVENT_FACTION_ID ); arbitrateNeverSendProperty( PVP_MODE ); arbitrateNeverSendProperty( PVP_CLAN ); arbitrateNeverSendProperty( OWNER_PEOPLE ); arbitrateNeverSendProperty( OUTPOST_INFOS ); GET_VP_NODE(POSITION)->BranchHasPayload = sentity->positionIsInitialized() && positionHasChangedEnough(); GET_VP_NODE(ORIENTATION)->BranchHasPayload = thetaIntModehasChanged(entry.Properties[PROPERTY_ORIENTATION]); } /* * Fill the BranchHasPayload flags of the tree, using the rules deciding if a discreet property need to be sent */ inline void CDistancePrioritizer::arbitrateCommonPosAndMode(const CPropertyHistory::CEntityEntry& entry) { // Position if changed enough (if not carried by mode) // Orientation if changed > 60° (it's head angle only) or in first block (useful for static entities such as bot objects) bool modeIsChanging = GET_VP_NODE(MODE)->BranchHasPayload; bool sheetIsChanging = GET_VP_NODE(SHEET)->BranchHasPayload; bool posIsReady = TVPNodeServer::PrioContext.Sentity->positionIsInitialized(); GET_VP_NODE(POSITION)->BranchHasPayload = (!modeIsChanging) && posIsReady && positionHasChangedEnough(); GET_VP_NODE(ORIENTATION)->BranchHasPayload = (sheetIsChanging && posIsReady) || orientationHasChangedEnough( entry.Properties[PROPERTY_ORIENTATION], 36.0f ); // 5° } /* * Fill the BranchHasPayload flags of the tree, using the rules deciding if a discreet property need to be sent */ inline void CDistancePrioritizer::arbitrateSlot0PosAndMode(const CPropertyHistory::CEntityEntry& entry) { arbitrateNeverSendProperty(POSITION); arbitrateNeverSendProperty(ORIENTATION); } /* * */ inline void CDistancePrioritizer::serialSlotHeader( CClientHost& client, CEntity *sentity, TPairState& pairState, CLFECOMMON::TCLEntityId slot, TOutBox& outbox ) { // Slot (8 bits) #ifdef NL_DEBUG sint beginbitpos = outbox.getPosInBit(); #endif outbox.serialAndLog1( slot ); // Association change bits (2 bits) uint32 associationBits = (uint32)pairState.AssociationChangeBits; outbox.serialAndLog2( associationBits, 2 ); if ( pairState.AssociationChangeBits != pairState.PrevAssociationBits ) { //LOG_WHAT_IS_SENT( "slot %hu ab %u beginpos %d endpos %d beginbitpos %d endbitpos %d", (uint16)slot, associationBits, beginbitpos/8, outbox.getPosInBit()/8, beginbitpos, outbox.getPosInBit() ); pairState.PrevAssociationBits = pairState.AssociationChangeBits; // & 0x3; _History->storeDisassociation( client.clientId(), slot, client.sendNumber(), pairState.AssociationChangeBits ); // pairState.AssociationChangeBits &= 0x7F; } // Timestamp (1 or 5 bits, depending on the type of entity) (TVPNodeServer::PrioContext.Timestamp initialized to 0) uint32 timestampDelta = 0; if ( sentity ) { const CEntityId& seenEntityId = TheDataset.getEntityId( TVPNodeServer::PrioContext.EntityIndex ); if ( seenEntityId.getType() == RYZOMID::player ) { // For players, always set the timestamp delta, using TickPosition // Note: discreet property change times won't be accurate TVPNodeServer::PrioContext.Timestamp = sentity->TickPosition; timestampDelta = CTickEventHandler::getGameCycle() - sentity->TickPosition; if ( timestampDelta > 15 ) // clamp to 4bit timestampDelta = 15; timestampDelta |= 0x10; // 'timestampIsThere bit': first bit is bit 5 (high to low order) } else if ( seenEntityId.getType() >= RYZOMID::object ) { // For non-players/non-bots types (e.g. bags), set the timestamp delta if entity is being spawned to the client //if ( _VisualPropertyTreeRoot->B->B->getSHEETnode()->BranchHasPayload ) // assumes this is done after arbitrateDiscreetProperties() // CHANGED BEN if ( GET_VP_NODE(SHEET)->BranchHasPayload ) // assumes this is done after arbitrateDiscreetProperties() { TVPNodeServer::PrioContext.Timestamp = TheDataset.getOnlineTimestamp( TVPNodeServer::PrioContext.EntityIndex ); timestampDelta = CTickEventHandler::getGameCycle() - TVPNodeServer::PrioContext.Timestamp; if ( timestampDelta > 15 ) // clamp to 4bit timestampDelta = 15; timestampDelta |= 0x10; // 'timestampIsThere bit': first bit is bit 5 (high to low order) } } // For bots, the timestamp is not needed, the client will take _ServerGameCycle } outbox.serialAndLog2( timestampDelta, (timestampDelta!=0) ? 5 : 1 ); } #ifdef STORE_MIRROR_VP_IN_CLASS #define caseFillAction( name ) ap->setValue64( TVPNodeServer::PrioContext.Sentity->VP_##name() ); #else // STORE_MIRROR_VP_IN_CLASS #define caseFillAction( name ) \ CMirrorPropValueRO prop( TheDataset, TVPNodeServer::PrioContext.EntityIndex, DSProperty##name ); \ ap->setValue64( prop() ); #endif // STORE_MIRROR_VP_IN_CLASS /* * SHEET */ void fillSHEET( TOutBox& outbox, TPropIndex ) { #ifdef NL_DEBUG // In non-debug cases, it is done in CVisionProvider::addPair() //CClientEntityIdTranslator::CEntityInfo& info = TVPNodeServer::PrioContext.ClientHost->IdTranslator.getInfo( TVPNodeServer::PrioContext.Slot ); // CHANGED BEN //info.AssociationState = CClientEntityIdTranslator::CEntityInfo::NormalAssociation; // CHANGED BEN TVPNodeServer::PrioContext.Prioritizer->getVisionArray()->setAssociationState( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.Slot, TPairState::NormalAssociation); #endif bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_SHEET ); // CHANGED BEN DECLARE_AP(SHEET); // Pack sheet and compressed row into the action #ifdef STORE_MIRROR_VP_IN_CLASS CMirrorPropValueRO& prop = TVPNodeServer::PrioContext.Sentity->VP_SHEET; #else CMirrorPropValueRO prop( TheDataset, TVPNodeServer::PrioContext.EntityIndex, DSPropertySHEET ); #endif uint32 sheetValue = prop(); TDataSetIndex compressedRow = TVPNodeServer::PrioContext.EntityIndex.getCompressedIndex(); uint64 value = (uint64)sheetValue | (((uint64)compressedRow) << 32); ap->setValue64( value ); LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu SHEET at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_SHEET, outbox.getPosInBit(), ap->getValue() ); // Add row into the action ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); // Include alias if non-null in mirror (only for mission giver NPCs) CMirrorPropValueRO aliasProp( TheDataset, TVPNodeServer::PrioContext.EntityIndex, DSPropertyNPC_ALIAS ); if (aliasProp() != 0) { bool aliasBit = true; outbox.serialBitAndLog( aliasBit ); outbox.serialAndLog1( const_cast(aliasProp()) ); // no need to store in history, alias never changes for an entity } else { bool aliasBit = false; outbox.serialBitAndLog( aliasBit ); } #ifdef TEST_LOST_PACKET if ( TestPacketLost.get() ) { nldebug( "This SHEET sending will be dropped..." ); TestPacketLostTimer = CTickEventHandler::getGameCycle() + 10; TestPacketLost = false; TestPacketLostSlot = TVPNodeServer::PrioContext.Slot; } #endif } /* * BEHAVIOUR */ void fillBEHAVIOUR( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_BEHAVIOUR ); DECLARE_AP(BEHAVIOUR); caseFillAction( BEHAVIOUR ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu BEHAVIOUR at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_BEHAVIOUR, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * NAME_STRING_ID */ void fillNAME_STRING_ID( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_NAME_STRING_ID ); DECLARE_AP(NAME_STRING_ID); caseFillAction( NAME_STRING_ID ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu NAME_STRING_ID at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_NAME_STRING_ID, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * TARGET_LIST */ static vector TargetSlotsList(256); NLMISC_COMMAND(displayTargetList,"Display the target list of an entity","") { if ( args.size() > 1 ) return false; CEntityId entity; entity.fromString(args[0].c_str()); CMirrorPropValueList targets(TheDataset, entity, "TargetList"); CMirrorPropValueList::iterator it; it = targets.begin(); if (it != targets.end()) { const uint32 *effectCycle = &((*it)()); log.displayNL("TargetStamp: %d", *effectCycle); ++it; } for (; it!=targets.end(); ++it) { uint32 index = (*it)(); log.displayNL("Target: %d", TDataSetRow::createFromRawIndex(index).getIndex()); } return true; } void fillTARGET_LIST( TOutBox& outbox, TPropIndex ) { CClientHost *client = TVPNodeServer::PrioContext.ClientHost; CMirrorPropValueList targets(TheDataset, TVPNodeServer::PrioContext.EntityIndex, DSPropertyTARGET_LIST ); CMirrorPropValueList::iterator it; TargetSlotsList.clear(); it = targets.begin(); for (; it!=targets.end(); ) { TDataSetRow index = TDataSetRow::createFromRawIndex((*it)()); ++it; // check list overflow if (it == targets.end()) break; // distance to target (in 1/127 of 100m) uint32 dt = *(&((*it)())); // translate slot TCLEntityId slot = client->IdTranslator.getCEId(index); if (slot != INVALID_SLOT) { TargetSlotsList.push_back(slot); TargetSlotsList.push_back((uint8)dt); } ++it; } // serialises branch has payload bool payLoad = true; outbox.serialBitAndLog(payLoad); // restricts to 256 entities uint longListSize = (uint)TargetSlotsList.size(); if (longListSize > 32) longListSize = 32; uint8 listSize = (uint8)longListSize; // serialises short size outbox.serialAndLog1(listSize); // serialises slot list if (listSize > 0) outbox.serialBuffer(&(TargetSlotsList[0]), listSize); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_TARGET_LIST ); DECLARE_AP(TARGET_LIST); ap->setValue64( TheDataset.getChangeTimestamp( DSPropertyTARGET_LIST, TVPNodeServer::PrioContext.EntityIndex ) ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu TARGET_LIST at bitpos %d", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_TARGET_LIST, outbox.getPosInBit() ); } /* * BARS */ void fillBARS( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_BARS ); DECLARE_AP(BARS); caseFillAction( BARS ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu BARS at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_BARS, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * VPA, VPB, VPC * Assumes: * - They have the same mirror size * - There mirror dataset property index is contiguous (VPC = VPB + 1 = VPA + 2) */ void fillVisualPropertyABC( TOutBox& outbox, TPropIndex propIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, propIndex ); DECLARE_AP_INDEX(propIndex); CMirrorPropValueRO prop( TheDataset, TVPNodeServer::PrioContext.EntityIndex, propIndex-PROPERTY_VPA+DSPropertyVPA ); \ ap->setValue64( prop() ); LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu %s at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, propIndex, CLFECOMMON::getPropText( propIndex ), outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * CONTEXTUAL */ void fillCONTEXTUAL( TOutBox& outbox, TPropIndex propIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_CONTEXTUAL ); DECLARE_AP(CONTEXTUAL); caseFillAction( CONTEXTUAL ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu CONTEXTUAL at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_CONTEXTUAL, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * VISUAL_FX */ void fillVISUAL_FX( TOutBox& outbox, TPropIndex propIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_VISUAL_FX ); DECLARE_AP(VISUAL_FX); caseFillAction( VISUAL_FX ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu VISUAL_FX at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_VISUAL_FX, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * MODE */ void fillMODE( TOutBox& outbox, TPropIndex ) { // Fill for mode special case bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_MODE ); DECLARE_AP(MODE); uint64 modeLong; // uses 8+4+16+16 = 44 bits // Mode value (on 8 bits) CMirrorPropValue prop( TheDataset, TVPNodeServer::PrioContext.EntityIndex, DSPropertyMODE ); // Store in history ap->setValue64( prop().RawModeAndParam ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); // Game cycle when mode changed (4 bits -> 1.6 second max) uint32 tickModeDelta = CTickEventHandler::getGameCycle() - prop.getTimestamp(); if ( tickModeDelta > 15 ) tickModeDelta = 15; // Pack all with position 2D when mode changed, or combat angle (16 bits * 2) /* // OBSOLETE: MBEHAV::COMBAT_FLOAT no longer used if ( prop().Mode == MBEHAV::COMBAT_FLOAT ) { uint64 theta = (uint64)(*(uint32*)&(prop().Theta)); modeLong = ((uint64)(prop().Mode)) | ((uint64)(tickModeDelta << 8)) | (theta << 12); LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu MODE at bitpos %d : %u [theta=%g dt=%u]", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_MODE, outbox.getPosInBit(), prop().Mode, prop().Theta, tickModeDelta ); } else */ { uint64 posModeX16, posModeY16; if ( TVPNodeServer::PrioContext.PositionAlreadySent ) { // Take the pos from the mode change posModeX16 = prop().Pos.X16; posModeY16 = prop().Pos.Y16; } else { // Take the current pos posModeX16 = TVPNodeServer::PrioContext.Sentity->X() >> 4; // TODO: make a method for this formula posModeY16 = TVPNodeServer::PrioContext.Sentity->Y() >> 4; tickModeDelta = 1; } modeLong = ((uint64)((uint8)(prop().Mode))) | ((uint64)(tickModeDelta << 8)) | (posModeX16 << 12) | (posModeY16 << 28); LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu MODE at bitpos %d : %u [x16=%hu y16=%hu dt=%u] %s", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_MODE, outbox.getPosInBit(), prop().Mode, prop().Pos.X16, prop().Pos.Y16, tickModeDelta, TVPNodeServer::PrioContext.PositionAlreadySent?"":" WithFirstPos" ); // The pos might be null (if mode set before 1st pos). The client has to handle the case. /*#ifdef NL_DEBUG if ( posModeX16==0 || posModeY16==0 ) nlwarning( "E%d: The pos16 in the mode is %hu %hu", TVPNodeServer::PrioContext.EntityIndex, (uint16)posModeX16, (uint16)posModeY16 ); #endif*/ } // Fill ap->setAndPackValue( modeLong, outbox ); //CActionFactory::getInstance()->remove( (CAction*&)act ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * GUILD_NAME_ID */ void fillGUILD_NAME_ID( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_GUILD_NAME_ID ); DECLARE_AP(GUILD_NAME_ID); caseFillAction( GUILD_NAME_ID ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu GUILD_NAME_ID at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_GUILD_NAME_ID, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * GUILD_SYMBOL */ void fillGUILD_SYMBOL( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_GUILD_SYMBOL ); DECLARE_AP(GUILD_SYMBOL); caseFillAction( GUILD_SYMBOL ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu GUILD_SYMBOL at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_GUILD_SYMBOL, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * EVENT_FACTION_ID */ void fillEVENT_FACTION_ID( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_EVENT_FACTION_ID ); DECLARE_AP(EVENT_FACTION_ID); caseFillAction( EVENT_FACTION_ID ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu EVENT_FACTION_ID at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_EVENT_FACTION_ID, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * PVP_MODE */ void fillPVP_MODE( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_PVP_MODE ); DECLARE_AP(PVP_MODE); caseFillAction( PVP_MODE ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu PVP_MODE at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_PVP_MODE, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * PVP_CLAN */ void fillPVP_CLAN( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, PROPERTY_PVP_CLAN ); DECLARE_AP(PVP_CLAN); caseFillAction( PVP_CLAN ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu PVP_CLAN at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_PVP_CLAN, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * OWNER_PEOPLE */ void fillOWNER_PEOPLE( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); DECLARE_AP(OWNER_PEOPLE); caseFillAction( OWNER_PEOPLE ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu OWNER_PEOPLE at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_OWNER_PEOPLE, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * OUTPOST_INFOS */ void fillOUTPOST_INFOS( TOutBox& outbox, TPropIndex ) { bool payloadBit = true; outbox.serialBitAndLog( payloadBit ); DECLARE_AP(OUTPOST_INFOS); caseFillAction( OUTPOST_INFOS ) LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu OUTPOST_INFOS at bitpos %d - value %"NL_I64"u", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, PROPERTY_OUTPOST_INFOS, outbox.getPosInBit(), ap->getValue() ); ap->packFast( outbox ); CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } /* * TARGET_ID, ENTITY_MOUNTED, RIDER_ENTITY */ void fillRowProperty( TOutBox& outbox, TPropIndex propIndex ) { bool payloadBit = true; TCLEntityId slot; CMirrorPropValueRO prop( TheDataset, TVPNodeServer::PrioContext.EntityIndex, CEntityContainer::propertyIndexInDataSetToVisualPropIndex( propIndex ) ); TEntityIndex targetindex(prop()); if ( !targetindex.isValid() ) { // No target slot = INVALID_SLOT; } else { TEntityIndex seenEntityIndex = TVPNodeServer::PrioContext.EntityIndex; const char *propName = getPropText( propIndex ); LOG_WHAT_IS_SENT( "%u: About to send client %hu (%s) the %s '%u --> %u'", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientHost->clientId(), TVPNodeServer::PrioContext.ClientHost->eId().toString().c_str(), propName, seenEntityIndex.getIndex(), targetindex.getIndex() ); if ( targetindex == TVPNodeServer::PrioContext.ClientHost->entityIndex() ) { // The entity targets the client slot = 0; } else if ( targetindex == seenEntityIndex ) { // The entity targets itself slot = INVALID_SLOT; } else { // Translate CEntityId to slot: get entityid, then slot TCLEntityId result = TVPNodeServer::PrioContext.ClientHost->IdTranslator.getCEId( targetindex ); if ( result != INVALID_SLOT ) { slot = result; } else { LOG_WHAT_IS_SENT( "%s slot not found: E%u", propName, targetindex.getIndex() ); payloadBit = false; } } } // Fill for property target/mount special case outbox.serialBitAndLog( payloadBit ); if ( payloadBit ) { LOG_WHAT_IS_SENT( "%u: Filling buffer for C%hu S%hu P%hu %s at bitpos %d - slot %hu", CTickEventHandler::getGameCycle(), TVPNodeServer::PrioContext.ClientId, (uint16)TVPNodeServer::PrioContext.Slot, propIndex, CLFECOMMON::getPropText( propIndex ), outbox.getPosInBit(), (uint16)slot ); //CActionSint64 *ap = (CActionSint64*)CActionFactory::getInstance()->createByPropIndex( TVPNodeServer::PrioContext.Slot, propIndex ); DECLARE_AP_INDEX(propIndex); ap->setValue64( prop().getIndex() ); // Store the entity index into the history CFrontEndService::instance()->history()->store( TVPNodeServer::PrioContext.ClientId, TVPNodeServer::PrioContext.ClientHost->sendNumber(), ap ); // The value that will be sent is the slot, not the entity index ap->setAndPackValue( slot, outbox ); //CActionFactory::getInstance()->remove( (CAction*&)ap ); REMOVE_AP(); ++(TVPNodeServer::PrioContext.ClientHost->NbActionsSentAtCycle); } } TVPNodeServer::TPrioContext TVPNodeServer::PrioContext; // Flattened Node Tree TVPNodeServer* TVPNodeServer::FlatVPTree[CLFECOMMON::NB_VISUAL_PROPERTIES]; // Reordered Node tree (used by fastPropagateBackBranchHasPayload()) std::vector TVPNodeServer::SortedFlatVPTree; const bool TVPNodeServer::FalseBoolPayLoad = false; // Reordered Node tree (used by fastFillDiscreetProperties) std::vector TVPNodeServer::SortedFlatVPTreeFill; void TVPNodeServer::initSortedFlatVPTree() { if (isLeaf()) return; CSortedFlatVPTreeItem item; item.Node = this; if (a()) { item.APayLoad = &(a()->BranchHasPayload); a()->initSortedFlatVPTree(); } if (b()) { item.BPayLoad = &(b()->BranchHasPayload); b()->initSortedFlatVPTree(); } SortedFlatVPTree.push_back(item); } void TVPNodeServer::initSortedFlatVPTreeFill() { uint thisItem = (uint)SortedFlatVPTreeFill.size(); SortedFlatVPTreeFill.push_back(CSortedFlatVPTreeFillItem()); SortedFlatVPTreeFill[thisItem].Node = this; if (a()) a()->initSortedFlatVPTreeFill(); if (b()) b()->initSortedFlatVPTreeFill(); SortedFlatVPTreeFill[thisItem].NextIfNoPayload = (uint)SortedFlatVPTreeFill.size(); } namespace CLFECOMMON { // Factory for TVPNodeBase::buildTree() TVPNodeBase *NewNode() { return (TVPNodeBase*) new TVPNodeServer(); } }; #ifdef NL_DEBUG NLMISC_DYNVARIABLE( uint32, MoveNumber, "MoveNumber of entities seen by monitored client" ) { if ( get ) { CFrontEndService *fe = CFrontEndService::instance(); //nlassert( fe->MonitoredClient <= MAX_NB_CLIENTS ); CClientHost *client = fe->receiveSub()->clientIdCont()[fe->MonitoredClient]; if ( client ) { *pointer = client->MoveNumber; } else { *pointer = 9999; } } } #endif NLMISC_COMMAND(verbosePropertiesSent,"Turn on or off or check the state of verbose logging of what is sent"," | all | off") { if ( args.size() > 1 ) return false; if ( args.size() == 1 ) { if ( args[0] == string("all") ) verbosePropertiesSent = 0; else if ( args[0] == string("off") ) verbosePropertiesSent = INVALID_CLIENT; else NLMISC::fromString(args[0], verbosePropertiesSent); } log.displayNL( "verbosePropertiesSent is %s", (verbosePropertiesSent==INVALID_CLIENT)?"off":((verbosePropertiesSent==0)?"all":toString("C%hu", verbosePropertiesSent).c_str()) ); return true; }