saul / demofile-net

Blazing fast cross-platform demo parser library for Counter-Strike 2 and Valve's Deadlock, written in C#.
MIT License
111 stars 10 forks source link

Deadlock no Playerspawn events are firing #113

Closed Theoya closed 1 month ago

Theoya commented 1 month ago

Research

Description

The event is never being triggered

Code to reproduce

demo.Source1GameEvents.PlayerSpawn += e =>
            {
                writer.WriteLine($"{e.Player?.PlayerName} connected");
                Console.WriteLine($"{e.Player?.PlayerName} connected");
                if (e.Player != null)
                {
                    PlayerEntityIndexMap.Add((int)e.Player?.EntityIndex.Value, e.Player.PlayerName);
                }

            };

Affected demos

No response

QuinnBast commented 1 month ago

I got curious, because I tried to listen to some pretty obvious events like BreakBreakable or ItemPickup. Surely these happen all the time.

But it appears that a LOT of the events don't actually trigger at all. So I created a custom EventHappenedListener class to simply check if an event happened or not:

public class EventHappensListener
{
    public bool eventHappened = false;
    public string eventName;

    public EventHappensListener(string eventName, Dictionary<String, EventHappensListener> eventList)
    {
        this.eventName = eventName;
        eventList[eventName] = this;
    }

    public void EventHappens(Source1GameEventBase someEvent)
    {
        eventHappened = true;
    }

    public override string ToString()
    {
        return $"{eventName}: {eventHappened}";
    }
}

And I attached it to every event in the game:

 demoParser.Source1GameEvents.AchievementEvent += new EventHappensListener("AchievementEvent", eventHappens).EventHappens;
demoParser.Source1GameEvents.AbilityAdded += new EventHappensListener("AbilityAdded", eventHappens).EventHappens;
demoParser.Source1GameEvents.AbilityRemoved += new EventHappensListener("AbilityRemoved", eventHappens).EventHappens;
demoParser.Source1GameEvents.AchievementEarned += new EventHappensListener("AchievementEarned", eventHappens).EventHappens;
demoParser.Source1GameEvents.AbilityCastFailed += new EventHappensListener("AbilityCastFailed", eventHappens).EventHappens;
demoParser.Source1GameEvents.AbilityCastSucceeded += new EventHappensListener("AbilityCastSucceeded", eventHappens).EventHappens;
demoParser.Source1GameEvents.AbilityLevelChanged += new EventHappensListener("AbilityLevelChanged", eventHappens).EventHappens;
demoParser.Source1GameEvents.AchievementWriteFailed += new EventHappensListener("AchievementWriteFailed", eventHappens).EventHappens;
demoParser.Source1GameEvents.BonusUpdated += new EventHappensListener("BonusUpdated", eventHappens).EventHappens;
demoParser.Source1GameEvents.BossDamaged += new EventHappensListener("BossDamaged", eventHappens).EventHappens;
demoParser.Source1GameEvents.BossKilled += new EventHappensListener("BossKilled", eventHappens).EventHappens;
demoParser.Source1GameEvents.BreakBreakable += new EventHappensListener("BreakBreakable", eventHappens).EventHappens;
demoParser.Source1GameEvents.BreakProp += new EventHappensListener("BreakProp", eventHappens).EventHappens;

// Etc....

This is the result:

AchievementEvent: False
AbilityAdded: False
AbilityRemoved: False
AchievementEarned: False
AbilityCastFailed: False
AbilityCastSucceeded: False
AbilityLevelChanged: False
AchievementWriteFailed: False
BonusUpdated: False
BossDamaged: False
BossKilled: False
BreakBreakable: False
BreakProp: False
BrokenBreakable: False
BotPlayerReplace: False
BreakPieceSpawned: False
BrokeEnemyShield: False
CurrencyMissed: False
CurrencyDenied: False
CitadelPauseEvent: False
CartUpdated: False
ClientDisconnect: False
CrateSpawn: False
CitadelHintChanged: False
CitadeltvChaseHero: True
CitadeltvUnitEvent: False
ClientsideLessonClosed: False
CrateSpawnNotification: False
ClientPlayerCurrencyChange: False
ClientPlayerHeroChanged: False
DemoStop: False
DifficultyChanged: False
DoorClose: False
DropRateModified: False
DynamicShadowLightChanged: False
EventTicketModified: False
EntityKilled: False
EntityVisible: False
FinaleStart: False
FlareIgniteNpc: False
GameNewmap: False
GameMessage: False
GameinstructorDraw: False
GameinstructorNodraw: False
GameuiActivated: False
GameuiHidden: False
GcConnected: False
GrenadeBounce: False
GameStateChanged: False
GameuiFreeCursorChanged: False
HltvCameraman: False
HltvChase: False
HltvChat: False
HltvFixed: False
HltvMessage: False
HltvReplay: False
HltvStatus: False
HltvTitle: False
HltvVersioninfo: True
HostnameChanged: False
HltvRankCamera: False
HltvRankEntity: False
HltvReplayStatus: False
HelicopterGrenadePuntMiss: False
HeroAssignedLaneChanged: False
HeroDraftOrderChanged: False
ItemPickup: False
InventoryUpdated: False
InstructorCloseLesson: False
ItemFileReloaded: False
InstructorStartLesson: False
ItemSchemaInitialized: False
InstructorServerHintCreate: False
InstructorServerHintStop: False
LocalPlayerTeam: False
LaneTestStateUpdated: False
LocalPlayerControllerTeam: False
LocalPlayerPawnChanged: False
LocalPlayerShotHit: False
LocalPlayerWeaponsChanged: False
LocalPlayerAbilitiesVdataChanged: False
LocalPlayerUnitStatesChanged: False
LocalPlayerAbilityCooldownEndChanged: False
MapShutdown: False
MapTransition: False
MatchClock: True
MidBossSpawned: False
NonPlayerUsedAbility: False
PlayerDeath: True
PlayerConnect: False
PlayerSpawn: False
PartyUpdated: False
PersonaUpdated: False
PhysgunPickup: False
PlayerActivate: False
PlayerChangename: False
PlayerChat: False
PlayerDisconnect: False
PlayerFootstep: False
PlayerHealed: False
PlayerHintmessage: False
PlayerInfo: False
PlayerRespawned: False
PlayerTeam: False
PlayerAbilityUpgraded: False
PlayerAmmoFull: False
PlayerAmmoIncreased: False
PlayerBotReplace: False
PlayerConnectFull: False
PlayerDamageIncreased: False
PlayerDraftingChanged: False
PlayerFullUpdate: False
PlayerGivenShield: False
PlayerHealPrevented: False
PlayerHeroChanged: False
PlayerHeroReset: False
PlayerItemsChanged: False
PlayerKillStreak: False
PlayerLevelChanged: False
PlayerMaxhealthIncreased: False
PlayerModifiersChanged: False
PlayerRezIncoming: False
PlayerStatsChanged: False
PlayerStatsUpdated: False
PlayerUsedAbility: True
PlayerUsedItem: False
PlayerWeaponSwitched: False
PlayerClosedItemShop: False
PlayerDataAbilitiesChanged: False
PlayerGuidedSandboxStarted: False
PlayerInfoIndividualUpdated: False
PlayerItemPriceChanged: False
PlayerKillStreakNew: False
PlayerLaneChallengeEnded: False
PlayerOpenedHeroSelect: False
PlayerLaneChallengeStarted: False
PlayerOpenedItemShop: False
PlayerRespawnTimeChanged: False
PlayerShopZoneChanged: False
PlayerAbilityBonusCounterChanged: False
PlayerAbilityUpgradeSellPriceChanged: False
RoundEnd: False
RagdollDissolved: False
RoundStart: False
RejuvStatusMsg: False
RoundFreezeEnd: False
ReloadFailedNoAmmo: False
RoundStartPostNav: False
RoundStartPreEntity: False
Source1GameEvent: True
ServerSpawn: False
ServerCvar: False
ServerMessage: False
ServerShutdown: False
SandboxPlayerMoved: False
ServerPreShutdown: False
SpecModeUpdated: False
SpecTargetUpdated: False
StorePricesheetUpdated: False
SetInstructorGroupEnabled: False
SpectateHomeTeamChanged: False
TeamInfo: False
TeamMsg: False
TeamScore: False
TeamplayBroadcastAudio: False
TeamplayRoundStart: False
TitanTransformingComplete: False
ToolsContentChanged: False
TitanTransformingStart: False
UserDataDownloaded: False
VoteChanged: False
VoteFailed: False
VotePassed: False
VoteStarted: False
VoteCastNo: False
VoteCastYes: False
WeaponReloadComplete: False
WeaponReloadStarted: False
WeaponZoomStarted: False
ZiplinePlayerAttached: False
ZiplinePlayerDetached: False

So for people who are curious, currently only the following events are firing:

CitadeltvChaseHero: True
HltvVersioninfo: True
MatchClock: True
PlayerDeath: True
PlayerUsedAbility: True
Source1GameEvent: True
Theoya commented 1 month ago

Source1GameEvent: True

The thing is, the sub events for this arent firing either

@saul

QuinnBast commented 1 month ago

I'm in a discord about this and this was from the discord. Maybe relevant to this discussion:

You need to capture events to get data Once it reads that data won't be there anymore

        demo.PacketEvents.SvcCreateStringTable += (e) =>
        {
            foreach (var v in demo.PlayerInfos)
            {
                if (v == null) continue;
                using (var db = new DemDbContext())
                {
                    if (!db.Players.Any(x => x.UserId == v.Userid))
                    {
                        db.Players.Add(new Player()
                        {
                            Name = v.Name,
                            SteamId = v.Steamid,
                            UserId = v.Userid,
                            Xuid = $"{v.Xuid}"
                        });

                        db.SaveChanges();
                    }
                }
            }
        };

IE this is what I am doing to capture the player inf, could also interogate after each update

Theoya commented 1 month ago

I'm in a discord about this and this was from the discord. Maybe relevant to this discussion:

You need to capture events to get data Once it reads that data won't be there anymore

        demo.PacketEvents.SvcCreateStringTable += (e) =>
        {
            foreach (var v in demo.PlayerInfos)
            {
                if (v == null) continue;
                using (var db = new DemDbContext())
                {
                    if (!db.Players.Any(x => x.UserId == v.Userid))
                    {
                        db.Players.Add(new Player()
                        {
                            Name = v.Name,
                            SteamId = v.Steamid,
                            UserId = v.Userid,
                            Xuid = $"{v.Xuid}"
                        });

                        db.SaveChanges();
                    }
                }
            }
        };

IE this is what I am doing to capture the player inf, could also interogate after each update

@QuinnBast could you hook me up with the discord?

And would i run something like this in a parser or while a game is running?

QuinnBast commented 1 month ago

Deadlock Developer Community Discord

And would i run something like this in a parser or while a game is running?

I think you'd run this while parsing a demofile but I have no idea, I haven't tried it. Might be worth asking more details in the discord. I think what they are doing is some raw packet parsing of the events.

saul commented 1 month ago

A property of demo files for both Source 1, Source 2, CS2 and Deadlock etc, is that not all Source1GameEvents are available in demo files.

The game defines many events (e.g. player spawn) that may not be recorded in the demo. Some events are available on the game server only, and never sent to clients, or if they are sent to clients, not then recorded in demo files.

Keep in mind that Valve store demo files for public matchmaking matches for some time, so they only want to store the absolute bare minimum information to replay the game from the game client. This means that any data that isn't necessary for that won't be recorded in the demo file.

I'm in a discord about this and this was from the discord. Maybe relevant to this discussion:

You need to capture events to get data

Once it reads that data won't be there anymore

IE this is what I am doing to capture the player inf, could also interogate after each update

This isn't true. The contents of string tables (e.g. userinfo) is persistent, and the data remains even after the event is fired. You can even access it from the player controller with the PlayerInfo property.

However when Source1GameEvents fire, the game event data is not persisted. For example on PlayerDeath, that information is not stored by the library. If you want to keep track of it, you'll have to store it yourself.

Hope that makes sense.

saul commented 1 month ago

If you want to know when a player respawns, you can use:

demo.EntityEvents.CCitadelPlayerPawn.AddChangeCallback(x => x.IsAlive, (pawn, old, newLife) =>
{
    Console.WriteLine($"{pawn.Controller?.PlayerName} {(old ? "alive" : "dead")} -> {(newLife ? "alive" : "dead")}");
});