package de.lighti.packet; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import com.valve.dota2.Netmessages.CSVCMsg_PacketEntities; import de.lighti.DotaPlay; import de.lighti.GameEventListener; import de.lighti.model.Entity; import de.lighti.model.state.EntityClass; import de.lighti.model.state.ParseState; import de.lighti.model.state.StateUtils.FlatSendTable; import de.lighti.model.state.StateUtils.StringTable; import de.lighti.model.state.StateUtils.StringTableEntry; import de.lighti.util.BitInputBuffer; /** * This class handles the CSVCMsg_PacketEntities SVCMessage. The packet is originally * a binary field and denotes created, changed, and deleted entities. * * The code is based on the c++ parser library https://github.com/dschleck/edith * * @author Tobias Mahlmann * */ public final class Entities { /** * Indicates if an Entity has been created or deleted. * @author Tobias Mahlmann * */ private enum EntityUpdateFlag { /** * Created. */ UF_EnterPVS, /** * Deleted. */ UF_Delete, /** * Deleted. */ UF_LeavePVS } private final static int MAX_EDICTS = 0x800; private final static String INSTANCE_BASELINE_TABLE = "instancebaseline"; private static StringTableEntry getBaseline( int class_i, ParseState state ) { if (state == null) { throw new IllegalArgumentException( "No state created." ); } final StringTable instance_baseline = state.getStringTable( INSTANCE_BASELINE_TABLE ); return instance_baseline.get( "" + class_i ); } public static void handlePacketEntities( CSVCMsg_PacketEntities e, ParseState state ) throws IOException { final ByteBuffer entityData = e.getEntityData().asReadOnlyByteBuffer(); final BitInputBuffer stream = new BitInputBuffer( entityData ); final int[] entityId = { -1 }; for (int i = 0; i < e.getUpdatedEntries(); i++) { final Set<EntityUpdateFlag> flags = readEntityHeader( entityId, stream ); if (flags.contains( EntityUpdateFlag.UF_EnterPVS )) { readEntityEnterPvs( entityId[0], stream, state ); } else if (flags.contains( EntityUpdateFlag.UF_LeavePVS )) { if (!e.hasIsDelta()) { throw new IllegalStateException( "Leave PVS on full update" ); } if (flags.contains( EntityUpdateFlag.UF_Delete )) { Logger.getLogger( Entities.class.getName() ).fine( "Entity " + entityId[0] + " deleted" ); final Entity removed = state.getEntity( entityId[0] ); if (removed.getId() != 1) { for (final GameEventListener l : DotaPlay.getListeners()) { l.entityRemoved( DotaPlay.getTickMs(), removed ); } removed.setId( -1 ); } } } else { readEntityUpdate( entityId[0], stream, state ); } } if (e.hasIsDelta()) { while (stream.readBit()) { entityId[0] = stream.readBitsAsInt( 11 ); Logger.getLogger( Entities.class.getName() ).fine( "Entity " + entityId[0] + " deleted" ); final Entity removed = state.getEntity( entityId[0] ); if ((removed != null) && (removed.getId() != 1)) { for (final GameEventListener l : DotaPlay.getListeners()) { l.entityRemoved( DotaPlay.getTickMs(), removed ); } removed.setId( -1 ); } } } } private static void readEntityEnterPvs( int entityId, BitInputBuffer stream, ParseState state ) throws IOException { final int class_i = stream.readBitsAsInt( state.getClassBits() ); //read serial, but we actually don't need this stream.readBitsAsInt( 10 ); if (entityId >= MAX_EDICTS) { throw new IllegalStateException( "Entity " + entityId + " exceeds max edicts" ); } final EntityClass eClass = state.getEntityClass( class_i ); final FlatSendTable flatSendTable = state.getFlatSendTable( eClass.getDtName() ); Entity entity = state.getEntity( entityId ); if (entity != null) { if (entity.getId() != -1) { Logger.getLogger( Entities.class.getName() ).fine( "Entity " + entityId + " deleted" ); for (final GameEventListener l : DotaPlay.getListeners()) { l.entityRemoved( DotaPlay.getTickMs(), entity ); } entity.setId( -1 ); } } entity = new Entity( entityId, eClass, flatSendTable ); state.setEntity( entityId, entity ); final StringTableEntry baseline = getBaseline( class_i, state ); final BitInputBuffer baselineStream = new BitInputBuffer( baseline.value ); entity.update( baselineStream ); entity.update( stream ); } private static Set<EntityUpdateFlag> readEntityHeader( int[] base, BitInputBuffer stream ) throws IOException { int value = stream.readBitsAsInt( 6 ); if ((value & 0x30) > 0) { final int a = (value >>> 4) & 3; final int b = (a == 3) ? 16 : 0; value = (stream.readBitsAsInt( (4 * a) + b ) << 4) | (value & 0xF); } base[0] += value + 1; final Set<EntityUpdateFlag> updateFlags = new HashSet<EntityUpdateFlag>(); if (!stream.readBit()) { if (stream.readBit()) { updateFlags.add( EntityUpdateFlag.UF_EnterPVS ); } } else { updateFlags.add( EntityUpdateFlag.UF_LeavePVS ); if (stream.readBit()) { updateFlags.add( EntityUpdateFlag.UF_Delete ); } } return updateFlags; } private static void readEntityUpdate( int entityId, BitInputBuffer stream, ParseState state ) { if (entityId >= MAX_EDICTS) { throw new IllegalArgumentException( "Entity id too big" ); } final Entity entity = state.getEntity( entityId ); if (entity == null) { throw new IllegalStateException( "Entity " + entityId + " is not set up." ); } entity.update( stream ); } /** * Private constructor for utility class. */ private Entities() { } }