// Simply tests that we can read the outer structure of a real match func TestParseRealMatches(t *testing.T) { assert := assert.New(t) // Important: make sure to include the most recent test last. These generate fixtures to easily // detect diffs in various data structures upon commit, and the latest replay should always be // last to provide a most up-to-date baseline. scenarios := []struct { matchId string replayUrl string expectCombatLogDamage int32 expectCombatLogHealing int32 expectCombatLogDeaths int32 expectCombatLogEvents int32 expectUnitOrderEvents int32 }{ { matchId: "1734886116", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1734886116.dem", expectCombatLogDamage: 1048805, expectCombatLogHealing: 25089, expectCombatLogDeaths: 1447, expectCombatLogEvents: 42307, expectUnitOrderEvents: 59775, }, { matchId: "1731962898", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1731962898.dem", expectCombatLogDamage: 415560, expectCombatLogHealing: 20018, expectCombatLogDeaths: 690, expectCombatLogEvents: 24296, expectUnitOrderEvents: 27525, }, { matchId: "1605340040", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1605340040.dem", expectCombatLogDamage: 522367, expectCombatLogHealing: 31721, expectCombatLogDeaths: 795, expectCombatLogEvents: 21116, expectUnitOrderEvents: 40669, }, { matchId: "1560289528", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1560289528.dem", expectCombatLogDamage: 1180993, expectCombatLogHealing: 57511, expectCombatLogDeaths: 1449, expectCombatLogEvents: 49146, expectUnitOrderEvents: 63387, }, { matchId: "1560294294", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1560294294.dem", expectCombatLogDamage: 768154, expectCombatLogHealing: 11565, expectCombatLogDeaths: 954, expectCombatLogEvents: 24535, expectUnitOrderEvents: 30657, }, { matchId: "1560315800", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1560315800.dem", expectCombatLogDamage: 1332418, expectCombatLogHealing: 57874, expectCombatLogDeaths: 1645, expectCombatLogEvents: 51288, expectUnitOrderEvents: 63992, }, { matchId: "1582611189", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1582611189.dem", expectCombatLogDamage: 599388, expectCombatLogHealing: 28576, expectCombatLogDeaths: 930, expectCombatLogEvents: 23800, expectUnitOrderEvents: 40237, }, { matchId: "1648457986", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1648457986.dem", expectCombatLogDamage: 224773, expectCombatLogHealing: 5914, expectCombatLogDeaths: 466, expectCombatLogEvents: 10170, expectUnitOrderEvents: 17822, }, { matchId: "1712853372", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1712853372.dem", expectCombatLogDamage: 671297, expectCombatLogHealing: 23467, expectCombatLogDeaths: 1099, expectCombatLogEvents: 30381, expectUnitOrderEvents: 48107, }, { matchId: "1716444111", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1716444111.dem", expectCombatLogDamage: 1398735, expectCombatLogHealing: 49659, expectCombatLogDeaths: 2169, expectCombatLogEvents: 76921, expectUnitOrderEvents: 48822, }, } for _, s := range scenarios { buf := mustGetReplayData(s.matchId, s.replayUrl) parser, err := NewParser(buf) if err != nil { t.Errorf("unable to instantiate parser: %s", err) continue } gotCombatLogDamage := int32(0) gotCombatLogHealing := int32(0) gotCombatLogDeaths := int32(0) gotCombatLogEvents := int32(0) gotUnitOrderEvents := int32(0) parser.Callbacks.OnCDOTAUserMsg_SpectatorPlayerUnitOrders(func(m *dota.CDOTAUserMsg_SpectatorPlayerUnitOrders) error { gotUnitOrderEvents += 1 return nil }) parser.OnGameEvent("dota_combatlog", func(m *GameEvent) error { gotCombatLogEvents += 1 t, err := m.GetInt32("type") assert.Nil(err) switch dota.DOTA_COMBATLOG_TYPES(t) { case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_DAMAGE: v, err := m.GetInt32("value") assert.Nil(err) gotCombatLogDamage += v case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_DEATH: gotCombatLogDeaths += 1 case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_HEAL: v, err := m.GetInt32("value") assert.Nil(err) gotCombatLogHealing += v } return nil }) if fixturesMode { // Writes out the source_1_legacy_game_events_list.json fixture so that we can identify changes to schema. parser.Callbacks.OnCMsgSource1LegacyGameEventList(func(m *dota.CMsgSource1LegacyGameEventList) error { _dump_fixture("legacy_game_events/list_latest.json", _json_marshal(m)) return nil }) } err = parser.Start() assert.Nil(err, s.matchId) if fixturesMode { // Use this to write out instancebaseline fixtures t, _ := parser.StringTables.GetTableByName("instancebaseline") for _, i := range t.Items { classId, _ := atoi32(i.Key) className := parser.ClassInfo[classId] _dump_fixture(_sprintf("instancebaseline/%s_%s.rawbuf", className, s.matchId), i.Value) } } assert.Equal(s.expectCombatLogDamage, gotCombatLogDamage, s.matchId) assert.Equal(s.expectCombatLogHealing, gotCombatLogHealing, s.matchId) assert.Equal(s.expectCombatLogDeaths, gotCombatLogDeaths, s.matchId) assert.Equal(s.expectCombatLogEvents, gotCombatLogEvents, s.matchId) assert.Equal(s.expectUnitOrderEvents, gotUnitOrderEvents, s.matchId) } }
func v1ParseLog(w http.ResponseWriter, r *http.Request) { switch r.Method { case "POST": var demoFile []byte if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data;") { form, _ := r.MultipartReader() part, _ := form.NextPart() demoFile, _ = ioutil.ReadAll(part) } else { demoFile, _ = ioutil.ReadAll(r.Body) } var gameTime time.Duration var preGameStartTime time.Duration var gameStartTime time.Duration var gameEndTime time.Duration var iPlayerResources map[int]time.Duration iPlayerResources = make(map[int]time.Duration) var iTeamData map[int]time.Duration iTeamData = make(map[int]time.Duration) var iHeroUnits map[int]time.Duration iHeroUnits = make(map[int]time.Duration) var owners map[uint32]int32 owners = make(map[uint32]int32) var heroes map[int]int32 heroes = make(map[int]int32) p, _ := manta.NewParser(demoFile) p.OnPacketEntity(func(pe *manta.PacketEntity, pet manta.EntityEventType) error { if pe.ClassName == "CDOTAGamerulesProxy" { if v, ok := pe.FetchFloat32("CDOTAGamerules.m_fGameTime"); ok { gameTime = time.Duration(v) * time.Second } if v, ok := pe.FetchFloat32("CDOTAGamerules.m_flPreGameStartTime"); ok { preGameStartTime = time.Duration(v) * time.Second } if v, ok := pe.FetchFloat32("CDOTAGamerules.m_flGameStartTime"); ok { gameStartTime = time.Duration(v) * time.Second } if v, ok := pe.FetchFloat32("CDOTAGamerules.m_flGameEndTime"); ok { gameEndTime = time.Duration(v) * time.Second } } if pe.ClassName == "CDOTA_PlayerResource" { for i := 0; i < 10; i++ { lastInterval := iPlayerResources[i] if gameTime.Seconds() > (lastInterval.Seconds() + 10) { iPlayerResources[i] = gameTime heroID, _ := pe.FetchInt32("m_vecPlayerTeamData.000" + strconv.Itoa(i) + ".m_nSelectedHeroID") if heroID > 0 { heroes[i] = heroID level, _ := pe.FetchInt32("m_vecPlayerTeamData.000" + strconv.Itoa(i) + ".m_iLevel") assists, _ := pe.FetchInt32("m_vecPlayerTeamData.000" + strconv.Itoa(i) + ".m_iAssists") deaths, _ := pe.FetchInt32("m_vecPlayerTeamData.000" + strconv.Itoa(i) + ".m_iDeaths") kills, _ := pe.FetchInt32("m_vecPlayerTeamData.000" + strconv.Itoa(i) + ".m_iKills") fmt.Fprintf(w, "{\"type\":2,\"time\":\"%s\",\"hero\":%d,\"level\":%d,\"kills\":%d,\"deaths\":%d,\"assists\":%d},", formatDuration(gameTime), heroID, level, kills, deaths, assists) } } } } if pe.ClassName == "CDOTA_DataDire" || pe.ClassName == "CDOTA_DataRadiant" { for i := 0; i < 5; i++ { var playerID = i if pe.ClassName == "CDOTA_DataDire" { playerID += 5 } lastInterval := iTeamData[int(playerID)] if gameTime.Seconds() > (lastInterval.Seconds() + 5) { iTeamData[int(playerID)] = gameTime if heroID, ok := heroes[int(playerID)]; ok { healing, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_fHealing") stuns, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_fStuns") buybackCooldown, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_flBuybackCooldownTime") creepGold, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iCreepKillGold") denies, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iDenyCount") heroGold, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iHeroKillGold") incomeGold, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iIncomeGold") lastHits, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iLastHitCount") missCount, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iMissCount") nearbyCreepCount, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iNearbyCreepDeathCount") reliableGold, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iReliableGold") unreliableGold, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iUnreliableGold") sharedGold, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iSharedGold") gold, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iTotalEarnedGold") xp, _ := pe.FetchInt32("m_vecDataTeam.000" + strconv.Itoa(i) + ".m_iTotalEarnedXP") fmt.Fprintf(w, "{\"type\":3,\"time\":\"%s\",\"hero\":%d,\"healing\":%d,\"stuns\":%d,\"buyback\":%d,\"lasthits\":%d,\"denies\":%d,\"misses\":%d,\"nearby_creeps\":%d,\"gold_creeps\":%d,\"gold_heroes\":%d,\"gold_income\":%d,\"gold_reliable\":%d,\"gold_unreliable\":%d,\"gold_shared\":%d,\"gold\":%d,\"xp\":%d},", formatDuration(gameTime), heroID, healing, stuns, buybackCooldown, lastHits, denies, missCount, nearbyCreepCount, creepGold, heroGold, incomeGold, reliableGold, unreliableGold, sharedGold, gold, xp) } } } } if strings.HasPrefix(pe.ClassName, "CDOTA_Unit_Hero") { playerID, _ := pe.FetchInt32("m_iPlayerID") ownerID, _ := pe.Fetch("m_hOwnerEntity") lastInterval := iHeroUnits[int(playerID)] if gameTime.Seconds() > (lastInterval.Seconds() + 0) { iHeroUnits[int(playerID)] = gameTime if heroID, ok := heroes[int(playerID)]; ok { owners[ownerID.(uint32)] = heroID x, _ := pe.Fetch("CBodyComponentBaseAnimatingOverlay.m_cellX") y, _ := pe.Fetch("CBodyComponentBaseAnimatingOverlay.m_cellY") fmt.Fprintf(w, "{\"type\":4,\"time\":\"%s\",\"hero\":%d,\"x\":%d,\"y\":%d},", formatDuration(gameTime), heroID, x, y) } } } if pe.ClassName == "CDOTA_NPC_Observer_Ward" && pet == manta.EntityEventType_Create { x, _ := pe.Fetch("CBodyComponentBaseAnimatingOverlay.m_cellX") y, _ := pe.Fetch("CBodyComponentBaseAnimatingOverlay.m_cellY") ownerID, _ := pe.Fetch("m_hOwnerEntity") heroID := owners[ownerID.(uint32)] fmt.Fprintf(w, "{\"type\":5,\"time\":\"%s\",\"x\":%d,\"y\":%d,\"hero\":%d},", formatDuration(gameTime), x, y, heroID) } if pe.ClassName == "CDOTA_NPC_Observer_Ward_TrueSight" && pet == manta.EntityEventType_Create { x, _ := pe.Fetch("CBodyComponentBaseAnimatingOverlay.m_cellX") y, _ := pe.Fetch("CBodyComponentBaseAnimatingOverlay.m_cellY") ownerID, _ := pe.Fetch("m_hOwnerEntity") heroID := owners[ownerID.(uint32)] fmt.Fprintf(w, "{\"type\":6,\"time\":\"%s\",\"x\":%d,\"y\":%d,\"hero\":%d},", formatDuration(gameTime), x, y, heroID) } return nil }) p.Callbacks.OnCUserMessageSayText2(func(m *dota.CUserMessageSayText2) error { fmt.Fprintf(w, "{\"type\":7,\"time\":\"%s\",\"player\":\"%s\",\"said\":\"%s\"},", formatDuration(gameTime), m.GetParam1(), m.GetParam2()) return nil }) p.Callbacks.OnCDOTAUserMsg_ChatEvent(func(e *dota.CDOTAUserMsg_ChatEvent) error { t := e.GetType() switch dota.DOTA_CHAT_MESSAGE(t) { case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_HERO_KILL: // covered by combat log case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_HERO_DENY: // covered by combat log case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_BARRACKS_KILL: //They go in incremental powers of 2, starting by the Dire side to the Dire Side, Bottom to Top, Melee to Ranged //ex: Bottom Melee Dire Rax = 1 and Top Ranged Radiant Rax = 2048. fmt.Fprintf(w, "{\"type\":8,\"time\":\"%s\",\"barracks\":%d,\"player\":%d},", formatDuration(gameTime), e.GetValue(), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_TOWER_KILL: //player1 = slot of player who killed tower (-1 if nonplayer) //value (2/3 radiant/dire killed tower, recently 0/1?) fmt.Fprintf(w, "{\"type\":9,\"time\":\"%s\",\"tower\":%d,\"player\":%d},", formatDuration(gameTime), e.GetValue(), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_TOWER_DENY: fmt.Fprintf(w, "{\"type\":10,\"time\":\"%s\",\"tower\":%d,\"player\":%d},", formatDuration(gameTime), e.GetValue(), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_EFFIGY_KILL: fmt.Fprintf(w, "{\"type\":11,\"time\":\"%s\",\"player\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_FIRSTBLOOD: fmt.Fprintf(w, "{\"type\":12,\"time\":\"%s\",\"player\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_STREAK_KILL: // covered by combat log case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_BUYBACK: // covered by combat log case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_ROSHAN_KILL: //player1 = team that killed roshan? (2/3) fmt.Fprintf(w, "{\"type\":13,\"time\":\"%s\",\"team\":%d,\"value\":%d},", formatDuration(gameTime), e.GetPlayerid_1(), e.GetValue()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_AEGIS: //player1 = slot who picked up/denied/stole aegis fmt.Fprintf(w, "{\"type\":14,\"time\":\"%s\",\"player\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_AEGIS_STOLEN: fmt.Fprintf(w, "{\"type\":15,\"time\":\"%s\",\"player\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_DENIED_AEGIS: fmt.Fprintf(w, "{\"type\":16,\"time\":\"%s\",\"player\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_COURIER_LOST: //player1 = team that lost courier (2/3) fmt.Fprintf(w, "{\"type\":17,\"time\":\"%s\",\"team\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_COURIER_RESPAWNED: fmt.Fprintf(w, "{\"type\":18,\"time\":\"%s\",\"team\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_GLYPH_USED: // team that used glyph (2/3, or 0/1) ? fmt.Fprintf(w, "{\"type\":19,\"time\":\"%s\",\"team\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_ITEM_PURCHASE: // Not usefull dose not include all PURCHASES case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_RUNE_PICKUP: fmt.Fprintf(w, "{\"type\":20,\"time\":\"%s\",\"player\":%d,\"rune\":%d},", formatDuration(gameTime), e.GetPlayerid_1(), e.GetValue()) //"0": "Double Damage", "1": "Haste", "2": "Illusion", "3": "Invisibility", "4": "Regeneration", "4": "Bounty" case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_RUNE_BOTTLE: fmt.Fprintf(w, "{\"type\":21,\"time\":\"%s\",\"player\":%d,\"rune\":%d},", formatDuration(gameTime), e.GetPlayerid_1(), e.GetValue()) //"0": "Double Damage", "1": "Haste", "2": "Illusion", "3": "Invisibility", "4": "Regeneration", "4": "Bounty" case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_SUPER_CREEPS: fmt.Fprintf(w, "{\"type\":22,\"time\":\"%s\",\"team\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_CONNECT: case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_DISCONNECT: case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_RECONNECT: case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_PLAYER_LEFT: case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_SAFE_TO_LEAVE: // Is this needed? case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_PAUSED: case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_UNPAUSED: // Maybe at some point? case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_INTHEBAG: fmt.Fprintf(w, "{\"type\":23,\"time\":\"%s\",\"player\":%d},", formatDuration(gameTime), e.GetPlayerid_1()) case dota.DOTA_CHAT_MESSAGE_CHAT_MESSAGE_TAUNT: // Is this needed? } return nil }) p.Callbacks.OnCMsgDOTACombatLogEntry(func(m *dota.CMsgDOTACombatLogEntry) error { t := m.GetType() switch dota.DOTA_COMBATLOG_TYPES(t) { case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_DAMAGE: iat := m.GetIsAttackerIllusion() iah := m.GetIsAttackerHero() iti := m.GetIsTargetIllusion() ith := m.GetIsTargetHero() ivr := m.GetIsVisibleRadiant() ivd := m.GetIsVisibleDire() itb := m.GetIsTargetBuilding() target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) targetSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetSourceName())) attacker, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetAttackerName())) damageSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetDamageSourceName())) inflictor, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetInflictorName())) value := m.GetValue() fmt.Fprintf(w, "{\"type\":24,\"time\":\"%s\",\"iat\":%t,\"iah\":%t,\"iti\":%t,\"ith\":%t,\"ivr\":%t,\"ivd\":%t,\"itb\":%t,\"attacker\":\"%s\",\"target\":\"%s\",\"target_source\":\"%s\",\"damage_source\":\"%s\",\"inflictor\":\"%s\",\"value\":%d},", formatDuration(gameTime), iat, iah, iti, ith, ivr, ivd, itb, attacker, target, targetSource, damageSource, inflictor, value) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_HEAL: iat := m.GetIsAttackerIllusion() iah := m.GetIsAttackerHero() iti := m.GetIsTargetIllusion() ith := m.GetIsTargetHero() ivr := m.GetIsVisibleRadiant() ivd := m.GetIsVisibleDire() itb := m.GetIsTargetBuilding() target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) targetSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetSourceName())) attacker, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetAttackerName())) damageSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetDamageSourceName())) inflictor, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetInflictorName())) value := m.GetValue() fmt.Fprintf(w, "{\"type\":25,\"time\":\"%s\",\"iat\":%t,\"iah\":%t,\"iti\":%t,\"ith\":%t,\"ivr\":%t,\"ivd\":%t,\"itb\":%t,\"attacker\":\"%s\",\"target\":\"%s\",\"target_source\":\"%s\",\"damage_source\":\"%s\",\"inflictor\":\"%s\",\"value\":%d},", formatDuration(gameTime), iat, iah, iti, ith, ivr, ivd, itb, attacker, target, targetSource, damageSource, inflictor, value) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_DEATH: iat := m.GetIsAttackerIllusion() iah := m.GetIsAttackerHero() iti := m.GetIsTargetIllusion() ith := m.GetIsTargetHero() ivr := m.GetIsVisibleRadiant() ivd := m.GetIsVisibleDire() itb := m.GetIsTargetBuilding() target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) targetSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetSourceName())) attacker, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetAttackerName())) damageSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetDamageSourceName())) fmt.Fprintf(w, "{\"type\":26,\"time\":\"%s\",\"iat\":%t,\"iah\":%t,\"iti\":%t,\"ith\":%t,\"ivr\":%t,\"ivd\":%t,\"itb\":%t,\"attacker\":\"%s\",\"target\":\"%s\",\"target_source\":\"%s\",\"damage_source\":\"%s\"},", formatDuration(gameTime), iat, iah, iti, ith, ivr, ivd, itb, attacker, target, targetSource, damageSource) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_ABILITY: iat := m.GetIsAttackerIllusion() iah := m.GetIsAttackerHero() ivr := m.GetIsVisibleRadiant() ivd := m.GetIsVisibleDire() attacker, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetAttackerName())) inflictor, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetInflictorName())) level := m.GetAbilityLevel() fmt.Fprintf(w, "{\"type\":27,\"time\":\"%s\",\"iat\":%t,\"iah\":%t,\"ivr\":%t,\"ivd\":%t,\"attacker\":\"%s\",\"inflictor\":\"%s\",\"ability_level\":%d},", formatDuration(gameTime), iat, iah, ivr, ivd, attacker, inflictor, level) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_ITEM: attacker, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetAttackerName())) inflictor, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetInflictorName())) level := m.GetAbilityLevel() fmt.Fprintf(w, "{\"type\":28,\"time\":\"%s\",\"player\":\"%s\",\"item\":\"%s\",\"level\":%d},", formatDuration(gameTime), attacker, inflictor, level) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_PURCHASE: target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) item, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetValue())) fmt.Fprintf(w, "{\"type\":29,\"time\":\"%s\",\"player\":\"%s\",\"item\":\"%s\"},", formatDuration(gameTime), target, item) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_BUYBACK: source, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetValue())) fmt.Fprintf(w, "{\"type\":30,\"time\":\"%s\",\"player\":\"%s\"},", formatDuration(gameTime), source) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_GOLD: amount := m.GetValue() reason := m.GetGoldReason() // "0": "Other", "1":"Death", "2":"Buyback", "5": "Abandon", "6": "Sell", "11":"Structure", "12":"Hero", "13":"Creep", "14": "Roshan", "15":"Courier" target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) targetSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetSourceName())) fmt.Fprintf(w, "{\"type\":31,\"time\":\"%s\",\"target\":\"%s\",\"targetsource\":\"%s\",\"reason\":%d,\"amount\":%d},", formatDuration(gameTime), target, targetSource, reason, amount) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_XP: amount := m.GetValue() reason := m.GetXpReason() //"0": "Other", "1": "Hero", "2": "Creep", "3": "Roshan" target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) fmt.Fprintf(w, "{\"type\":32,\"time\":\"%s\",\"target\":\"%s\",\"reason\":%d,\"amount\":%d},", formatDuration(gameTime), target, reason, amount) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_TEAM_BUILDING_KILL: target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) value := m.GetValue() fmt.Fprintf(w, "{\"type\":33,\"time\":\"%s\",\"target\":\"%s\",\"value\":%d},", formatDuration(gameTime), target, value) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_NEUTRAL_CAMP_STACK: // Not used? case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_PICKUP_RUNE: // use Chat Event case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_FIRST_BLOOD: // use Chat Event case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_PLAYERSTATS: case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_GAME_STATE: case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_LOCATION: // Useless... case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_MULTIKILL: target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) targetSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetSourceName())) attacker, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetAttackerName())) value := m.GetValue() //"2": "Double Kill", "3": "Triple Kill", "4": "Ultra Kill", "5": "Rampage" fmt.Fprintf(w, "{\"type\":34,\"time\":\"%s\",\"attacker\":\"%s\",\"target\":\"%s\",\"target_source\":\"%s\",\"value\":%d},", formatDuration(gameTime), attacker, target, targetSource, value) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_KILLSTREAK: target, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetName())) targetSource, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetTargetSourceName())) attacker, _ := p.LookupStringByIndex("CombatLogNames", int32(m.GetAttackerName())) value := m.GetValue() //"3": "Killing Spree", "4": "Dominating","5": "Mega Kill", "6": "Unstoppable", "7": "Wicked Sick", "8": "Monster Kill", "9": "Godlike", "10": "Beyond Godlike" fmt.Fprintf(w, "{\"type\":35,\"time\":\"%s\",\"attacker\":\"%s\",\"target\":\"%s\",\"target_source\":\"%s\",\"value\":%d},", formatDuration(gameTime), attacker, target, targetSource, value) } return nil }) start := time.Now().UTC() fmt.Fprintf(w, "[{\"type\":0,\"version\":2,\"date\":\"%s\"},", start.Format(time.RFC1123Z)) p.Start() elapsed := time.Since(start) fmt.Fprintf(w, "{\"type\":1,\"elapsed\":\"%s\",\"pregame_start\":\"%s\",\"game_start\":\"%s\",\"game_end\":\"%s\"}]", formatDuration(elapsed), formatDuration(preGameStartTime), formatDuration(gameStartTime), formatDuration(gameEndTime)) default: http.Error(w, "Post the replay file you wish to parse.", http.StatusMethodNotAllowed) return } }
func (ge *GameEvent) Type() dota.DOTA_COMBATLOG_TYPES { return dota.DOTA_COMBATLOG_TYPES(ge.m.GetKeys()[0].GetValByte()) }
func (s testScenario) test(t *testing.T) { assert := assert.New(t) if s.debugTick == 0 { debugLevel = s.debugLevel } defer func() { debugLevel = 0 }() buf := mustGetReplayData(s.matchId, s.replayUrl) parser, err := NewParser(buf) if err != nil { t.Errorf("unable to instantiate parser: %s", err) return } if s.skipPacketEntities { parser.ProcessPacketEntities = false } gotFileInfo := false gotCombatLogDamage := int32(0) gotCombatLogHealing := int32(0) gotCombatLogDeaths := int32(0) gotCombatLogEvents := int32(0) gotUnitOrderEvents := int32(0) gotEntityEvents := int32(0) gotPlayer6Name := "<Missing>" gotPlayer6Steamid := uint64(0) gotHeroEntityMana := float32(0.0) if s.debugTick > 0 { parser.Callbacks.OnCNETMsg_Tick(func(m *dota.CNETMsg_Tick) error { if parser.Tick >= s.debugTick { debugLevel = s.debugLevel } return nil }) } parser.Callbacks.OnCDOTAUserMsg_SpectatorPlayerUnitOrders(func(m *dota.CDOTAUserMsg_SpectatorPlayerUnitOrders) error { gotUnitOrderEvents += 1 return nil }) parser.Callbacks.OnCDemoFileInfo(func(m *dota.CDemoFileInfo) error { gotFileInfo = true return nil }) parser.OnPacketEntity(func(pe *PacketEntity, pet EntityEventType) error { gotEntityEvents += 1 if pe.ClassName == s.expectHeroEntityName { if v, ok := pe.FetchFloat32("m_flMaxMana"); ok { gotHeroEntityMana = v } } if pe.ClassName == "CDOTA_PlayerResource" { if v, ok := pe.FetchString("m_vecPlayerData.0006.m_iszPlayerName"); ok { gotPlayer6Name = v } else if v, ok := pe.FetchString("m_iszPlayerNames.0006"); ok { gotPlayer6Name = v } if v, ok := pe.FetchUint64("m_vecPlayerData.0006.m_iPlayerSteamID"); ok { gotPlayer6Steamid = v } else if v, ok := pe.FetchUint64("m_iPlayerSteamIDs.0006"); ok { gotPlayer6Steamid = v } } return nil }) parser.OnGameEvent("dota_combatlog", func(m *GameEvent) error { gotCombatLogEvents += 1 t, err := m.GetInt32("type") assert.Nil(err) switch dota.DOTA_COMBATLOG_TYPES(t) { case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_DAMAGE: v, err := m.GetInt32("value") assert.Nil(err) gotCombatLogDamage += v case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_DEATH: gotCombatLogDeaths += 1 case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_HEAL: v, err := m.GetInt32("value") assert.Nil(err) gotCombatLogHealing += v } return nil }) err = parser.Start() assert.Nil(err, s.matchId) assert.True(gotFileInfo) assert.Equal(s.expectGameBuild, parser.GameBuild, s.matchId) if s.expectEntityEvents > 0 { assert.Equal(s.expectEntityEvents, gotEntityEvents, s.matchId) } if s.expectCombatLogDamage > 0 { assert.Equal(s.expectCombatLogDamage, gotCombatLogDamage, s.matchId) } if s.expectCombatLogHealing > 0 { assert.Equal(s.expectCombatLogHealing, gotCombatLogHealing, s.matchId) } if s.expectCombatLogDeaths > 0 { assert.Equal(s.expectCombatLogDeaths, gotCombatLogDeaths, s.matchId) } if s.expectCombatLogEvents > 0 { assert.Equal(s.expectCombatLogEvents, gotCombatLogEvents, s.matchId) } if s.expectUnitOrderEvents > 0 { assert.Equal(s.expectUnitOrderEvents, gotUnitOrderEvents, s.matchId) } if s.expectPlayer6Name != "" { assert.Equal(s.expectPlayer6Name, gotPlayer6Name, s.matchId) } if s.expectPlayer6Steamid > 0 { assert.Equal(s.expectPlayer6Steamid, gotPlayer6Steamid, s.matchId) } if s.expectHeroEntityMana > 0.0 { assert.Equal(s.expectHeroEntityMana, gotHeroEntityMana, s.matchId) } }
// Simply tests that we can read the outer structure of a real match func TestParseRealMatches(t *testing.T) { assert := assert.New(t) scenarios := []struct { matchId string replayUrl string expectCombatLogDamage int expectCombatLogHealing int expectCombatLogDeaths int expectCombatLogEvents int expectUnitOrderEvents int }{ { matchId: "1560289528", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1560289528.dem", expectCombatLogDamage: 1180993, expectCombatLogHealing: 57511, expectCombatLogDeaths: 1449, expectCombatLogEvents: 49146, expectUnitOrderEvents: 63387, }, { matchId: "1560294294", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1560294294.dem", expectCombatLogDamage: 768154, expectCombatLogHealing: 11565, expectCombatLogDeaths: 954, expectCombatLogEvents: 24535, expectUnitOrderEvents: 30657, }, { matchId: "1560315800", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1560315800.dem", expectCombatLogDamage: 1332418, expectCombatLogHealing: 57874, expectCombatLogDeaths: 1645, expectCombatLogEvents: 51288, expectUnitOrderEvents: 63992, }, { matchId: "1582611189", replayUrl: "https://s3-us-west-2.amazonaws.com/manta.dotabuff/1582611189.dem", expectCombatLogDamage: 599388, expectCombatLogHealing: 28576, expectCombatLogDeaths: 930, expectCombatLogEvents: 23800, expectUnitOrderEvents: 40237, }, } for _, s := range scenarios { buf := mustGetReplayData(s.matchId, s.replayUrl) parser, err := NewParser(buf) if err != nil { t.Errorf("unable to instantiate parser: %s", err) continue } gotCombatLogDamage := 0 gotCombatLogHealing := 0 gotCombatLogDeaths := 0 gotCombatLogEvents := 0 gotUnitOrderEvents := 0 parser.Callbacks.OnCDOTAUserMsg_SpectatorPlayerUnitOrders(func(m *dota.CDOTAUserMsg_SpectatorPlayerUnitOrders) error { gotUnitOrderEvents += 1 return nil }) parser.GameEvents.OnDotaCombatlog(func(m *GameEventDotaCombatlog) error { gotCombatLogEvents += 1 switch dota.DOTA_COMBATLOG_TYPES(m.Type) { case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_DAMAGE: gotCombatLogDamage += int(m.Value) case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_DEATH: gotCombatLogDeaths += 1 case dota.DOTA_COMBATLOG_TYPES_DOTA_COMBATLOG_HEAL: gotCombatLogHealing += int(m.Value) } return nil }) err = parser.Start() assert.Nil(err, s.matchId) /* Use this to write out instancebaseline fixtures t, _ := parser.stringTables.getTableByName("instancebaseline") for _, i := range t.items { classId, _ := atoi32(i.key) className := parser.classInfo[classId] _dump_fixture(_sprintf("instancebaseline/%s_%s.rawbuf", className), s.matchId, i.value) } */ /* Use this to dump layout of sendtables out := _sprintf("") classIds := make([]int, 0) for classId, _ := range parser.classInfo { classIds = append(classIds, int(classId)) } sort.Ints(classIds) for _, classId := range classIds { className := parser.classInfo[int32(classId)] st := parser.sendTables.tables[className] out += _sprintf("class id=%d name=%s\n", classId, className) out += _sprintf("table id=%d name=%s version=%d\n", st.index, st.name, st.version) for i, p := range st.props { out += _sprintf(" -> prop %d: %s\n", i, p.Describe()) } out += "\n" } */ assert.Equal(s.expectCombatLogDamage, gotCombatLogDamage, s.matchId) assert.Equal(s.expectCombatLogHealing, gotCombatLogHealing, s.matchId) assert.Equal(s.expectCombatLogDeaths, gotCombatLogDeaths, s.matchId) assert.Equal(s.expectCombatLogEvents, gotCombatLogEvents, s.matchId) assert.Equal(s.expectUnitOrderEvents, gotUnitOrderEvents, s.matchId) } }