Skip to content

Commit

Permalink
#2686 Mouse over now adapts gridSize if given from the response - the…
Browse files Browse the repository at this point in the history
… weight cache radius is also different now between player_position and enemy_position.
  • Loading branch information
Wotuu committed Feb 5, 2025
1 parent 57ef129 commit 6e3abda
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 49 deletions.
8 changes: 4 additions & 4 deletions app/Service/CombatLogEvent/CombatLogEventService.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ public function getGridAggregation(CombatLogEventFilter $filters): ?CombatLogEve

$size = match ($dataType) {
CombatLogEventDataType::PlayerPosition => [
':sizeX' => config('keystoneguru.heatmap.service.data.player.sizeX'),
':sizeY' => config('keystoneguru.heatmap.service.data.player.sizeY'),
':sizeX' => config('keystoneguru.heatmap.service.data.player.size_x'),
':sizeY' => config('keystoneguru.heatmap.service.data.player.size_y'),
':player' => 'true',
],
CombatLogEventDataType::EnemyPosition => [
':sizeX' => config('keystoneguru.heatmap.service.data.enemy.sizeX'),
':sizeY' => config('keystoneguru.heatmap.service.data.enemy.sizeY'),
':sizeX' => config('keystoneguru.heatmap.service.data.enemy.size_x'),
':sizeY' => config('keystoneguru.heatmap.service.data.enemy.size_y'),
':player' => 'false',
]
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Collection;

/**
* This class is used as a DTO to store the result of a CombatLogEvent aggregation (response from Opensearch).
*/
class CombatLogEventGridAggregationResult implements Arrayable
{
private bool $useFacade;
Expand All @@ -30,35 +33,41 @@ public function toArray(): array
/** @var Collection<Floor> $floors */
$floors = $dungeon->floors->keyBy('id');

$data = [];
$weightMax = 0;
$data = [];
foreach ($this->results as $floorId => $rows) {
/** @var Floor $floor */
$floor = $floors->get($floorId);

$latLngs = [];

$rawData = [];
if ($this->floorsAsArray) {
$rowCount = count($rows);
for ($i = 0; $i < $rowCount; $i += 3) {
[$x, $y, $count] = [$rows[$i], $rows[$i + 1], $rows[$i + 2]];

$latLngArray = $this->convertIngameLocationToLatLngArray(new IngameXY($x, $y, $floor));
$latLngArray['weight'] = $count;

$latLngs[] = $latLngArray;
$rawData[] = [$rows[$i], $rows[$i + 1], $rows[$i + 2]];
}
} else {
foreach ($rows as $xy => $count) {
/**
* @var string $xy
* @var int $count
*/
[$x, $y] = explode(',', $xy);
$row = explode(',', $xy);
$row[] = $count;

$rawData[] = $row;
}
}

$latLngs = [];
foreach ($rawData as $row) {
[$x, $y, $count] = $row;

$latLngArray = $this->convertIngameLocationToLatLngArray(new IngameXY($x, $y, $floor));
$latLngArray['weight'] = $count;
$latLngArray = $this->convertIngameLocationToLatLngArray(new IngameXY($x, $y, $floor));
$latLngArray['weight'] = $count;

$latLngs[] = $latLngArray;
$latLngs[] = $latLngArray;
if ($weightMax < $count) {
$weightMax = $count;
}
}

Expand Down Expand Up @@ -97,9 +106,12 @@ public function toArray(): array
}

return [
'data' => array_values($data),
'data_type' => $this->combatLogEventFilter->getDataType(),
'run_count' => $this->runCount,
'data' => array_values($data),
'data_type' => $this->combatLogEventFilter->getDataType(),
'weight_max' => $weightMax,
'run_count' => $this->runCount,
'grid_size_x' => config('keystoneguru.heatmap.service.data.player.size_x'),
'grid_size_y' => config('keystoneguru.heatmap.service.data.player.size_y'),
];


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class HeatmapDataResponse implements Arrayable
private Collection $data;
private CombatLogEventDataType $dataType;
private int $runCount;
private int $weightMax;
private int $gridSizeX;
private int $gridSizeY;
private ?string $url = null;

private function __construct()
Expand All @@ -33,6 +36,21 @@ public function getRunCount(): int
return $this->runCount;
}

public function getWeightMax(): int
{
return $this->weightMax;
}

public function getGridSizeX(): int
{
return $this->gridSizeX;
}

public function getGridSizeY(): int
{
return $this->gridSizeY;
}

public function getUrl(): ?string
{
return $this->url;
Expand All @@ -41,12 +59,15 @@ public function getUrl(): ?string
public function toArray(): array
{
return array_filter([
'data' => $this->data->map(
'data' => $this->data->map(
fn(HeatmapDataFloorData $floorData) => $floorData->toArray()
)->values()->toArray(),
'data_type' => $this->dataType->value,
'run_count' => $this->runCount,
'url' => $this->url,
'data_type' => $this->dataType->value,
'run_count' => $this->runCount,
'weight_max' => $this->weightMax,
'grid_size_x' => $this->gridSizeX,
'grid_size_y' => $this->gridSizeY,
'url' => $this->url,
]);
}

Expand All @@ -61,8 +82,11 @@ public static function fromArray(array $response): HeatmapDataResponse
);
}

$result->dataType = $response['data_type'];
$result->runCount = $response['run_count'];
$result->dataType = $response['data_type'];
$result->runCount = $response['run_count'];
$result->weightMax = $response['weight_max'];
$result->gridSizeX = $response['grid_size_x'] ?? config('keystoneguru.heatmap.service.data.player.size_x');
$result->gridSizeY = $response['grid_size_y'] ?? config('keystoneguru.heatmap.service.data.player.size_y');
if (isset($response['url'])) {
$result->url = $response['url'];
}
Expand Down
7 changes: 7 additions & 0 deletions app/Service/RaiderIO/Dtos/RaiderIOHeatmapGridResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
use App\Service\Coordinates\CoordinatesServiceInterface;
use Illuminate\Support\Facades\Auth;

/**
* This represents the Raider.io version of the response from Opensearch. It uses much of the same
* structure, but has a few differences in the way it is presented.
*/
class RaiderIOHeatmapGridResponse extends CombatLogEventGridAggregationResult
{
public function __construct(
CoordinatesServiceInterface $coordinatesService,
CombatLogEventFilter $combatLogEventFilter,
array $results,
int $runCount,
private readonly int $maxSamplesInGrid,
private readonly string $url,
bool $floorsAsArray = false,
) {
Expand All @@ -28,6 +33,8 @@ public function toArray(): array
if (Auth::check() && (Auth::user()->hasRole(Role::roles([Role::ROLE_ADMIN, Role::ROLE_INTERNAL_TEAM])))) {
$result['url'] = $this->url;
}
// Override the weight_max
$result['weight_max'] = $this->maxSamplesInGrid;

return $result;
}
Expand Down
1 change: 1 addition & 0 deletions app/Service/RaiderIO/RaiderIOApiService.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public function getHeatmapData(HeatmapDataFilter $heatmapDataFilter): HeatmapDat
CombatLogEventFilter::fromHeatmapDataFilter($this->seasonService, $heatmapDataFilter),
$json['gridsByFloor'],
$json['numRuns'],
$json['maxSamplesInGrid'],
$url,
$floorsAsArray
))->toArray()
Expand Down
8 changes: 4 additions & 4 deletions config/keystoneguru.php
Original file line number Diff line number Diff line change
Expand Up @@ -375,16 +375,16 @@
'data' => [
// Player data can get away with less accurate positioning
'player' => [
'sizeX' => 400,
'sizeY' => 300,
'size_x' => 300,
'size_y' => 200,
],
// Enemy requires precise positioning, this resolution is too much
// for raw since the buckets would be too small, but since the coordinates
// are equal to enemy positions this only just increases the accuracy of the
// points, while still having a low bucket count.
'enemy' => [
'sizeX' => 800,
'sizeY' => 600,
'size_x' => 800,
'size_y' => 600,
],
],
],
Expand Down
3 changes: 3 additions & 0 deletions resources/assets/js/custom/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ const COMBAT_LOG_EVENT_EVENT_TYPE_NPC_DEATH = 'npc_death';
const COMBAT_LOG_EVENT_EVENT_TYPE_PLAYER_DEATH = 'player_death';
const COMBAT_LOG_EVENT_EVENT_TYPE_PLAYER_SPELL = 'player_spell';

const COMBAT_LOG_EVENT_DATA_TYPE_PLAYER_POSITION = 'player_position';
const COMBAT_LOG_EVENT_DATA_TYPE_ENEMY_POSITION = 'enemy_position';

// Leaflet constants
const LEAFLET_PANE_MAP = 'mapPane';
const LEAFLET_PANE_TILE = 'tilePane';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,14 @@ class CommonMapsHeatmapsearchsidebar extends SearchInlineBase {

super._search({
success: function (json) {
getState().getDungeonMap().pluginHeat.setRawLatLngsPerFloor(json.data, json.sizeX ?? null, json.sizeY ?? null);
getState().getDungeonMap().pluginHeat.setRawLatLngsPerFloor(
json.data,
json.data_type,
json.run_count,
json.weight_max,
json.grid_size_x ?? null,
json.grid_size_y ?? null,
);

if (json.hasOwnProperty('url')) {
console.log(json.url);
Expand Down
49 changes: 31 additions & 18 deletions resources/assets/js/custom/mapplugins/heatplugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ class HeatPlugin extends MapPlugin {
this.weightByFloorIdGrid = [];
/** The raw latLngs per floor */
this.rawLatLngsByFloorId = [];
this.sizeX = 300;
this.sizeY = 200;
this.dataType = COMBAT_LOG_EVENT_DATA_TYPE_PLAYER_POSITION;
this.runCount = 0;
this.gridSizeX = 300;
this.gridSizeY = 200;
/**
* The radius where we consider points around us when calculating weights for points that don't exist in the heatmap.
* We build a full grid of weights for each floor, so we don't have to do that on the fly.
**/
this.weightCacheRadius = 5;
this.weightCacheRadius = [];
this.weightCacheRadius[COMBAT_LOG_EVENT_DATA_TYPE_PLAYER_POSITION] = 5;
this.weightCacheRadius[COMBAT_LOG_EVENT_DATA_TYPE_ENEMY_POSITION] = 2;

/** The max weight that we have in the heatmap per floor, used for %-age calculations in the tooltip */
this.weightMaxByFloorId = [];
Expand All @@ -36,8 +40,8 @@ class HeatPlugin extends MapPlugin {

_getGridPositionForLatLng(latLng) {
return {
x: Math.floor((latLng.lat / MAP_MAX_LAT) * this.sizeX),
y: Math.floor((latLng.lng / MAP_MAX_LNG) * this.sizeY)
x: Math.floor((latLng.lat / MAP_MAX_LAT) * this.gridSizeX),
y: Math.floor((latLng.lng / MAP_MAX_LNG) * this.gridSizeY)
}
}

Expand All @@ -51,7 +55,7 @@ class HeatPlugin extends MapPlugin {
return grid[x][y];
}

const aspectRatio = this.sizeY / this.sizeX; // Adjust vertical distance scaling
const aspectRatio = this.gridSizeY / this.gridSizeX; // Adjust vertical distance scaling
let weightedSum = 0;
let weightTotal = 0;

Expand All @@ -60,7 +64,7 @@ class HeatPlugin extends MapPlugin {
const nx = x + dx;
const ny = y + dy;

if (nx < 0 || nx >= this.sizeX || ny < 0 || ny >= this.sizeY) continue; // Out of bounds
if (nx < 0 || nx >= this.gridSizeX || ny < 0 || ny >= this.gridSizeY) continue; // Out of bounds
if (grid[nx]?.[ny] === undefined) continue; // No weight assigned

// Adjust distance calculation
Expand All @@ -87,7 +91,7 @@ class HeatPlugin extends MapPlugin {

let latLng = event.latlng;
let gridPosition = this._getGridPositionForLatLng(latLng);
let weight = this._getWeightAt(getState().getCurrentFloor().id, gridPosition.x, gridPosition.y, 9);
let weight = this._getWeightAt(getState().getCurrentFloor().id, gridPosition.x, gridPosition.y, this.weightCacheRadius[this.dataType]);

if (weight !== null && weight > 0) {
if (!this.mouseTooltip) {
Expand All @@ -103,7 +107,10 @@ class HeatPlugin extends MapPlugin {
}

// Update tooltip position and content
let percent = Math.round(weight / this.weightMaxByFloorId[getState().getCurrentFloor().id] * 100);
let max = this.weightMax;
// This somehow doesn't work right, weightMax produces a better result
// let max = this.dataType === COMBAT_LOG_EVENT_DATA_TYPE_PLAYER_POSITION ? this.weightMax : this.runCount;
let percent = Math.round(weight / max * 100);
this.mouseTooltip.setLatLng(latLng).setContent(`${percent}% - ${weight}`);
} else if (this.mouseTooltip) {
// Remove tooltip if no weight
Expand Down Expand Up @@ -194,12 +201,21 @@ class HeatPlugin extends MapPlugin {

/**
* @param rawLatLngsPerFloor {Object}
* @param sizeX {Number|null}
* @param sizeY {Number|null}
* @param dataType {String}
* @param runCount {Number}
* @param weightMax {Number}
* @param gridSizeX {Number}
* @param gridSizeY {Number}
*/
setRawLatLngsPerFloor(rawLatLngsPerFloor, sizeX, sizeY) {
setRawLatLngsPerFloor(rawLatLngsPerFloor, dataType, runCount, weightMax, gridSizeX, gridSizeY) {
console.assert(this instanceof HeatPlugin, 'this is not an instance of HeatPlugin', this);

this.dataType = dataType;
this.runCount = runCount;
this.gridSizeX = gridSizeX;
this.gridSizeY = gridSizeY;
this.weightMax = weightMax ?? Math.max(this.weightMaxByFloorId);

// Construct an easily referenced array that splits up the latLngs per floor
this.rawLatLngsByFloorId = [];
this.weightMaxByFloorId = [];
Expand Down Expand Up @@ -229,20 +245,17 @@ class HeatPlugin extends MapPlugin {

weightByFloorIdGridCache[rawLatLngsOnFloor.floor_id] = [];
// Precompute missing weights for the entire grid
for (let x = 0; x < this.sizeX; x++) {
for (let x = 0; x < this.gridSizeX; x++) {
weightByFloorIdGridCache[rawLatLngsOnFloor.floor_id][x] ??= [];
for (let y = 0; y < this.sizeY; y++) {
weightByFloorIdGridCache[rawLatLngsOnFloor.floor_id][x][y] = this._getWeightAt(rawLatLngsOnFloor.floor_id, x, y, this.weightCacheRadius); // Use radius=5 (adjust as needed)
for (let y = 0; y < this.gridSizeY; y++) {
weightByFloorIdGridCache[rawLatLngsOnFloor.floor_id][x][y] = this._getWeightAt(rawLatLngsOnFloor.floor_id, x, y, this.weightCacheRadius[[this.dataType]]); // Use radius=5 (adjust as needed)
}
}

this.weightByFloorIdGrid[rawLatLngsOnFloor.floor_id] = weightByFloorIdGridCache[rawLatLngsOnFloor.floor_id];
}

this._applyLatLngsForFloor(getState().getCurrentFloor().id);

this.sizeX = sizeX ?? this.sizeX;
this.sizeY = sizeY ?? this.sizeY;
}

clear() {
Expand Down

0 comments on commit 6e3abda

Please sign in to comment.