Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distinction between bought and picked up weapon #146

Open
BestAwperEver opened this issue Sep 10, 2019 · 19 comments
Open

Distinction between bought and picked up weapon #146

BestAwperEver opened this issue Sep 10, 2019 · 19 comments

Comments

@BestAwperEver
Copy link
Contributor

In my application I'm trying to track all player's purchases. For this purpose I use ItemPickup event, its WeaponTraceable() method and my own internal structure that maps every weapon's ID to its owner:

app.parser.RegisterEventHandler(func(e events.ItemPickup) {
	var weaponTraceable *common.Equipment
	weaponTraceable = e.WeaponTraceable()
	prevOwner, ok := app.weaponOwner[weaponTraceable.UniqueID()]
	if !ok {
		// bought an item
	} else {
		// actually picked it up
		app.weaponOwner[weaponTraceable.UniqueID()] = e.Player
	}
})

This works perfectly for any weapon except flashbang. When a player buys a second flashbang, they already have this weapon and therefore weaponOwner[weaponTraceable.UniqueID()] stores a value for it. Currently I use a separate rule for second flashbang which looks like this:

if player.IsInBuyZone() && (app.freezetime || app.parser.CurrentTime() - app.roundBeginTime < app.buyTime) {
	// bought a flashbang
} else {
	// picked it up
}

where app.freezetime is binded to "cs_gamerules_data.m_bFreezePeriod" property of "CCSGameRulesProxy" server class on entity creation, app.roundBeginTime gets updated every time a freezetime ends and app.buyTime is accuired from app.parser.GameState().ConVars()["mp_buytime"].

This seems to be working but still could possibly be the cause of some bugs later on. So my question is -- what is a proper way to determine if a player actually bought a second flashbang?

@markus-wa
Copy link
Owner

Hmm, interesting problem. Unfortunately I don't have a quick answer.

It would probably be worth it to integrate this directly into the library.
Something like ItemPickup.IsBought
But I think your implementation is probably correct and I would imagine it would look similar if done inside the parser.

I will do some more testing, depending on the findings a PR would be very welcome 😄

@markus-wa
Copy link
Owner

markus-wa commented Sep 10, 2019

Side note: your current code will think knifes and starter pistols are bought.

This may or may not be what you want.

@BestAwperEver
Copy link
Contributor Author

I personally think that IsBought is too strong a name for the shown implementation 🙂 Theoretically, a player can accuire a new weapon through a console command, be given a new weapon for free from some plugin, from round restoring etc. In my application I have quite a complicated logic for eliminating all these reasons which is not general enough to be included directly into the library.

However ItemPickup.IsNew which wraps this code in my opition would be really handy. The only thing which stops me from submitting a PR right now is this second flashbang case.

Side note: your current code will think knifes and starter pistols are bought.
This may or may not be what you want.

Thank you for your care 😃
Currently I'm handling it via

app.nonBuyable = map[common.EquipmentElement]bool{
	common.EqGlock: true,
	common.EqP2000: true,
	common.EqUSP:   true,
	common.EqKnife: true,
	common.EqBomb:  true,
}

@markus-wa
Copy link
Owner

I personally think that IsBought is too strong a name for the shown implementation 🙂

Good point, yeah. I guess for the flashbang having the custom rule until #145 is solved would be acceptable. But if you'd rather wait for that to be resolved that's fine as well, I think it shouldn't be too long away.

app.nonBuyable = map[common.EquipmentElement]bool{
	common.EqGlock: true,
	common.EqP2000: true,
	common.EqUSP:   true,
	common.EqKnife: true,
	common.EqBomb:  true,
}

Technically, a Glock or USP can be bought if dropped first. But I assume that edge case is not super important 😄

@BestAwperEver
Copy link
Contributor Author

Technically, a Glock or USP can be bought if dropped first

That's true, I'm just hoping that in a real game (and I was recently working hard on eliminating all the unimportant rounds from the competitive game) when the player actually wants to win they wouldn't do such a nonsense 😃

@BestAwperEver
Copy link
Contributor Author

But if you'd rather wait for that to be resolved that's fine as well

TBH I think that your library, being in a sense a fundamental one, shoudn't rely on such rules as mine for a second flashbang 🙂 There always might be a case when e.g. a player A bought a flashbang playing as a terrorist on de_dust2, was almost immediately killed by a sniper from double doors and then a player B, which already had a flashbang, has picked up the second flashbang from player A's dead body.

@markus-wa
Copy link
Owner

I agree, but from what I understand this situation would actually be covered by the buytime check.

But anyway, it's certainly safer to wait since we don't know all the edge cases.

@BestAwperEver
Copy link
Contributor Author

BestAwperEver commented Sep 10, 2019

this situation would actually be covered by the buytime check

Not really, in this particular case player A may be dead until the end of the buy time.

This actually is the real problem I've encountered when parsing some MM demos. DD2 seems to be the only map where such situations (player's death in the buy zone in the first seconds of the round) can occur but still it is affecting my economy calculations 😞

I probably should implement some other checks like "have the player spent money equivalent of the item in the same tick as they have picked it up" but I don't know how to perform it aside from storing all players' money which they had in the previous availiable frame which is not reliable since there are some demos where there can be up to 13 in-game ticks between two demo frames and theoretically a player can obtain several new items in between.

@markus-wa
Copy link
Owner

Ahhh, bollocks!

Yeah we definitely better wait then.

@markus-wa
Copy link
Owner

So with #147 being merged, do you think your fix would be possible now?

If so please feel free to file a PR 🙂.

@BestAwperEver
Copy link
Contributor Author

BestAwperEver commented Sep 13, 2019

Well #147 doesn't really solve this issue: while we are now able to tell how many flashbangs a player has, there is still no way to tell if the second flashbang was bought or picked up since WeaponTraceable() returns UniqueID of the weapon the player already had. At least the method proposed above cannot tell the difference because app.weaponOwner[weaponTraceable.UniqueID()] would always be equal to the current player.

I suspect that there is some fix that can help us but I don't know how to implement it. Let me describe why do I think so:

Case 1: a player has no flashbangs and they pick up (and I mean actually pick up) one. Now the flashbang they have has the same UniqueID as the picked up one and we can track its previous owner (see lines 80, 83, 150, 154, 155 etc. from output3.log) and actually whatever we want to track.

Case 2: a player has one flashbang and they pick up or buy another one. Now UniqueID of the flashbang is lost somewhere and it serves only as ammo for the weapon player already has. In this case we cannot tell the origin of this second flashbang. However, it may be the same flashbang from the first case which otherwise we would be able to track be the player's "flashbang ammo" empty. So there is somewhere the UniqueID of this second flashbang, but we just don't have it anymore when ItemPickup is fired.

So I think there should be a method to catch this moment when UniqueUD of the second flashbang is lost and store it somewhere. Moreover, a believe there should be a way to attach this information directly to ItemPickup event so WeaponTracable() would return the actual picked up item and not try to find it in the player's inventory. It would be more robust and able to track any item despite of its ability to serve as ammo (e. g. when you play Danger Zone you can have several medkits and explosives; and I do not find it impossible that at some point your library may be used for parsing Danger Zone demos 😄)

@markus-wa
Copy link
Owner

Well #147 doesn't really solve this issue: while we are now able to tell how many flashbangs a player has, there is still no way to tell if the second flashbang was bought or picked up

Hmm you're right.

So there is somewhere the UniqueID of this second flashbang, but we just don't have it anymore when ItemPickup is fired.

Not quite, UniqueID is an internally generated thing, currently there is no Equipment instance being created in this case, so we don't have a UniqueID. But of course we could create one.

So I think there should be a method to catch this moment when UniqueUD of the second flashbang is lost and store it somewhere. Moreover, a believe there should be a way to attach this information directly to ItemPickup event so WeaponTracable() would return the actual picked up item and not try to find it in the player's inventory. It would be more robust and able to track any item despite of its ability to serve as ammo (e. g. when you play Danger Zone you can have several medkits and explosives; and I do not find it impossible that at some point your library may be used for parsing Danger Zone demos 😄)

I think you're right with that, thanks for your inputs!

Let's see if we can make this work somehow 😅.

@markus-wa
Copy link
Owner

It appears like this isn't possible for flashbangs, sorry.

We can't create a new UniqueID because we don't actually have a separate entity for the second flashbang from what I can tell.

WeaponTracable() would return the actual picked up item and not try to find it in the player's inventory.

The item_pickup event has no information about the actual item, only about the type of the item, so there would be no way to tell which of the two flashbangs was the new one even if we could create the second entity.

So I think for now there is no way to do this 🙁 .

@BestAwperEver
Copy link
Contributor Author

BestAwperEver commented Nov 6, 2019

So all we know is that a player somehow accquired the second flashbang? That's a pity.
How do you think, is there a way to add something like "MoneySpentThisTick" to a player property list? Should be a decent hack to make an almost certain guess that a player has bought something if we know that he has got it in the same tick he lost amount of money equivalent to item's price.

@markus-wa
Copy link
Owner

You could try to use CCSPlayerResource.m_iTotalCashSpent.NNN where NNN is Player.Entity.ID() with left padding (e.g. CCSPlayerResource.m_iTotalCashSpent.001)

But I woudln't want to integrate this into the library, seems a bit too hacky to support officially.

@BestAwperEver
Copy link
Contributor Author

BestAwperEver commented Nov 6, 2019

Yeah I agree. And also I forgot that we don't have information about each and every tick and there, as I have already mentioned above, might be a gap up to 13 ticks so the player can buy several items in between.

@markus-wa
Copy link
Owner

right, good point as well. quite unfortunate.

@JanEricNitschke
Copy link

From what i understand the problem here is just for flash bangs in case a player already has one. Would it be possible to implement it for everything else?

Either with a "yes, no, dont know" logic or as ItemPickup.IsDefinitleyNew which is false for duplicate flashbangs?

@MarcusKNielsen
Copy link

MarcusKNielsen commented Jan 13, 2024

I agree with @JanEricNitschke if it works for everything except the flashbangs, then this is still a very useful tool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants