868 lines
33 KiB
PHP
868 lines
33 KiB
PHP
<?php
|
||
|
||
|
||
namespace Exodus4D\Pathfinder\Controller\Api\Rest;
|
||
|
||
|
||
use Exodus4D\Pathfinder\Lib\Config;
|
||
use Exodus4D\Pathfinder\Controller\Ccp\Universe;
|
||
use Exodus4D\Pathfinder\Model\Pathfinder;
|
||
|
||
class Route extends AbstractRestController {
|
||
|
||
|
||
/**
|
||
* cache key for current Thera connections from eve-scout.com
|
||
*/
|
||
const CACHE_KEY_THERA_JUMP_DATA = 'CACHED_THERA_JUMP_DATA';
|
||
|
||
/**
|
||
* route search depth
|
||
*/
|
||
const ROUTE_SEARCH_DEPTH_DEFAULT = 1;
|
||
|
||
/**
|
||
* ESI route search can handle max 100 custom connections
|
||
* -> each connection has a A->B and B->A entry. So we have 50 "real connections"
|
||
*/
|
||
const MAX_CONNECTION_COUNT = 100;
|
||
|
||
/**
|
||
* cache time for static jump data (e.g. K-Space stargates)
|
||
* @var int
|
||
*/
|
||
private $staticJumpDataCacheTime = 86400;
|
||
|
||
/**
|
||
* cache time for dynamic jump data (e.g. W-Space systems, Jumpbridges. ...)
|
||
* @var int
|
||
*/
|
||
private $dynamicJumpDataCacheTime = 10;
|
||
|
||
/**
|
||
* cache time for Thera connections from eve-scout.com
|
||
* @var int
|
||
*/
|
||
private $theraJumpDataCacheTime = 60;
|
||
|
||
/**
|
||
* array system information grouped by systemId
|
||
* @var array
|
||
*/
|
||
private $nameArray = [];
|
||
|
||
/**
|
||
* array neighbour systems grouped by systemName
|
||
* @var array
|
||
*/
|
||
private $jumpArray = [];
|
||
|
||
/**
|
||
* array with systemName => systemId matching
|
||
* @var array
|
||
*/
|
||
private $idArray = [];
|
||
|
||
/**
|
||
* template for routeData payload
|
||
* @var array
|
||
*/
|
||
private $defaultRouteData = [
|
||
'routePossible' => false,
|
||
'routeJumps' => 0,
|
||
'maxDepth' => self::ROUTE_SEARCH_DEPTH_DEFAULT,
|
||
'depthSearched' => 0,
|
||
'searchType' => '',
|
||
'route' => [],
|
||
'error' => ''
|
||
];
|
||
|
||
/**
|
||
* reset all jump data
|
||
*/
|
||
protected function resetJumpData(){
|
||
$this->nameArray = [];
|
||
$this->jumpArray = [];
|
||
$this->idArray = [];
|
||
}
|
||
|
||
/**
|
||
* set static system jump data for this instance
|
||
* the data is fixed and should not change
|
||
* -> jump data includes JUST "static" connections (Stargates)
|
||
* -> this data is equal for EACH route search (does not depend on map data)
|
||
*/
|
||
private function setStaticJumpData(){
|
||
if($universeDB = $this->getDB('UNIVERSE')){
|
||
$query = "SELECT * FROM system_neighbour";
|
||
$rows = $universeDB->exec($query, null, $this->staticJumpDataCacheTime);
|
||
|
||
if(count($rows) > 0){
|
||
array_walk($rows, function(&$row){
|
||
$row['jumpNodes'] = array_map('intval', explode(':', $row['jumpNodes']));
|
||
});
|
||
$this->updateJumpData($rows);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* set/add dynamic system jump data for specific "mapId"´s
|
||
* -> this data is dynamic and could change on any map change
|
||
* -> (e.g. new system added, connection added/updated, ...)
|
||
* @param array $mapIds
|
||
* @param array $filterData
|
||
* @throws \Exception
|
||
*/
|
||
private function setDynamicJumpData($mapIds = [], $filterData = []){
|
||
// make sure, mapIds are integers (protect against SQL injections)
|
||
$mapIds = array_unique( array_map('intval', $mapIds), SORT_NUMERIC);
|
||
|
||
if( !empty($mapIds) ){
|
||
// map filter ---------------------------------------------------------------------------------------------
|
||
$whereMapIdsQuery = (count($mapIds) == 1) ? " = " . reset($mapIds) : " IN (" . implode(', ', $mapIds) . ")";
|
||
|
||
// connection filter --------------------------------------------------------------------------------------
|
||
$whereQuery = "";
|
||
$includeScopes = [];
|
||
$includeTypes = [];
|
||
$excludeTypes = [];
|
||
$includeEOL = true;
|
||
|
||
$excludeEndpointTypes = [];
|
||
|
||
if( $filterData['stargates'] === true){
|
||
// include "stargates" for search
|
||
$includeScopes[] = 'stargate';
|
||
$includeTypes[] = 'stargate';
|
||
|
||
}
|
||
|
||
if( $filterData['jumpbridges'] === true ){
|
||
// add jumpbridge connections for search
|
||
$includeScopes[] = 'jumpbridge';
|
||
$includeTypes[] = 'jumpbridge';
|
||
}
|
||
|
||
if( $filterData['wormholes'] === true ){
|
||
// add wormhole connections for search
|
||
$includeScopes[] = 'wh';
|
||
$includeTypes[] = 'wh_fresh';
|
||
|
||
|
||
if( $filterData['wormholesReduced'] === true ){
|
||
$includeTypes[] = 'wh_reduced';
|
||
}
|
||
|
||
if( $filterData['wormholesCritical'] === true ){
|
||
$includeTypes[] = 'wh_critical';
|
||
}
|
||
|
||
if( $filterData['wormholesEOL'] === false ){
|
||
$includeEOL = false;
|
||
}
|
||
|
||
if(!empty($filterData['excludeTypes'])){
|
||
$excludeTypes = $filterData['excludeTypes'];
|
||
}
|
||
}
|
||
|
||
if( $filterData['endpointsBubble'] !== true ){
|
||
$excludeEndpointTypes[] = 'bubble';
|
||
}
|
||
|
||
// search connections -------------------------------------------------------------------------------------
|
||
|
||
if( !empty($includeScopes) ){
|
||
$whereQuery .= " `connection`.`scope` IN ('" . implode("', '", $includeScopes) . "') AND ";
|
||
|
||
if( !empty($excludeTypes) ){
|
||
$whereQuery .= " `connection`.`type` NOT REGEXP '" . implode("|", $excludeTypes) . "' AND ";
|
||
}
|
||
|
||
if( !empty($includeTypes) ){
|
||
$whereQuery .= " `connection`.`type` REGEXP '" . implode("|", $includeTypes) . "' AND ";
|
||
}
|
||
|
||
if(!$includeEOL){
|
||
$whereQuery .= " `connection`.`eolUpdated` IS NULL AND ";
|
||
}
|
||
|
||
if( !empty($excludeEndpointTypes) ){
|
||
$whereQuery .= " CONCAT_WS(' ', `connection`.`sourceEndpointType`, `connection`.`targetEndpointType`) ";
|
||
$whereQuery .= " NOT REGEXP '" . implode("|", $excludeEndpointTypes) . "' AND ";
|
||
}
|
||
|
||
$query = "SELECT
|
||
`system_src`.`systemId` systemSourceId,
|
||
`system_tar`.`systemId` systemTargetId
|
||
FROM
|
||
`connection` INNER JOIN
|
||
`map` ON
|
||
`map`.`id` = `connection`.`mapId` AND
|
||
`map`.`active` = 1 INNER JOIN
|
||
`system` `system_src` ON
|
||
`system_src`.`id` = `connection`.`source` AND
|
||
`system_src`.`active` = 1 INNER JOIN
|
||
`system` `system_tar` ON
|
||
`system_tar`.`id` = `connection`.`target` AND
|
||
`system_tar`.`active` = 1
|
||
WHERE
|
||
" . $whereQuery . "
|
||
`connection`.`active` = 1 AND
|
||
`connection`.`mapId` " . $whereMapIdsQuery . "
|
||
";
|
||
|
||
$rows = $this->getDB()->exec($query, null, $this->dynamicJumpDataCacheTime);
|
||
|
||
if(count($rows) > 0){
|
||
$jumpData = [];
|
||
$universe = new Universe();
|
||
|
||
/**
|
||
* enrich dynamic jump data with static system data (from universe DB)
|
||
* @param array $row
|
||
* @param string $systemSourceKey
|
||
* @param string $systemTargetKey
|
||
*/
|
||
$enrichJumpData = function(array &$row, string $systemSourceKey, string $systemTargetKey) use (&$jumpData, &$universe) {
|
||
if(
|
||
!array_key_exists($row[$systemSourceKey], $jumpData) &&
|
||
!is_null($staticData = $universe->getSystemData($row[$systemSourceKey]))
|
||
){
|
||
$jumpData[$row[$systemSourceKey]] = [
|
||
'systemId' => (int)$row[$systemSourceKey],
|
||
'systemName' => $staticData->name,
|
||
'constellationId' => $staticData->constellation->id,
|
||
'regionId' => $staticData->constellation->region->id,
|
||
'trueSec' => $staticData->trueSec,
|
||
];
|
||
}
|
||
|
||
if( !in_array($row[$systemTargetKey], (array)$jumpData[$row[$systemSourceKey]]['jumpNodes']) ){
|
||
$jumpData[$row[$systemSourceKey]]['jumpNodes'][] = (int)$row[$systemTargetKey];
|
||
}
|
||
};
|
||
|
||
for($i = 0; $i < count($rows); $i++){
|
||
$enrichJumpData($rows[$i], 'systemSourceId', 'systemTargetId');
|
||
$enrichJumpData($rows[$i], 'systemTargetId', 'systemSourceId');
|
||
}
|
||
|
||
$this->updateJumpData($jumpData);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* set current Thera connections jump data for this instance
|
||
* -> Connected wormholes pulled from eve-scout.com
|
||
*/
|
||
private function setTheraJumpData(){
|
||
if(!$this->getF3()->exists(self::CACHE_KEY_THERA_JUMP_DATA, $jumpData)){
|
||
$jumpData = [];
|
||
$connectionsData = $this->getF3()->eveScoutClient()->send('getTheraConnections');
|
||
|
||
if(!empty($connectionsData) && !isset($connectionsData['error'])){
|
||
/**
|
||
* map Thera jump data to Pathfinder format
|
||
* @param array $row
|
||
* @param string $systemSourceKey
|
||
* @param string $systemTargetKey
|
||
*/
|
||
$enrichJumpData = function(array &$row, string $systemSourceKey, string $systemTargetKey) use (&$jumpData) {
|
||
// check if response data is valid
|
||
if(
|
||
is_object($systemSource = $row[$systemSourceKey]) && !empty((array)$systemSource) &&
|
||
is_object($systemTarget = $row[$systemTargetKey]) && !empty((array)$systemTarget)
|
||
){
|
||
if(!array_key_exists($systemSource->id, $jumpData)){
|
||
$jumpData[$systemSource->id] = [
|
||
'systemId' => (int)$systemSource->id,
|
||
'systemName' => $systemSource->name,
|
||
'constellationId' => (int)$systemSource->constellationID,
|
||
'regionId' => (int)$systemSource->regionId,
|
||
'trueSec' => $systemSource->security,
|
||
];
|
||
}
|
||
|
||
if( !in_array((int)$systemTarget->id, (array)$jumpData[$systemSource->id]['jumpNodes']) ){
|
||
$jumpData[$systemSource->id]['jumpNodes'][] = (int)$systemTarget->id;
|
||
}
|
||
}
|
||
};
|
||
|
||
foreach((array)$connectionsData['connections'] as $connectionData){
|
||
$enrichJumpData($connectionData, 'source', 'target');
|
||
$enrichJumpData($connectionData, 'target', 'source');
|
||
}
|
||
|
||
if(!empty($jumpData)){
|
||
$this->getF3()->set(self::CACHE_KEY_THERA_JUMP_DATA, $jumpData, $this->theraJumpDataCacheTime);
|
||
}
|
||
}
|
||
}
|
||
|
||
$this->updateJumpData($jumpData);
|
||
}
|
||
|
||
/**
|
||
* update jump data for this instance
|
||
* -> data is either coming from CCPs [SDE] OR from map specific data
|
||
* @param array $rows
|
||
*/
|
||
private function updateJumpData(&$rows = []){
|
||
foreach($rows as &$row){
|
||
$regionId = (int)$row['regionId'];
|
||
$constId = (int)$row['constellationId'];
|
||
$systemName = (string)($row['systemName']);
|
||
$systemId = (int)$row['systemId'];
|
||
$secStatus = (float)$row['trueSec'];
|
||
|
||
// fill "nameArray" data ----------------------------------------------------------------------------------
|
||
if( !isset($this->nameArray[$systemId]) ){
|
||
$this->nameArray[$systemId][0] = $systemName;
|
||
$this->nameArray[$systemId][1] = $regionId;
|
||
$this->nameArray[$systemId][2] = $constId;
|
||
$this->nameArray[$systemId][3] = $secStatus;
|
||
}
|
||
|
||
// fill "idArray" data ------------------------------------------------------------------------------------
|
||
if( !isset($this->idArray[$systemId]) ){
|
||
$this->idArray[$systemId] = $systemName;
|
||
}
|
||
|
||
// fill "jumpArray" data ----------------------------------------------------------------------------------
|
||
if( !is_array($this->jumpArray[$systemId]) ){
|
||
$this->jumpArray[$systemId] = [];
|
||
}
|
||
$this->jumpArray[$systemId] = array_merge((array)$row['jumpNodes'], $this->jumpArray[$systemId]);
|
||
|
||
// add systemName to end (if not already there)
|
||
if(end($this->jumpArray[$systemId]) != $systemName){
|
||
array_push($this->jumpArray[$systemId], $systemName);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* filter systems (remove some systems) e.g. WH,LS,0.0 for "secure search"
|
||
* @param array $filterData
|
||
* @param array $keepSystems
|
||
*/
|
||
private function filterJumpData($filterData = [], $keepSystems = []){
|
||
if($filterData['flag'] == 'secure'){
|
||
// remove all systems (TrueSec < 0.5) from search arrays
|
||
$this->jumpArray = array_filter($this->jumpArray, function($systemId) use ($keepSystems) {
|
||
$systemNameData = $this->nameArray[$systemId];
|
||
$systemSec = $systemNameData[3];
|
||
|
||
if(
|
||
$systemSec < 0.45 &&
|
||
!in_array($systemId, $keepSystems) &&
|
||
!preg_match('/^j\d+$/i', $this->idArray[$systemId]) // WHs are supposed to be "secure"
|
||
){
|
||
// remove system from nameArray and idArray
|
||
unset($this->nameArray[$systemId]);
|
||
unset($this->idArray[$systemId]);
|
||
return false;
|
||
}else{
|
||
return true;
|
||
}
|
||
}, ARRAY_FILTER_USE_KEY );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* get system data by systemId and dataName
|
||
* @param $systemId
|
||
* @param $option
|
||
* @return null
|
||
*/
|
||
private function getSystemInfoBySystemId($systemId, $option){
|
||
$info = null;
|
||
switch($option){
|
||
case 'systemName':
|
||
$info = $this->nameArray[$systemId][0];
|
||
break;
|
||
case 'regionId':
|
||
$info = $this->nameArray[$systemId][1];
|
||
break;
|
||
case 'constellationId':
|
||
$info = $this->nameArray[$systemId][2];
|
||
break;
|
||
case 'trueSec':
|
||
$info = $this->nameArray[$systemId][3];
|
||
break;
|
||
}
|
||
|
||
return $info;
|
||
}
|
||
|
||
/**
|
||
* recursive search function within a undirected graph
|
||
* @param $G
|
||
* @param $A
|
||
* @param $B
|
||
* @param int $M
|
||
* @return array
|
||
*/
|
||
private function graph_find_path(&$G, $A, $B, $M = 50000){
|
||
$maxDepth = $M;
|
||
|
||
// $P will hold the result path at the end.
|
||
// Remains empty if no path was found.
|
||
$P = [];
|
||
|
||
// For each Node ID create a "visit information",
|
||
// initially set as 0 (meaning not yet visited)
|
||
// as soon as we visit a node we will tag it with the "source"
|
||
// so we can track the path when we reach the search target
|
||
|
||
$V = [];
|
||
|
||
// We are going to keep a list of nodes that are "within reach",
|
||
// initially this list will only contain the start node,
|
||
// then gradually expand (almost like a flood fill)
|
||
$R = [trim($A)];
|
||
|
||
$A = trim($A);
|
||
$B = trim($B);
|
||
|
||
while(count($R) > 0 && $M > 0){
|
||
$M--;
|
||
|
||
$X = trim(array_shift($R));
|
||
|
||
if(array_key_exists($X, $G)){
|
||
foreach($G[$X] as $Y){
|
||
$Y = trim($Y);
|
||
// See if we got a solution
|
||
if($Y == $B){
|
||
// We did? Construct a result path then
|
||
array_push($P, $B);
|
||
array_push($P, $X);
|
||
while($V[$X] != $A){
|
||
array_push($P, trim($V[$X]));
|
||
$X = $V[$X];
|
||
}
|
||
array_push($P, $A);
|
||
//return array_reverse($P);
|
||
return [
|
||
'path'=> array_reverse($P),
|
||
'depth' => ($maxDepth - $M)
|
||
];
|
||
}
|
||
// First time we visit this node?
|
||
if(!array_key_exists($Y, $V)){
|
||
// Store the path so we can track it back,
|
||
$V[$Y] = $X;
|
||
// and add it to the "within reach" list
|
||
array_push($R, $Y);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return [
|
||
'path'=> $P,
|
||
'depth' => ($maxDepth - $M)
|
||
];
|
||
}
|
||
|
||
/**
|
||
* get formatted jump node data
|
||
* @param int $systemId
|
||
* @return array
|
||
*/
|
||
protected function getJumpNodeData(int $systemId) : array {
|
||
return [
|
||
'system' => $this->getSystemInfoBySystemId($systemId, 'systemName'),
|
||
'security' => $this->getSystemInfoBySystemId($systemId, 'trueSec')
|
||
];
|
||
}
|
||
|
||
/**
|
||
* search root between two systemIds
|
||
* -> function searches over ESI API, as fallback a custom search algorithm is used (no ESI)
|
||
* @param int $systemFromId
|
||
* @param int $systemToId
|
||
* @param int $searchDepth
|
||
* @param array $mapIds
|
||
* @param array $filterData
|
||
* @return array
|
||
* @throws \Exception
|
||
*/
|
||
public function searchRoute(int $systemFromId, int $systemToId, $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
|
||
// search root by ESI API
|
||
$routeData = $this->searchRouteESI($systemFromId, $systemToId, $searchDepth, $mapIds, $filterData);
|
||
|
||
// Endpoint return http:404 in case no route find (e.g. from inside a wh)
|
||
// we thread that error "no route found" as a valid response! -> no fallback to custom search
|
||
if(!empty($routeData['error']) && strtolower($routeData['error']) !== 'no route found'){
|
||
// ESI route search has errors -> fallback to custom search implementation
|
||
$routeData = $this->searchRouteCustom($systemFromId, $systemToId, $searchDepth, $mapIds, $filterData);
|
||
}
|
||
|
||
return $routeData;
|
||
}
|
||
|
||
/**
|
||
* uses a custom search algorithm to fine a route
|
||
* @param int $systemFromId
|
||
* @param int $systemToId
|
||
* @param int $searchDepth
|
||
* @param array $mapIds
|
||
* @param array $filterData
|
||
* @return array
|
||
* @throws \Exception
|
||
*/
|
||
private function searchRouteCustom(int $systemFromId, int $systemToId, $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
|
||
// reset all previous set jump data
|
||
$this->resetJumpData();
|
||
|
||
$searchDepth = $searchDepth ? $searchDepth : Config::getPathfinderData('route.search_depth');
|
||
|
||
$routeData = $this->defaultRouteData;
|
||
$routeData['maxDepth'] = $searchDepth;
|
||
$routeData['searchType'] = 'custom';
|
||
|
||
if($systemFromId && $systemToId){
|
||
// prepare search data ------------------------------------------------------------------------------------
|
||
// add static data (e.g. K-Space stargates,..)
|
||
$this->setStaticJumpData();
|
||
|
||
// add map specific data
|
||
$this->setDynamicJumpData($mapIds, $filterData);
|
||
|
||
// add current Thera connections data
|
||
if($filterData['wormholesThera']){
|
||
$this->setTheraJumpData();
|
||
}
|
||
|
||
// filter jump data (e.g. remove some systems (0.0, LS)
|
||
// --> don´t filter some systems (e.g. systemFrom, systemTo) even if they are are WH,LS,0.0
|
||
$this->filterJumpData($filterData, [$systemFromId, $systemToId]);
|
||
|
||
// search route -------------------------------------------------------------------------------------------
|
||
|
||
// jump counter
|
||
$jumpNum = 0;
|
||
$depthSearched = 0;
|
||
|
||
if(isset($this->jumpArray[$systemFromId])){
|
||
// check if the system we are looking for is a direct neighbour
|
||
foreach($this->jumpArray[$systemFromId] as $n){
|
||
if($n == $systemToId){
|
||
$jumpNum = 2;
|
||
$routeData['route'][] = $this->getJumpNodeData($n);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// system is not a direct neighbour -> search recursive its neighbours
|
||
if($jumpNum == 0){
|
||
$searchResult = $this->graph_find_path( $this->jumpArray, $systemFromId, $systemToId, $searchDepth );
|
||
$depthSearched = $searchResult['depth'];
|
||
foreach($searchResult['path'] as $systemId){
|
||
if($jumpNum > 0){
|
||
$routeData['route'][] = $this->getJumpNodeData($systemId);
|
||
}
|
||
$jumpNum++;
|
||
}
|
||
}
|
||
|
||
if($jumpNum > 0){
|
||
// route found
|
||
$routeData['routePossible'] = true;
|
||
// insert "from" system on top
|
||
array_unshift($routeData['route'], $this->getJumpNodeData($systemFromId));
|
||
}else{
|
||
// route not found
|
||
$routeData['routePossible'] = false;
|
||
}
|
||
}
|
||
|
||
// route jumps
|
||
$routeData['routeJumps'] = $jumpNum - 1;
|
||
$routeData['depthSearched'] = $depthSearched;
|
||
}
|
||
|
||
return $routeData;
|
||
}
|
||
|
||
/**
|
||
* uses ESI route search endpoint to fine a route
|
||
* @param int $systemFromId
|
||
* @param int $systemToId
|
||
* @param int $searchDepth
|
||
* @param array $mapIds
|
||
* @param array $filterData
|
||
* @return array
|
||
* @throws \Exception
|
||
*/
|
||
private function searchRouteESI(int $systemFromId, int $systemToId, int $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
|
||
// reset all previous set jump data
|
||
$this->resetJumpData();
|
||
|
||
$searchDepth = $searchDepth ? $searchDepth : Config::getPathfinderData('route.search_depth');
|
||
|
||
$routeData = $this->defaultRouteData;
|
||
$routeData['maxDepth'] = $searchDepth;
|
||
$routeData['searchType'] = 'esi';
|
||
|
||
if($systemFromId && $systemToId){
|
||
// ESI route search can only handle 50 $connections (100 entries)
|
||
// we want to add NON stargate connections ONLY for ESI route search
|
||
// because ESI will use them anyways!
|
||
$filterData['stargates'] = false;
|
||
|
||
// prepare search data ------------------------------------------------------------------------------------
|
||
|
||
// add map specific data
|
||
$this->setDynamicJumpData($mapIds, $filterData);
|
||
|
||
// add current Thera connections data
|
||
if($filterData['wormholesThera']){
|
||
$this->setTheraJumpData();
|
||
}
|
||
|
||
// filter jump data (e.g. remove some systems (0.0, LS)
|
||
// --> don´t filter some systems (e.g. systemFrom, systemTo) even if they are are WH,LS,0.0
|
||
$this->filterJumpData($filterData, [$systemFromId, $systemToId]);
|
||
|
||
// pre-populate connections array with new connected_pairs from TrailBlazer expansion because
|
||
// they have not been added to ESI routes. Can be removed when ESI is updated
|
||
$connections = [
|
||
[30001721,30001957], // Saminer => F7-ICZ
|
||
[30001957,30001721], // F7-ICZ => Saminer
|
||
[30003605,30003823], // Kennink => Eggheron
|
||
[30003823,30003605], // Eggheron => Kennink
|
||
[30003452,30005198], // Pakhshi => Irgrus
|
||
[30005198,30003452], // Irgrus => Pakhshi
|
||
[30000134,30005196], // Hykkota => Ahbazon
|
||
[30005196,30000134], // Ahbazon => Hykkota
|
||
];
|
||
foreach($this->jumpArray as $systemSourceId => $jumpData){
|
||
$count = count($jumpData);
|
||
if($count > 1){
|
||
// ... should always > 1
|
||
// loop all connections for current source system
|
||
foreach($jumpData as $systemTargetId){
|
||
// skip last entry
|
||
if(--$count <= 0){
|
||
break;
|
||
}
|
||
|
||
// systemIds exist and wer not removed before in filterJumpData()
|
||
if($systemSourceId && $systemTargetId){
|
||
$jumpNode = [$systemSourceId, $systemTargetId];
|
||
// jumpNode must be unique for ESI,
|
||
// ... there can be multiple connections between same systems in Pathfinder
|
||
if(!in_array($jumpNode, $connections)){
|
||
$connections[] = [$systemSourceId, $systemTargetId];
|
||
// check if connections limit is reached
|
||
if(count($connections) >= self::MAX_CONNECTION_COUNT){
|
||
// ESI API limit for custom "connections"
|
||
break 2;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// search route -------------------------------------------------------------------------------------------
|
||
$options = [
|
||
'flag' => $filterData['flag'],
|
||
'connections' => $connections
|
||
];
|
||
|
||
$result = $this->getF3()->ccpClient()->send('getRoute', $systemFromId, $systemToId, $options);
|
||
|
||
// format result ------------------------------------------------------------------------------------------
|
||
|
||
// jump counter
|
||
$jumpNum = 0;
|
||
$depthSearched = 0;
|
||
if( !empty($result['error']) ){
|
||
$routeData['error'] = $result['error'];
|
||
}elseif( !empty($result['route']) ){
|
||
$jumpNum = count($result['route']) - 1;
|
||
|
||
// check max search depth
|
||
if($jumpNum <= $routeData['maxDepth']){
|
||
$depthSearched = $jumpNum;
|
||
|
||
$routeData['routePossible'] = true;
|
||
|
||
// Now (after search) we have to "add" static jump data information
|
||
$this->setStaticJumpData();
|
||
|
||
foreach($result['route'] as $systemId){
|
||
$routeData['route'][] = $this->getJumpNodeData($systemId);
|
||
}
|
||
}else{
|
||
$depthSearched = $routeData['maxDepth'];
|
||
}
|
||
}
|
||
|
||
// route jumps
|
||
$routeData['routeJumps'] = $jumpNum;
|
||
$routeData['depthSearched'] = $depthSearched;
|
||
}
|
||
|
||
return $routeData;
|
||
}
|
||
|
||
/**
|
||
* get key for route cache
|
||
* @param $mapIds
|
||
* @param $systemFrom
|
||
* @param $systemTo
|
||
* @param array $filterData
|
||
* @return string
|
||
*/
|
||
private function getRouteCacheKey($mapIds, $systemFrom, $systemTo, $filterData = []){
|
||
|
||
$keyParts = [
|
||
implode('_', $mapIds),
|
||
self::formatHiveKey($systemFrom),
|
||
self::formatHiveKey($systemTo)
|
||
];
|
||
|
||
$keyParts += $filterData;
|
||
return 'route_' . hash('md5', implode('_', $keyParts));
|
||
}
|
||
|
||
/**
|
||
* search multiple route between two systems
|
||
* @param \Base $f3
|
||
* @throws \Exception
|
||
*/
|
||
public function post(\Base $f3){
|
||
$requestData = $this->getRequestData($f3);
|
||
|
||
$activeCharacter = $this->getCharacter();
|
||
|
||
$return = (object) [];
|
||
$return->error = [];
|
||
$return->routesData = [];
|
||
|
||
if( !empty($requestData['routeData']) ){
|
||
$routesData = (array)$requestData['routeData'];
|
||
|
||
// map data where access was already checked -> cached data
|
||
$validMaps = [];
|
||
|
||
/**
|
||
* @var $map Pathfinder\MapModel
|
||
*/
|
||
$map = Pathfinder\AbstractPathfinderModel::getNew('MapModel');
|
||
|
||
// limit max search routes to max limit
|
||
array_splice($routesData, Config::getPathfinderData('route.limit'));
|
||
|
||
foreach($routesData as $key => $routeData){
|
||
// mapIds are optional. If mapIds is empty or not set
|
||
// route search is limited to CCPs static data
|
||
$mapData = (array)$routeData['mapIds'];
|
||
$mapData = array_flip( array_map('intval', $mapData) );
|
||
|
||
// check map access (filter requested mapIDs and format) ----------------------------------------------
|
||
array_walk($mapData, function(&$item, &$key, $data){
|
||
/**
|
||
* @var Pathfinder\MapModel $data[0]
|
||
*/
|
||
if( isset($data[1][$key]) ){
|
||
// character has map access -> do not check again
|
||
$item = $data[1][$key];
|
||
}else{
|
||
// check map access for current character
|
||
$data[0]->getById($key);
|
||
|
||
if( $data[0]->hasAccess($data[2]) ){
|
||
$item = ['id' => $key, 'name' => $data[0]->name];
|
||
}else{
|
||
$item = false;
|
||
}
|
||
$data[0]->reset();
|
||
}
|
||
|
||
}, [$map, $validMaps, $activeCharacter]);
|
||
|
||
// filter maps with NO access right
|
||
$mapData = array_filter($mapData);
|
||
$mapIds = array_column($mapData, 'id');
|
||
|
||
// add map data to cache array
|
||
$validMaps += $mapData;
|
||
|
||
// search route with filter options
|
||
$filterData = [
|
||
'stargates' => (bool) $routeData['stargates'],
|
||
'jumpbridges' => (bool) $routeData['jumpbridges'],
|
||
'wormholes' => (bool) $routeData['wormholes'],
|
||
'wormholesReduced' => (bool) $routeData['wormholesReduced'],
|
||
'wormholesCritical' => (bool) $routeData['wormholesCritical'],
|
||
'wormholesEOL' => (bool) $routeData['wormholesEOL'],
|
||
'wormholesThera' => (bool) $routeData['wormholesThera'],
|
||
'wormholesSizeMin' => (string) $routeData['wormholesSizeMin'],
|
||
'excludeTypes' => (array) $routeData['excludeTypes'],
|
||
'endpointsBubble' => (bool) $routeData['endpointsBubble'],
|
||
'flag' => $routeData['flag']
|
||
];
|
||
|
||
$returnRoutData = [
|
||
'systemFromData' => $routeData['systemFromData'],
|
||
'systemToData' => $routeData['systemToData'],
|
||
'skipSearch' => (bool) $routeData['skipSearch'],
|
||
'maps' => $mapData,
|
||
'mapIds' => $mapIds
|
||
];
|
||
|
||
// add filter options for each route as well
|
||
$returnRoutData += $filterData;
|
||
|
||
if(
|
||
!$returnRoutData['skipSearch'] &&
|
||
count($mapIds) > 0
|
||
){
|
||
$systemFrom = $routeData['systemFromData']['name'];
|
||
$systemFromId = (int)$routeData['systemFromData']['systemId'];
|
||
$systemTo = $routeData['systemToData']['name'];
|
||
$systemToId = (int)$routeData['systemToData']['systemId'];
|
||
|
||
$cacheKey = $this->getRouteCacheKey(
|
||
$mapIds,
|
||
$systemFrom,
|
||
$systemTo,
|
||
$filterData
|
||
);
|
||
|
||
if($f3->exists($cacheKey, $cachedData)){
|
||
// get data from cache
|
||
$returnRoutData = $cachedData;
|
||
}else{
|
||
$foundRoutData = $this->searchRoute($systemFromId, $systemToId, 0, $mapIds, $filterData);
|
||
|
||
$returnRoutData = array_merge($returnRoutData, $foundRoutData);
|
||
|
||
// cache if route was found
|
||
if(
|
||
isset($returnRoutData['routePossible']) &&
|
||
$returnRoutData['routePossible'] === true
|
||
){
|
||
$f3->set($cacheKey, $returnRoutData, $this->dynamicJumpDataCacheTime);
|
||
}
|
||
}
|
||
}
|
||
|
||
$return->routesData[] = $returnRoutData;
|
||
}
|
||
}
|
||
|
||
$this->out($return);
|
||
}
|
||
} |