Alrighty! Let's get this PR incorporated into Postgame core. First: rebase on top of latest develop.
Set Up Postgame
I had you build this separate from Postgame first so we could delay getting Postgame set up properly on your computer. I do not have a development container setup, which means you will need to set up an application like XAMPP and start both the Apache and MariaDB servers.
Setup an .env.local file at the root of Postgame and make sure it has a DATABASE_URL value. For example, on my local machine, the file contains:
If you go to your local website, you'll see something similar to this:
Because you're writing code to actually import a replay, you'll need to delete the replay over and over again as you work on this feature. Get the ID of the replay you just imported and delete.
php bin/console app:replay:delete <id>
Merging this into core
This functionality will be located in our ReplayImportService (src/Service/ReplayImportService.php). This is a complicated service that handles everything about reading in a replay.
Take a look ReplayImportService::handlePacket(). You will see I have an if statement that calls respective methods dedicated to certain packet types. You will need to add your own if condition to this and watch for packets of MsgPlayerUpdate instance; i.e. create a handleMsgPlayerUpdate() method.
You do not need to migrate your listening to MsgAddPlayer and MsgRemovePlayer, this service already does that. And... replays can be a bit... messed up where packets for players that haven't fully joined yet are already sent. This service already takes into account all of that. Use ReplayImportService::currPlayersByIndex in this service instead of your id_to_callsign map.
Create ReplayImportService::$currPlayersHeatMap private array that will store callsigns to PlayerMovementGrid objects.
Create a PlayerMovementGrid (src/Utility/) class. In each call to handleMsgPlayerUpdate(), you will check if the callsign already exists in $currPlayersHeatMap, if not, create a new PlayerMovementGrid.
In this method, you will then store the player locations in the respective PlayerMovementGrid objects (i.e. call an addPosition method).
Create a new entity called PlayerHeatMap (run php bin/console make:entity) that should contain the following information:
replay (foreign key to Replay entity; a relation)
player (foreign key to Player entity; a relation)
heatmap (a doctrine array, not a simply_array)
Step through the interactive wizard of creating the entity and all of the fields. This will automatically create a migration file for you, a much better process than Laravel imo.
In ReplayImportService::importReplay, add and call a new method called processHeatMaps()before the call to updateBatchStatus. In this new method you're going to be converting PlayerMovementGrid instances into PlayerHeatMap instances and persisting them to our ORM.
Set the replay object, player object, and finally the location array from PlayerMovementGrid. Then persist the entity. Take a look at handleMsgCaptureFlag to see how entity values are set and how they're persisted.
Write a new HeatMapWriterService and this is where your SVG writing logic will be placed. Take a look at MapThumbnailWriterService (src/Service/MapThumbnailWriterService.php) for an idea of how they look.
Use dependency injection to inject an instance of HeatMapWriterService into our ReplayImportService (i.e. look at the constructor and see how it works).
In the processHeatMaps method, you will now call a new method from HeatMapWriterService called writeHeatMap(Replay $replay, PlayerHeatMap $heatMap).
This writer will write to a folder called "heat-maps" and the filename will be in the format of <replay_id>-<player_callsign>.svg (where <player_callsign> is URL encoded; urlencode).
Anyway, this is why I wanted you to write this separately from Postgame core at first lol
Alrighty! Let's get this PR incorporated into Postgame core. First: rebase on top of latest
develop
.Set Up Postgame
I had you build this separate from Postgame first so we could delay getting Postgame set up properly on your computer. I do not have a development container setup, which means you will need to set up an application like XAMPP and start both the Apache and MariaDB servers.
Setup an
.env.local
file at the root of Postgame and make sure it has aDATABASE_URL
value. For example, on my local machine, the file contains:Where
bzflag-postgame
is the name of the database I created (you'll need to do this either via phpMyAdmin orphp bin/console doctrine:database:create
)After the database is created, you will need to run our database migrations to create our schema.
Once this is set up, now you can import the sample replay file I sent you.
If you go to your local website, you'll see something similar to this:
Because you're writing code to actually import a replay, you'll need to delete the replay over and over again as you work on this feature. Get the ID of the replay you just imported and delete.
Merging this into core
This functionality will be located in our
ReplayImportService
(src/Service/ReplayImportService.php
). This is a complicated service that handles everything about reading in a replay.Take a look
ReplayImportService::handlePacket()
. You will see I have an if statement that calls respective methods dedicated to certain packet types. You will need to add your own if condition to this and watch for packets ofMsgPlayerUpdate
instance; i.e. create ahandleMsgPlayerUpdate()
method.You do not need to migrate your listening to
MsgAddPlayer
andMsgRemovePlayer
, this service already does that. And... replays can be a bit... messed up where packets for players that haven't fully joined yet are already sent. This service already takes into account all of that. UseReplayImportService::currPlayersByIndex
in this service instead of yourid_to_callsign
map.Create
ReplayImportService::$currPlayersHeatMap
private array that will store callsigns toPlayerMovementGrid
objects.Create a
PlayerMovementGrid
(src/Utility/) class. In each call tohandleMsgPlayerUpdate()
, you will check if the callsign already exists in$currPlayersHeatMap
, if not, create a newPlayerMovementGrid
.In this method, you will then store the player locations in the respective
PlayerMovementGrid
objects (i.e. call anaddPosition
method).Create a new entity called
PlayerHeatMap
(runphp bin/console make:entity
) that should contain the following information:relation
)relation
)array
, not asimply_array
)Step through the interactive wizard of creating the entity and all of the fields. This will automatically create a migration file for you, a much better process than Laravel imo.
In
ReplayImportService::importReplay
, add and call a new method calledprocessHeatMaps()
before the call toupdateBatchStatus
. In this new method you're going to be convertingPlayerMovementGrid
instances intoPlayerHeatMap
instances andpersist
ing them to our ORM.https://github.com/allejo/postgame/blob/50482cc82339e15fc4ef19ea6ff2d87a3993b53f/src/Service/ReplayImportService.php#L298
PlayerMovementGrid
. Then persist the entity. Take a look athandleMsgCaptureFlag
to see how entity values are set and how they're persisted.Write a new
HeatMapWriterService
and this is where your SVG writing logic will be placed. Take a look atMapThumbnailWriterService
(src/Service/MapThumbnailWriterService.php) for an idea of how they look.Use dependency injection to inject an instance of
HeatMapWriterService
into ourReplayImportService
(i.e. look at the constructor and see how it works).In the
processHeatMaps
method, you will now call a new method fromHeatMapWriterService
calledwriteHeatMap(Replay $replay, PlayerHeatMap $heatMap)
.This writer will write to a folder called "heat-maps" and the filename will be in the format of
<replay_id>-<player_callsign>.svg
(where<player_callsign>
is URL encoded;urlencode
).Anyway, this is why I wanted you to write this separately from Postgame core at first lol