package de.lighti.model.state; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.logging.Logger; import com.valve.dota2.Netmessages.CSVCMsg_GameEventList; import de.lighti.model.Entity; import de.lighti.model.state.StateUtils.DTProp; import de.lighti.model.state.StateUtils.FlatSendTable; import de.lighti.model.state.StateUtils.SP_Flags; import de.lighti.model.state.StateUtils.SP_Type; import de.lighti.model.state.StateUtils.SendProp; import de.lighti.model.state.StateUtils.SendTable; import de.lighti.model.state.StateUtils.StringTable; import de.lighti.util.Utils; public class ParseState { private class CompileState { Set<String> excluding; Vector<SendProp> props; CompileState() { excluding = new HashSet<String>(); props = new Vector<SendProp>(); playedHeroes = new HashMap<String, String>(); } } private final Map<Integer, Entity> entities; private final Map<Integer, EntityClass> entityClasses; private final int classBits; private final Map<String, SendTable> sendTables; private final LinkedHashMap<String, StringTable> stringTables; private final Map<String, FlatSendTable> flatSendTables; private Map<String, String> playedHeroes; private float tickInterval; private CSVCMsg_GameEventList gameEventList; private final int protocolVersion; public ParseState( int version, int maxClasses ) { protocolVersion = version; entities = new HashMap<Integer, Entity>(); sendTables = new HashMap<String, SendTable>(); flatSendTables = new HashMap<String, FlatSendTable>(); entityClasses = new HashMap<Integer, EntityClass>(); classBits = Utils.log2( maxClasses ); stringTables = new LinkedHashMap<String, StringTable>(); } private void buildHierarchy( SendTable sendTable, DTProp dtProp, CompileState state ) { dtProp.sendTable = sendTable; dtProp.propStart = state.props.size(); gather( sendTable, dtProp, state ); final Iterator<SendProp> iter = dtProp.nonDtProps.iterator(); while (iter.hasNext()) { final SendProp p = iter.next(); state.props.add( p ); } dtProp.propCount = state.props.size() - dtProp.propStart; } public void clearEntities() { entities.clear(); } private void compileSendTable( SendTable table ) { final CompileState state = new CompileState(); gatherExcludes( table, state.excluding ); final DTProp dtProp = new DTProp(); buildHierarchy( table, dtProp, state ); final Vector<Integer> priorities = new Vector(); priorities.add( 64 ); for (final SendProp prop : state.props) { final int priority = prop.getPriority(); final Iterator<Integer> iter = priorities.iterator(); int p = iter.next(); while (iter.hasNext() && p != priority) { p = iter.next(); } if (p == priorities.lastElement()) { priorities.add( priority ); } } Collections.sort( priorities ); int propOffset = 0; for (int priorityIndex = 0; priorityIndex < priorities.size(); ++priorityIndex) { final int priority = priorities.get( priorityIndex ); int hole = propOffset; int cursor = hole; while (cursor < state.props.size()) { final SendProp prop = state.props.get( cursor ); if (prop.getPriority() == priority || priority == 64 && (SP_Flags.SP_ChangesOften.getId() & prop.getFlags()) > 0) { final SendProp temp = state.props.get( hole ); state.props.set( hole, state.props.get( cursor ) ); state.props.set( cursor, temp ); ++hole; ++propOffset; } ++cursor; } } final FlatSendTable flatTable = new FlatSendTable( table.getName() ); flatTable.setProps( state.props ); flatTable.setDtProp( dtProp ); flatSendTables.put( table.getName(), flatTable ); } public void compileSendTables() { for (final SendTable t : sendTables.values()) { compileSendTable( t ); } } public void createClass( int classId, String tableName, String networkName ) { if (entityClasses.containsKey( classId )) { throw new IllegalArgumentException( "Class with id " + classId + " already exists." ); } final EntityClass ec = new EntityClass( classId, tableName, networkName ); Logger.getLogger( ParseState.class.getName() ).fine( "Created class: " + ec ); entityClasses.put( classId, ec ); } public SendTable createSendTable( String netTableName, boolean needsDecoder ) { final SendTable table = new SendTable( netTableName, needsDecoder ); sendTables.put( netTableName, table ); return table; } public StringTable createStringTable( String name, int maxEntries, int flags, boolean userDataFixedSize, int userDataSize, int userDataSizeBits ) { if (stringTables.containsKey( name )) { throw new IllegalStateException( "StringTable " + name + " already exists." ); } final StringTable table = new StringTable( name, maxEntries, userDataFixedSize, userDataSize, userDataSizeBits, flags ); stringTables.put( name, table ); return table; } public Entity deleteEntity( int entityId ) { if (!entities.containsKey( entityId )) { throw new IllegalArgumentException( "Entity " + entityId + " does not exist" ); } return entities.remove( entityId ); } private void gather( SendTable from, DTProp dt_prop, CompileState state ) { for (final SendProp prop : from.getProps()) { if (((SP_Flags.SP_Exclude.getId() | SP_Flags.SP_InsideArray.getId()) & prop.getFlags()) > 0) { continue; } else if (state.excluding.contains( from.getName() + prop.getName() )) { continue; } if (SP_Type.SP_DataTable == prop.getType()) { final SendTable dtTable = sendTables.get( prop.getDtName() ); if (dtTable == from) { throw new IllegalStateException(); } if ((SP_Flags.SP_Collapsible.getId() & prop.getFlags()) > 0) { gather( dtTable, dt_prop, state ); } else { final DTProp newDtProp = new DTProp(); dt_prop.dtProps.add( newDtProp ); buildHierarchy( dtTable, newDtProp, state ); } } else { if (dt_prop.nonDtProps.contains( prop )) { throw new IllegalStateException(); } dt_prop.nonDtProps.add( prop ); } } } private void gatherExcludes( SendTable table, Set<String> excluding ) { for (final SendProp prop : table.getProps()) { if ((SP_Flags.SP_Exclude.getId() & prop.getFlags()) > 0) { excluding.add( prop.getDtName() + prop.getName() ); } else if (SP_Type.SP_DataTable == prop.getType()) { gatherExcludes( sendTables.get( prop.getDtName() ), excluding ); } } } public int getClassBits() { return classBits; } public Entity getEntity( int id ) { return entities.get( id ); } public EntityClass getEntityClass( int id ) { return entityClasses.get( id ); } public FlatSendTable getFlatSendTable( String dtName ) { return flatSendTables.get( dtName ); } public CSVCMsg_GameEventList getGameEventList() { return gameEventList; } public String getHeroForPlayer( String player ) { return playedHeroes.get( player ); } public int getProtocolVersion() { return protocolVersion; } public Collection<SendTable> getSendTables() { return sendTables.values(); } public StringTable getStringTable( int tableId ) { final Iterator<String> iter = stringTables.keySet().iterator(); String key = iter.next(); while (tableId > 0) { key = iter.next(); tableId--; } return stringTables.get( key ); } public StringTable getStringTable( String key ) { return stringTables.get( key ); } public LinkedHashMap<String, StringTable> getStringTables() { return stringTables; } /** * Dota2 game time is counted in "ticks". A wallclock minute * has 30 ticks, therefore a tick occurs roughly every 33ms. * This method converts a tick number in the number of miliseconds * passed since game start time. * @param tick * @return */ public long getTickAsMs( int tick ) { return (long) (tick * 1000 * tickInterval); } public float getTickInterval() { return tickInterval; } public boolean hasEntity( int id ) { return entities.containsKey( id ); } public void setEntity( int id, Entity entity ) { entities.put( id, entity ); } public void setGameEventList( CSVCMsg_GameEventList e ) { gameEventList = e; } public void setPlayerInfo( String playerName, String heroName ) { playedHeroes.put( playerName, heroName ); } public void setTickInterval( float tickInterval ) { this.tickInterval = tickInterval; } }