Example #1
0
// 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)
	}
}
Example #2
0
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
	}
}
Example #3
0
func (ge *GameEvent) Type() dota.DOTA_COMBATLOG_TYPES {
	return dota.DOTA_COMBATLOG_TYPES(ge.m.GetKeys()[0].GetValByte())
}
Example #4
0
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)
	}
}
Example #5
0
// 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)
	}
}