package de.lighti.parsing;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.lighti.DefaultGameEventListener;
import de.lighti.DotaPlay;
import de.lighti.model.AppState;
import de.lighti.model.Entity;
import de.lighti.model.Property;
import de.lighti.model.game.Hero;
import de.lighti.model.game.Player;
import de.lighti.model.state.ParseState;
/**
* The game keeps re-creating an entity CDOTA_PlayerResource throughout a match, hence it's entity id is. This volatile
* more or less meaningless. Instead, this entity holds various vectors whose fields have to be tracked
* and mapped to our own Player objects.
*
* @author Tobias Mahlmann
*
*/
public class PlayersListener extends DefaultGameEventListener {
private final AppState state;
private final Pattern playerPattern;
private final Map<String, Player> playerBuffer;
public PlayersListener( AppState state ) {
super();
this.state = state;
playerPattern = Pattern.compile( "\\.[0-9][0-9][0-9][0-9]$" );
playerBuffer = new HashMap<String, Player>();
}
@Override
public void entityCreated( long tickMs, Entity e ) {
if (e.getEntityClass().getName().equals( "CDOTA_PlayerResource" )) {
for (final Property<?> p : e.getProperties()) {
handleWorldVar( tickMs, p.getName(), p.getValue() );
}
}
}
@Override
public <T> void entityUpdated( long tickMs, Entity e, String name, T oldValue ) {
if (e.getEntityClass().getName().equals( "CDOTA_PlayerResource" )) {
handleWorldVar( tickMs, name, e.getProperty( name ).getValue() );
}
}
private void handleWorldVar( long time, String name, Object value ) {
final Matcher m = playerPattern.matcher( name );
if (m.find()) {
final String id = name.substring( name.lastIndexOf( "." ) + 1 );
final String valueName = name.substring( 0, name.lastIndexOf( "." ) );
Player p = playerBuffer.get( id );
if (p == null) {
playerBuffer.put( id, new Player( Integer.valueOf( id ) ) );
p = playerBuffer.get( id );
}
switch (valueName) {
case "m_iszPlayerNames":
p.setName( (String) value );
break;
case "m_iTotalEarnedGold":
p.setTotalEarnedGold( time, (Integer) value );
break;
case "m_iTotalEarnedXP":
p.setTotalXP( time, (Integer) value );
break;
case "m_hSelectedHero":
p.setHero( state.getHero( (Integer) value & 0x7FF ) );
break;
case "m_iPlayerTeams":
p.setRadiant( (Integer) value == 2 ); //2 = Radiant, 3 = Dire, 5 = Spectator
break;
case "m_iDeaths":
//As PlayerResource is recreated over and over again, we will get an update value = 0, and value = <the actual value> one after another.
//Since deaths can't decrease, we simply check if the value we get is higher than what we've stored
if (p.getDeaths( time ).size() < (Integer) value) {
p.addDeath( time );
}
break;
default:
Map<String, Object> tickMap = state.gameEventsPerMs.get( DotaPlay.getTickMs() );
if (tickMap == null) {
tickMap = new HashMap<String, Object>();
state.gameEventsPerMs.put( DotaPlay.getTickMs(), tickMap );
}
tickMap.put( name, value );
state.addPlayerVariable( valueName );
break;
}
}
}
@Override
public void parseComplete( long tickMs, ParseState state ) {
for (final Player p : playerBuffer.values()) {
final Hero h = p.getHero();
if (h != null) {
//Propagate the player's deaths into the hero as only the heros stored where it died
for (final Long l : p.getDeaths()) {
final int x = h.getX( l );
final int y = h.getY( l );
h.addDeath( l, x, y );
}
this.state.addPlayer( p );
}
}
}
}