diff --git a/.travis.yml b/.travis.yml index bbaf67357..5f2d294d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ notifications: - howardc93@gmail.com - aqc2109@columbia.edu script: -- docker build -t yasp/parser . +- docker build -t odota/parser . deploy: provider: script skip_cleanup: true diff --git a/Dockerfile b/Dockerfile index 5aacdf0d3..be39eadb0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,4 +12,6 @@ WORKDIR /usr/src/parser ADD . /usr/src/parser RUN mvn -q -f /usr/src/parser/pom.xml clean install -U +EXPOSE 5600 + CMD ["java", "-jar", "-Xmx256m", "/usr/src/parser/target/stats-0.1.0.jar", "5600"] \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7463118b0..94d88ee88 100644 --- a/pom.xml +++ b/pom.xml @@ -40,27 +40,27 @@ - - - + + + com.skadistats clarity 2.1 - + com.google.code.gson gson @@ -71,24 +71,56 @@ org.apache.maven.plugins - maven-shade-plugin - 2.2 + maven-jar-plugin + 3.0.2 + + + + true + yasp.Main + + + ${project.build.finalName}-original + + + + + com.jolira + onejar-maven-plugin + 1.4.4 - package - shade + one-jar - - - - yasp.Main - - - + + ${project.build.finalName}.jar + + + diff --git a/scripts/postbuild.sh b/scripts/postbuild.sh index 1e5dd1dbf..e5ca6fa30 100755 --- a/scripts/postbuild.sh +++ b/scripts/postbuild.sh @@ -2,7 +2,7 @@ if [ -n "$DOCKER_USERNAME" ]; then docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" - docker tag yasp/parser:latest yasp/parser:${TRAVIS_COMMIT} - docker push yasp/parser:${TRAVIS_COMMIT} - docker push yasp/parser:latest -fi \ No newline at end of file + docker tag odota/parser:latest odota/parser:${TRAVIS_COMMIT} + docker push odota/parser:${TRAVIS_COMMIT} + docker push odota/parser:latest +fi diff --git a/scripts/rebuild.sh b/scripts/rebuild.sh index 23995e015..c88e2c829 100644 --- a/scripts/rebuild.sh +++ b/scripts/rebuild.sh @@ -1,5 +1,5 @@ #!/bin/bash +sudo docker build -t odota/parser . sudo docker rm -fv parser -sudo docker build -t yasp/parser . -sudo docker run -d --name parser --net=host yasp/parser \ No newline at end of file +sudo docker run -d --name parser --net=host odota/parser \ No newline at end of file diff --git a/scripts/test_java_parser.sh b/scripts/test.sh similarity index 100% rename from scripts/test_java_parser.sh rename to scripts/test.sh diff --git a/src/main/java/yasp/Main.java b/src/main/java/yasp/Main.java index 531188a70..4f6f9a56c 100644 --- a/src/main/java/yasp/Main.java +++ b/src/main/java/yasp/Main.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InputStream; +import java.io.FileInputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; @@ -9,28 +10,47 @@ import com.sun.net.httpserver.HttpServer; public class Main { - + public static void main(String[] args) throws Exception { - HttpServer server = HttpServer.create(new InetSocketAddress(Integer.valueOf(args.length > 0 ? args[0] : "5600")), 0); - server.createContext("/", new MyHandler()); - server.setExecutor(java.util.concurrent.Executors.newFixedThreadPool(4)); - server.start(); + if (args.length > 0 && args[0].equals("--file")) { System.exit(parseFile(args[1])); } + else if (args.length > 0 && args[0].equals("--")) { System.exit(parseStream(System.in, System.out)); } + else { startServer(args); } + } + + public static void startServer(String[] args) throws Exception { + HttpServer server = HttpServer.create(new InetSocketAddress(Integer.valueOf(args.length > 0 ? args[0] : "5600")), 0); + server.createContext("/", new MyHandler()); + server.setExecutor(java.util.concurrent.Executors.newFixedThreadPool(4)); + server.start(); + } + + private static int parseStream(InputStream is, OutputStream os) throws IOException { + try { + new Parse(is, os); + } + catch (Exception e) + { + e.printStackTrace(); + return -1; + } + finally { + is.close(); + os.close(); + } + + return 0; } + private static int parseFile(String replayFile) throws Exception { + System.out.print(String.format("Parsing file %s", replayFile)); + return parseStream(new FileInputStream(replayFile), System.out); + } + static class MyHandler implements HttpHandler { - @Override - public void handle(HttpExchange t) throws IOException { - t.sendResponseHeaders(200, 0); - InputStream is = t.getRequestBody(); - OutputStream os = t.getResponseBody(); - try { - new Parse(is, os); - } - catch (Exception e) - { - e.printStackTrace(); - } - os.close(); - } + @Override + public void handle(HttpExchange t) throws IOException { + t.sendResponseHeaders(200, 0); + parseStream(t.getRequestBody(), t.getResponseBody()); + } } } diff --git a/src/main/java/yasp/Parse.java b/src/main/java/yasp/Parse.java index faaa121f6..ababb6e98 100644 --- a/src/main/java/yasp/Parse.java +++ b/src/main/java/yasp/Parse.java @@ -33,9 +33,13 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Iterator; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import yasp.processors.warding.OnWardExpired; +import yasp.processors.warding.OnWardPlaced; +import yasp.processors.warding.OnWardKilled; public class Parse { private class Entry { @@ -96,6 +100,7 @@ public Entry(Integer time) { this.time = time; } } + float INTERVAL = 1; float nextInterval = 0; Integer time = 0; @@ -106,7 +111,8 @@ public Entry(Integer time) { private Gson g = new Gson(); HashMap name_to_slot = new HashMap(); HashMap slot_to_playerslot = new HashMap(); - HashMap cosmeticsMap = new HashMap(); + HashMap steamid_to_playerslot = new HashMap(); + HashMap cosmeticsMap = new HashMap(); InputStream is = null; OutputStream os = null; @@ -120,7 +126,6 @@ public Parse(InputStream input, OutputStream output) throws IOException System.err.format("total time taken: %s\n", (tMatch) / 1000.0); } - public void output(Entry e) { try { this.os.write((g.toJson(e) + "\n").getBytes()); @@ -292,17 +297,26 @@ public void onCombatLogEntry(Context ctx, CombatLogEntry cle) { @OnEntityEntered public void onEntityEntered(Context ctx, Entity e) { - processEntity(ctx, e, false); - } - - @OnEntityLeft - public void onEntityLeft(Context ctx, Entity e) { - processEntity(ctx, e, true); + processEntity(ctx, e); } - + @UsesEntities @OnTickStart public void onTickStart(Context ctx, boolean synthetic) { + /* + Iterator cosmetics = ctx.getProcessor(Entities.class).getAllByDtName("CDOTAWearableItem"); + while ( cosmetics.hasNext() ) + { + Entity e = cosmetics.next(); + Integer accountId = getEntityProperty(e, "m_iAccountID", null); + Integer itemDefinitionIndex = getEntityProperty(e, "m_iItemDefinitionIndex", null); + if (itemDefinitionIndex == 7559) + { + System.err.format("%s,%s\n", accountId, itemDefinitionIndex); + } + } + */ + //TODO check engine to decide whether to use s1 or s2 entities //ctx.getEngineType() @@ -365,6 +379,7 @@ public void onTickStart(Context ctx, boolean synthetic) { validIndices[added] = i; added += 1; slot_to_playerslot.put(added, entry.value); + steamid_to_playerslot.put(steamid, entry.value); } } catch(Exception e) @@ -459,6 +474,19 @@ public void onTickStart(Context ctx, boolean synthetic) { } } } + + @OnWardKilled + public void onWardKilled(Context ctx, Entity e, String killerHeroName) { + Entry wardEntry = buildWardEntry(ctx, e); + wardEntry.attackername = killerHeroName; + output(wardEntry); + } + + @OnWardExpired + @OnWardPlaced + public void onWardExistenceChanged(Context ctx, Entity e) { + output(buildWardEntry(ctx, e)); + } public T getEntityProperty(Entity e, String property, Integer idx) { try { @@ -476,51 +504,45 @@ public T getEntityProperty(Entity e, String property, Integer idx) { } } - public void processEntity(Context ctx, Entity e, boolean entityLeft) + public void processEntity(Context ctx, Entity e) { - //CDOTA_NPC_Observer_Ward - //CDOTA_NPC_Observer_Ward_TrueSight - //s1 "DT_DOTA_NPC_Observer_Ward" - //s1 "DT_DOTA_NPC_Observer_Ward_TrueSight" - boolean isObserver = e.getDtClass().getDtName().equals("CDOTA_NPC_Observer_Ward"); - boolean isSentry = e.getDtClass().getDtName().equals("CDOTA_NPC_Observer_Ward_TrueSight"); - if (isObserver || isSentry) { - //System.err.println(e); - Entry entry = new Entry(time); - Integer x = getEntityProperty(e, "CBodyComponent.m_cellX", null); - Integer y = getEntityProperty(e, "CBodyComponent.m_cellY", null); - Integer z = getEntityProperty(e, "CBodyComponent.m_cellZ", null); - Integer[] pos = {x, y}; - entry.x = x; - entry.y = y; - entry.z = z; - if (entityLeft) - { - entry.type = isObserver ? "obs_left" : "sen_left"; - } - else + if (e.getDtClass().getDtName().equals("CDOTAWearableItem")) { + Integer accountId = getEntityProperty(e, "m_iAccountID", null); + Integer itemDefinitionIndex = getEntityProperty(e, "m_iItemDefinitionIndex", null); + //System.err.format("%s,%s\n", accountId, itemDefinitionIndex); + if (accountId > 0) { - entry.type = isObserver ? "obs" : "sen"; + cosmeticsMap.put(itemDefinitionIndex, accountId); } - entry.key = Arrays.toString(pos); - entry.entityleft = entityLeft; - entry.ehandle = e.getHandle(); - //System.err.println(entry.key); - Integer owner = getEntityProperty(e, "m_hOwnerEntity", null); - Entity ownerEntity = ctx.getProcessor(Entities.class).getByHandle(owner); - entry.slot = ownerEntity != null ? (Integer) getEntityProperty(ownerEntity, "m_iPlayerID", null) : null; - //2/3 radiant/dire - //entry.team = e.getProperty("m_iTeamNum"); - output(entry); - } - else if (e.getDtClass().getDtName().equals("CDOTAWearableItem")) { - Integer accountId = getEntityProperty(e, "m_iAccountID", null); - Integer itemDefinitionIndex = getEntityProperty(e, "m_iItemDefinitionIndex", null); - //System.err.format("%s,%s\n", accountId, itemDefinitionIndex); - if (accountId > 0) - { - cosmeticsMap.put(itemDefinitionIndex, accountId); - } + } + } + + private Entry buildWardEntry(Context ctx, Entity e) { + Entry entry = new Entry(time); + boolean isObserver = !e.getDtClass().getDtName().contains("TrueSight"); + Integer x = getEntityProperty(e, "CBodyComponent.m_cellX", null); + Integer y = getEntityProperty(e, "CBodyComponent.m_cellY", null); + Integer z = getEntityProperty(e, "CBodyComponent.m_cellZ", null); + Integer life_state = getEntityProperty(e, "m_lifeState", null); + Integer[] pos = {x, y}; + entry.x = x; + entry.y = y; + entry.z = z; + entry.type = isObserver ? "obs" : "sen"; + entry.entityleft = life_state == 1; + entry.key = Arrays.toString(pos); + entry.ehandle = e.getHandle(); + + if (entry.entityleft) { + entry.type += "_left"; } + + //System.err.println(entry.key); + Integer owner = getEntityProperty(e, "m_hOwnerEntity", null); + Entity ownerEntity = ctx.getProcessor(Entities.class).getByHandle(owner); + entry.slot = ownerEntity != null ? (Integer) getEntityProperty(ownerEntity, "m_iPlayerID", null) : null; + //2/3 radiant/dire + //entry.team = e.getProperty("m_iTeamNum"); + return entry; } } diff --git a/src/main/java/yasp/processors/warding/OnWardExpired.java b/src/main/java/yasp/processors/warding/OnWardExpired.java new file mode 100644 index 000000000..affb3e08d --- /dev/null +++ b/src/main/java/yasp/processors/warding/OnWardExpired.java @@ -0,0 +1,19 @@ +package yasp.processors.warding; + +import java.lang.annotation.Annotation; + +import skadistats.clarity.event.UsagePointMarker; +import skadistats.clarity.event.UsagePointType; +import skadistats.clarity.model.Entity; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(value = ElementType.METHOD) +@UsagePointMarker(value = UsagePointType.EVENT_LISTENER, parameterClasses = { Entity.class }) +public @interface OnWardExpired { +} + diff --git a/src/main/java/yasp/processors/warding/OnWardKilled.java b/src/main/java/yasp/processors/warding/OnWardKilled.java new file mode 100644 index 000000000..bc615ad6e --- /dev/null +++ b/src/main/java/yasp/processors/warding/OnWardKilled.java @@ -0,0 +1,19 @@ +package yasp.processors.warding; + +import java.lang.annotation.Annotation; + +import skadistats.clarity.event.UsagePointMarker; +import skadistats.clarity.event.UsagePointType; +import skadistats.clarity.model.Entity; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(value = ElementType.METHOD) +@UsagePointMarker(value = UsagePointType.EVENT_LISTENER, parameterClasses = { Entity.class, String.class }) +public @interface OnWardKilled { +} + diff --git a/src/main/java/yasp/processors/warding/OnWardPlaced.java b/src/main/java/yasp/processors/warding/OnWardPlaced.java new file mode 100644 index 000000000..0312c8add --- /dev/null +++ b/src/main/java/yasp/processors/warding/OnWardPlaced.java @@ -0,0 +1,19 @@ +package yasp.processors.warding; + +import java.lang.annotation.Annotation; + +import skadistats.clarity.event.UsagePointMarker; +import skadistats.clarity.event.UsagePointType; +import skadistats.clarity.model.Entity; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(value = ElementType.METHOD) +@UsagePointMarker(value = UsagePointType.EVENT_LISTENER, parameterClasses = { Entity.class }) +public @interface OnWardPlaced { +} + diff --git a/src/main/java/yasp/processors/warding/Wards.java b/src/main/java/yasp/processors/warding/Wards.java new file mode 100644 index 000000000..203956b06 --- /dev/null +++ b/src/main/java/yasp/processors/warding/Wards.java @@ -0,0 +1,202 @@ +package yasp.processors.warding; + +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +import skadistats.clarity.event.Event; +import skadistats.clarity.event.EventListener; +import skadistats.clarity.event.Initializer; +import skadistats.clarity.event.Provides; +import skadistats.clarity.model.Entity; +import skadistats.clarity.model.FieldPath; +import skadistats.clarity.model.CombatLogEntry; +import skadistats.clarity.processor.entities.OnEntityCreated; +import skadistats.clarity.processor.entities.OnEntityDeleted; +import skadistats.clarity.processor.entities.OnEntityUpdated; +import skadistats.clarity.processor.entities.UsesEntities; +import skadistats.clarity.processor.gameevents.OnCombatLogEntry; +import skadistats.clarity.processor.reader.OnTickEnd; +import skadistats.clarity.processor.runner.Context; +import skadistats.clarity.wire.common.proto.DotaUserMessages; + +/** + * @author micaelbergeron + */ +@UsesEntities +@Provides({ OnWardKilled.class, OnWardExpired.class, OnWardPlaced.class }) +public class Wards { + + private static final Map WARDS_TARGET_NAME_BY_DT_CLASS; + private static final Set WARDS_DT_CLASSES; + private static final Set WARDS_TARGET_NAMES; + + static { + final String TARGET_NAME_OBSERVER = "npc_dota_observer_wards"; + final String TARGET_NAME_SENTRY = "npc_dota_sentry_wards"; + HashMap target_by_dtclass = new HashMap<>(4); + + target_by_dtclass.put("DT_DOTA_NPC_Observer_Ward", TARGET_NAME_OBSERVER); + target_by_dtclass.put("CDOTA_NPC_Observer_Ward", TARGET_NAME_OBSERVER); + target_by_dtclass.put("DT_DOTA_NPC_Observer_Ward_TrueSight", TARGET_NAME_SENTRY); + target_by_dtclass.put("CDOTA_NPC_Observer_Ward_TrueSight", TARGET_NAME_SENTRY); + + WARDS_TARGET_NAME_BY_DT_CLASS = Collections.unmodifiableMap(target_by_dtclass); + WARDS_DT_CLASSES = Collections.unmodifiableSet(target_by_dtclass.keySet()); + WARDS_TARGET_NAMES = Collections.unmodifiableSet(new HashSet<>(target_by_dtclass.values())); + } + + private final Map lifeStatePaths = new HashMap<>(); + private final Map currentLifeState = new HashMap<>(); + + private final Map> wardKillersByWardClass = new HashMap<>(); + private Queue toProcess = new ArrayDeque<>(); + + private Event evKilled; + private Event evExpired; + private Event evPlaced; + + private class ProcessEntityCommand { + + private final Entity entity; + private final FieldPath fieldPath; + + public ProcessEntityCommand(Entity e, FieldPath p) { + entity = e; + fieldPath = p; + } + } + + @Initializer(OnWardKilled.class) + public void initOnWardKilled(final Context ctx, final EventListener listener) { + evKilled = ctx.createEvent(OnWardKilled.class, Entity.class, String.class); + } + + @Initializer(OnWardExpired.class) + public void initOnWardExpired(final Context ctx, final EventListener listener) { + evExpired = ctx.createEvent(OnWardExpired.class, Entity.class); + } + + @Initializer(OnWardPlaced.class) + public void initOnWardPlaced(final Context ctx, final EventListener listener) { + evPlaced = ctx.createEvent(OnWardPlaced.class, Entity.class); + } + + public Wards() { + WARDS_TARGET_NAMES.forEach((cls) -> { + wardKillersByWardClass.put(cls, new ArrayDeque<>()); + }); + } + + @OnEntityCreated + public void onCreated(Context ctx, Entity e) { + if (!isWard(e)) return; + + FieldPath lifeStatePath; + + clearCachedState(e); + ensureFieldPathForEntityInitialized(e); + if ((lifeStatePath = getFieldPathForEntity(e)) != null) { + processLifeStateChange(e, lifeStatePath); + } + } + + @OnEntityUpdated + public void onUpdated(Context ctx, Entity e, FieldPath[] fieldPaths, int num) { + FieldPath p; + if ((p = getFieldPathForEntity(e)) != null) { + for (int i = 0; i < num; i++) { + if (fieldPaths[i].equals(p)) { + toProcess.add(new ProcessEntityCommand(e, p)); + break; + } + } + } + } + + @OnEntityDeleted + public void onDeleted(Context ctx, Entity e) { + clearCachedState(e); + } + + @OnCombatLogEntry + public void onCombatLogEntry(Context ctx, CombatLogEntry entry) { + if (!isWardDeath(entry)) return; + + String killer; + if ((killer = entry.getDamageSourceName()) != null) { + wardKillersByWardClass.get(entry.getTargetName()).add(killer); + } + } + + @OnTickEnd + public void onTickEnd(Context ctx, boolean synthetic) { + if (!synthetic) return; + + ProcessEntityCommand cmd; + while ((cmd = toProcess.poll()) != null) { + processLifeStateChange(cmd.entity, cmd.fieldPath); + } + } + + private FieldPath getFieldPathForEntity(Entity e) { + return lifeStatePaths.get(e.getDtClass().getClassId()); + } + + private void clearCachedState(Entity e) { + currentLifeState.remove(e.getIndex()); + } + + private void ensureFieldPathForEntityInitialized(Entity e) { + Integer cid = e.getDtClass().getClassId(); + if (!lifeStatePaths.containsKey(cid)) { + lifeStatePaths.put(cid, e.getDtClass().getFieldPathForName("m_lifeState")); + } + } + + private boolean isWard(Entity e) { + return WARDS_DT_CLASSES.contains(e.getDtClass().getDtName()); + } + + private boolean isWardDeath(CombatLogEntry e) { + return e.getType().equals(DotaUserMessages.DOTA_COMBATLOG_TYPES.DOTA_COMBATLOG_DEATH) + && WARDS_TARGET_NAMES.contains(e.getTargetName()); + } + + public void processLifeStateChange(Entity e, FieldPath p) { + int oldState = currentLifeState.containsKey(e.getIndex()) ? currentLifeState.get(e.getIndex()) : 2; + int newState = e.getPropertyForFieldPath(p); + if (oldState != newState) { + switch(newState) { + case 0: + if (evPlaced != null) { + evPlaced.raise(e); + } + break; + case 1: + String killer; + if ((killer = wardKillersByWardClass.get(getWardTargetName(e.getDtClass().getDtName())).poll()) != null) { + if (evKilled != null) { + evKilled.raise(e, killer); + } + } else { + if (evExpired != null) { + evExpired.raise(e); + } + } + break; + } + } + + currentLifeState.put(e.getIndex(), newState); + } + + private String getWardTargetName(String ward_dtclass_name) { + return WARDS_TARGET_NAME_BY_DT_CLASS.get(ward_dtclass_name); + } +} +