smrealms /
smr
We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
| 1 | <?php declare(strict_types=1); |
||
| 2 | |||
| 3 | // Use this exception to help override container forwarding for NPC's |
||
| 4 | class ForwardException extends Exception {} |
||
| 5 | |||
| 6 | function overrideForward($container) { |
||
| 7 | global $forwardedContainer; |
||
| 8 | $forwardedContainer = $container; |
||
| 9 | if ($container['body'] == 'error.php') { |
||
| 10 | // We hit a create_error - this shouldn't happen for an NPC often, |
||
| 11 | // for now we want to throw an exception for it for testing. |
||
| 12 | debug('Hit an error'); |
||
| 13 | throw new Exception($container['message']); |
||
| 14 | } |
||
| 15 | // We have to throw the exception to get back up the stack, |
||
| 16 | // otherwise we quickly hit problems of overflowing the stack. |
||
| 17 | throw new ForwardException; |
||
| 18 | } |
||
| 19 | const OVERRIDE_FORWARD = true; |
||
| 20 | |||
| 21 | // Must be defined before anything that might throw an exception |
||
| 22 | const NPC_SCRIPT = true; |
||
| 23 | |||
| 24 | // global config |
||
| 25 | require_once(realpath(dirname(__FILE__)) . '/../../bootstrap.php'); |
||
| 26 | // bot config |
||
| 27 | require_once(CONFIG . 'npc/config.specific.php'); |
||
| 28 | // needed libs |
||
| 29 | require_once(get_file_loc('smr.inc.php')); |
||
| 30 | require_once(get_file_loc('shop_goods.inc.php')); |
||
| 31 | |||
| 32 | // Raise exceptions for all types of errors for improved error reporting |
||
| 33 | // and to attempt to shut down the NPCs cleanly on errors. |
||
| 34 | set_error_handler("exception_error_handler"); |
||
| 35 | |||
| 36 | const SHIP_UPGRADE_PATH = array( |
||
| 37 | RACE_ALSKANT => array( |
||
| 38 | SHIP_TYPE_TRADE_MASTER, |
||
| 39 | SHIP_TYPE_TRIP_MAKER, |
||
| 40 | SHIP_TYPE_SMALL_TIMER |
||
| 41 | ), |
||
| 42 | RACE_CREONTI => array( |
||
| 43 | SHIP_TYPE_LEVIATHAN, |
||
| 44 | SHIP_TYPE_MEDIUM_CARGO_HULK |
||
| 45 | ), |
||
| 46 | RACE_HUMAN => array( |
||
| 47 | SHIP_TYPE_AMBASSADOR, |
||
| 48 | SHIP_TYPE_RENAISSANCE, |
||
| 49 | SHIP_TYPE_LIGHT_FREIGHTER |
||
| 50 | ), |
||
| 51 | RACE_IKTHORNE => array( |
||
| 52 | SHIP_TYPE_FAVOURED_OFFSPRING, |
||
| 53 | SHIP_TYPE_PROTO_CARRIER, |
||
| 54 | SHIP_TYPE_TINY_DELIGHT |
||
| 55 | ), |
||
| 56 | RACE_SALVENE => array( |
||
| 57 | SHIP_TYPE_DRUDGE, |
||
| 58 | SHIP_TYPE_HATCHLINGS_DUE |
||
| 59 | ), |
||
| 60 | RACE_THEVIAN => array( |
||
| 61 | SHIP_TYPE_EXPEDITER, |
||
| 62 | SHIP_TYPE_SWIFT_VENTURE |
||
| 63 | ), |
||
| 64 | RACE_WQHUMAN => array( |
||
| 65 | SHIP_TYPE_BLOCKADE_RUNNER, |
||
| 66 | SHIP_TYPE_NEGOTIATOR, |
||
| 67 | SHIP_TYPE_SLIP_FREIGHTER |
||
| 68 | ), |
||
| 69 | RACE_NIJARIN => array( |
||
| 70 | SHIP_TYPE_VENGEANCE, |
||
| 71 | SHIP_TYPE_REDEEMER |
||
| 72 | ) |
||
| 73 | ); |
||
| 74 | |||
| 75 | |||
| 76 | try { |
||
| 77 | $db = Smr\Database::getInstance(); |
||
| 78 | debug('Script started'); |
||
| 79 | |||
| 80 | // Make sure NPC's have been set up in the database |
||
| 81 | $db->query('SELECT 1 FROM npc_logins LIMIT 1'); |
||
| 82 | if (!$db->nextRecord()) { |
||
| 83 | debug('No NPCs have been created yet!'); |
||
| 84 | exit; |
||
| 85 | } |
||
| 86 | |||
| 87 | try { |
||
| 88 | changeNPCLogin(); |
||
| 89 | } catch (ForwardException $e) {} |
||
| 90 | |||
| 91 | NPCStuff(); |
||
| 92 | } catch (Throwable $e) { |
||
| 93 | logException($e); |
||
| 94 | // Try to shut down cleanly |
||
| 95 | exitNPC(); |
||
| 96 | } |
||
| 97 | |||
| 98 | |||
| 99 | function NPCStuff() { |
||
| 100 | global $actions, $previousContainer; |
||
| 101 | |||
| 102 | $underAttack = false; |
||
| 103 | $actions = -1; |
||
| 104 | |||
| 105 | $session = Smr\Session::getInstance(); |
||
| 106 | $db = Smr\Database::getInstance(); |
||
|
0 ignored issues
–
show
Unused Code
introduced
by
Loading history...
|
|||
| 107 | |||
| 108 | while (true) { |
||
| 109 | // Clear the $_REQUEST global, in case we had set it, to avoid |
||
| 110 | // contaminating subsequent page processing. |
||
| 111 | $_REQUEST = []; |
||
| 112 | |||
| 113 | $actions++; |
||
| 114 | |||
| 115 | // Avoid infinite loops by restricting the number of actions |
||
| 116 | if ($actions > NPC_MAX_ACTIONS) { |
||
| 117 | debug('Reached maximum number of actions: ' . NPC_MAX_ACTIONS); |
||
| 118 | changeNPCLogin(); |
||
| 119 | } |
||
| 120 | |||
| 121 | try { |
||
| 122 | $TRADE_ROUTE =& $GLOBALS['TRADE_ROUTE']; |
||
| 123 | debug('Action #' . $actions); |
||
| 124 | |||
| 125 | //We have to reload player on each loop |
||
| 126 | $player = $session->getPlayer(true); |
||
| 127 | // Sanity check to be certain we actually have an NPC |
||
| 128 | if (!$player->isNPC()) { |
||
| 129 | throw new Exception('Player is not an NPC!'); |
||
| 130 | } |
||
| 131 | $player->updateTurns(); |
||
| 132 | |||
| 133 | if ($actions == 0) { |
||
| 134 | if ($player->getTurns() <= rand($player->getMaxTurns() / 2, $player->getMaxTurns()) && ($player->hasNewbieTurns() || $player->hasFederalProtection())) { |
||
| 135 | debug('We don\'t have enough turns to bother starting trading, and we are protected: ' . $player->getTurns()); |
||
| 136 | changeNPCLogin(); |
||
| 137 | } |
||
| 138 | |||
| 139 | // Ensure the NPC doesn't think it's under attack at startup, |
||
| 140 | // since this could cause it to get stuck in a loop in Fed. |
||
| 141 | $player->removeUnderAttack(); |
||
| 142 | $player->update(); |
||
| 143 | } |
||
| 144 | |||
| 145 | if (!isset($TRADE_ROUTE)) { //We only want to change trade route if there isn't already one set. |
||
| 146 | $TRADE_ROUTES =& findRoutes($player); |
||
| 147 | $TRADE_ROUTE =& changeRoute($TRADE_ROUTES); |
||
| 148 | } |
||
| 149 | |||
| 150 | if ($player->isDead()) { |
||
| 151 | debug('Some evil person killed us, let\'s move on now.'); |
||
| 152 | $previousContainer = null; //We died, we don't care what we were doing beforehand. |
||
| 153 | $TRADE_ROUTE =& changeRoute($TRADE_ROUTES); //Change route |
||
| 154 | processContainer(Page::create('death_processing.php')); |
||
| 155 | } |
||
| 156 | if ($player->getNewbieTurns() <= NEWBIE_TURNS_WARNING_LIMIT && $player->getNewbieWarning()) { |
||
| 157 | processContainer(Page::create('newbie_warning_processing.php')); |
||
| 158 | } |
||
| 159 | |||
| 160 | $fedContainer = null; |
||
| 161 | $var = $session->getCurrentVar(); |
||
| 162 | if (isset($var['url']) && $var['url'] == 'shop_ship_processing.php' && ($fedContainer = plotToFed($player, true)) !== true) { //We just bought a ship, we should head back to our trade gal/uno - we use HQ for now as it's both in our gal and a UNO, plus it's safe which is always a bonus |
||
| 163 | processContainer($fedContainer); |
||
| 164 | } elseif (!$underAttack && $player->isUnderAttack() === true |
||
| 165 | && ($player->hasPlottedCourse() === false || $player->getPlottedCourse()->getEndSector()->offersFederalProtection() === false) |
||
| 166 | && ($fedContainer == null ? $fedContainer = plotToFed($player, true) : $fedContainer) !== true) { |
||
| 167 | // We're under attack and need to plot course to fed. |
||
| 168 | debug('Under Attack'); |
||
| 169 | $underAttack = true; |
||
| 170 | processContainer($fedContainer); |
||
| 171 | } elseif ($player->hasPlottedCourse() === true && $player->getPlottedCourse()->getEndSector()->offersFederalProtection()) { //We have a route to fed to follow, figure it's probably a damned sensible thing to follow. |
||
| 172 | debug('Follow Course: ' . $player->getPlottedCourse()->getNextOnPath()); |
||
| 173 | processContainer(moveToSector($player, $player->getPlottedCourse()->getNextOnPath())); |
||
| 174 | } elseif (($container = canWeUNO($player, true)) !== false) { //We have money and are at a uno, let's uno! |
||
| 175 | debug('We\'re UNOing'); |
||
| 176 | processContainer($container); |
||
| 177 | } elseif ($player->hasPlottedCourse() === true) { //We have a route to follow, figure it's probably a sensible thing to follow. |
||
| 178 | debug('Follow Course: ' . $player->getPlottedCourse()->getNextOnPath()); |
||
| 179 | processContainer(moveToSector($player, $player->getPlottedCourse()->getNextOnPath())); |
||
| 180 | } elseif ($player->getTurns() < NPC_LOW_TURNS || ($player->getTurns() < $player->getMaxTurns() / 2 && $player->getNewbieTurns() < NPC_LOW_NEWBIE_TURNS) || $underAttack) { //We're low on turns or have been under attack and need to plot course to fed |
||
| 181 | if ($player->getTurns() < NPC_LOW_TURNS) { |
||
| 182 | debug('Low Turns:' . $player->getTurns()); |
||
| 183 | } |
||
| 184 | if ($underAttack) { |
||
| 185 | debug('Fedding after attack.'); |
||
| 186 | } |
||
| 187 | if ($player->hasNewbieTurns()) { //We have newbie turns, we can just wait here. |
||
| 188 | debug('We have newbie turns, let\'s just switch to another NPC.'); |
||
| 189 | changeNPCLogin(); |
||
| 190 | } |
||
| 191 | if ($player->hasFederalProtection()) { |
||
| 192 | debug('We are in fed, time to switch to another NPC.'); |
||
| 193 | changeNPCLogin(); |
||
| 194 | } |
||
| 195 | $ship = $player->getShip(); |
||
| 196 | processContainer(plotToFed($player, !$ship->hasMaxShields() || !$ship->hasMaxArmour() || !$ship->hasMaxCargoHolds())); |
||
| 197 | } elseif (($container = checkForShipUpgrade($player)) !== false) { //We have money and are at a uno, let's uno! |
||
| 198 | debug('We\'re reshipping!'); |
||
| 199 | processContainer($container); |
||
| 200 | } elseif (($container = canWeUNO($player, false)) !== false) { //We need to UNO and have enough money to do it properly so let's do it sooner rather than later. |
||
| 201 | debug('We need to UNO, so off we go!'); |
||
| 202 | processContainer($container); |
||
| 203 | } elseif ($TRADE_ROUTE instanceof \Routes\Route) { |
||
| 204 | debug('Trade Route'); |
||
| 205 | $forwardRoute = $TRADE_ROUTE->getForwardRoute(); |
||
| 206 | $returnRoute = $TRADE_ROUTE->getReturnRoute(); |
||
| 207 | if ($forwardRoute->getBuySectorId() == $player->getSectorID() || $returnRoute->getBuySectorId() == $player->getSectorID()) { |
||
| 208 | if ($forwardRoute->getBuySectorId() == $player->getSectorID()) { |
||
| 209 | $buyRoute = $forwardRoute; |
||
| 210 | $sellRoute = $returnRoute; |
||
| 211 | } elseif ($returnRoute->getBuySectorId() == $player->getSectorID()) { |
||
| 212 | $buyRoute = $returnRoute; |
||
| 213 | $sellRoute = $forwardRoute; |
||
| 214 | } |
||
| 215 | |||
| 216 | $ship = $player->getShip(); |
||
| 217 | if ($ship->getUsedHolds() > 0) { |
||
| 218 | if ($ship->hasCargo($sellRoute->getGoodID())) { //Sell goods |
||
| 219 | $goodID = $sellRoute->getGoodID(); |
||
| 220 | |||
| 221 | $port = $player->getSector()->getPort(); |
||
| 222 | $tradeable = checkPortTradeable($port, $player); |
||
| 223 | |||
| 224 | if ($tradeable === true && $port->getGoodAmount($goodID) >= $ship->getCargo($sellRoute->getGoodID())) { //TODO: Sell what we can rather than forcing sell all at once? |
||
| 225 | //Sell goods |
||
| 226 | debug('Sell Goods'); |
||
| 227 | processContainer(tradeGoods($goodID, $player, $port)); |
||
| 228 | } else { |
||
| 229 | //Move to next route or fed. |
||
| 230 | if (($TRADE_ROUTE =& changeRoute($TRADE_ROUTES)) === false) { |
||
| 231 | debug('Changing Route Failed'); |
||
| 232 | processContainer(plotToFed($player)); |
||
| 233 | } else { |
||
| 234 | debug('Route Changed'); |
||
| 235 | continue; |
||
| 236 | } |
||
| 237 | } |
||
| 238 | } elseif ($ship->hasCargo($buyRoute->getGoodID()) === true) { //We've bought goods, plot to sell |
||
| 239 | debug('Plot To Sell: ' . $buyRoute->getSellSectorId()); |
||
| 240 | processContainer(plotToSector($player, $buyRoute->getSellSectorId())); |
||
| 241 | } else { |
||
| 242 | //Dump goods |
||
| 243 | debug('Dump Goods'); |
||
| 244 | processContainer(dumpCargo($player)); |
||
| 245 | } |
||
| 246 | } else { //Buy goods |
||
| 247 | $goodID = $buyRoute->getGoodID(); |
||
| 248 | |||
| 249 | $port = $player->getSector()->getPort(); |
||
| 250 | $tradeable = checkPortTradeable($port, $player); |
||
| 251 | |||
| 252 | if ($tradeable === true && $port->getGoodAmount($goodID) >= $ship->getEmptyHolds()) { //Buy goods |
||
| 253 | debug('Buy Goods'); |
||
| 254 | processContainer(tradeGoods($goodID, $player, $port)); |
||
| 255 | } else { |
||
| 256 | //Move to next route or fed. |
||
| 257 | if (($TRADE_ROUTE =& changeRoute($TRADE_ROUTES)) === false) { |
||
| 258 | debug('Changing Route Failed'); |
||
| 259 | processContainer(plotToFed($player)); |
||
| 260 | } else { |
||
| 261 | debug('Route Changed'); |
||
| 262 | continue; |
||
| 263 | } |
||
| 264 | } |
||
| 265 | } |
||
| 266 | } else { |
||
| 267 | debug('Plot To Buy: ' . $forwardRoute->getBuySectorId()); |
||
| 268 | processContainer(plotToSector($player, $forwardRoute->getBuySectorId())); |
||
| 269 | } |
||
| 270 | } else { //Something weird is going on.. Let's fed and wait. |
||
| 271 | debug('No actual action? Wtf?'); |
||
| 272 | processContainer(plotToFed($player)); |
||
| 273 | } |
||
| 274 | /* |
||
| 275 | else { //Otherwise let's run around at random. |
||
| 276 | $links = $player->getSector()->getLinks(); |
||
| 277 | $moveTo = $links[array_rand($links)]; |
||
| 278 | debug('Random Wanderings: '.$moveTo); |
||
| 279 | processContainer(moveToSector($player,$moveTo)); |
||
| 280 | } |
||
| 281 | */ |
||
| 282 | } catch (ForwardException $e) { |
||
| 283 | global $lock; |
||
| 284 | if ($lock) { //only save if we have the lock. |
||
| 285 | SmrSector::saveSectors(); |
||
| 286 | SmrShip::saveShips(); |
||
| 287 | SmrPlayer::savePlayers(); |
||
| 288 | SmrForce::saveForces(); |
||
| 289 | SmrPort::savePorts(); |
||
| 290 | if (class_exists('WeightedRandom', false)) { |
||
| 291 | WeightedRandom::saveWeightedRandoms(); |
||
| 292 | } |
||
| 293 | release_lock(); |
||
| 294 | } |
||
| 295 | //Clean up the caches as the data may get changed by other players |
||
| 296 | clearCaches(); |
||
| 297 | //Clear up some global vars |
||
| 298 | global $locksFailed; |
||
| 299 | $locksFailed = array(); |
||
| 300 | $_REQUEST = array(); |
||
| 301 | //Have a sleep between actions |
||
| 302 | sleepNPC(); |
||
| 303 | } |
||
| 304 | } |
||
| 305 | debug('Actions Finished.'); |
||
| 306 | exitNPC(); |
||
| 307 | } |
||
| 308 | |||
| 309 | function clearCaches() { |
||
| 310 | SmrSector::clearCache(); |
||
| 311 | SmrPlayer::clearCache(); |
||
| 312 | SmrShip::clearCache(); |
||
| 313 | SmrForce::clearCache(); |
||
| 314 | SmrPort::clearCache(); |
||
| 315 | } |
||
| 316 | |||
| 317 | function debug($message, $debugObject = null) { |
||
| 318 | echo date('Y-m-d H:i:s - ') . $message . ($debugObject !== null ?EOL.var_export($debugObject, true) : '') . EOL; |
||
| 319 | if (NPC_LOG_TO_DATABASE) { |
||
| 320 | $session = Smr\Session::getInstance(); |
||
| 321 | $accountID = $session->getAccountID(); |
||
| 322 | $var = $session->getCurrentVar(); |
||
| 323 | $db = Smr\Database::getInstance(); |
||
| 324 | $db->query('INSERT INTO npc_logs (script_id, npc_id, time, message, debug_info, var) VALUES (' . (defined('SCRIPT_ID') ?SCRIPT_ID:0) . ', ' . $accountID() . ',NOW(),' . $db->escapeString($message) . ',' . $db->escapeString(var_export($debugObject, true)) . ',' . $db->escapeString(var_export($var, true)) . ')'); |
||
| 325 | |||
| 326 | // On the first call to debug, we need to update the script_id retroactively |
||
| 327 | if (!defined('SCRIPT_ID')) { |
||
| 328 | define('SCRIPT_ID', $db->getInsertID()); |
||
| 329 | $db->query('UPDATE npc_logs SET script_id=' . SCRIPT_ID . ' WHERE log_id=' . SCRIPT_ID); |
||
| 330 | } |
||
| 331 | } |
||
| 332 | } |
||
| 333 | |||
| 334 | function processContainer($container) { |
||
| 335 | global $forwardedContainer, $previousContainer; |
||
| 336 | $session = Smr\Session::getInstance(); |
||
| 337 | $player = $session->getPlayer(); |
||
| 338 | if ($container == $previousContainer && $forwardedContainer['body'] != 'forces_attack.php') { |
||
| 339 | debug('We are executing the same container twice?', array('ForwardedContainer' => $forwardedContainer, 'Container' => $container)); |
||
| 340 | if ($player->hasNewbieTurns() || $player->hasFederalProtection()) { |
||
| 341 | // Only throw the exception if we have protection, otherwise let's hope that the NPC will be able to find its way to safety rather than dying in the open. |
||
| 342 | throw new Exception('We are executing the same container twice?'); |
||
| 343 | } |
||
| 344 | } |
||
| 345 | clearCaches(); //Clear caches of anything we have used for decision making before processing container and getting lock. |
||
| 346 | $previousContainer = $container; |
||
| 347 | debug('Executing container', $container); |
||
| 348 | // The next "page request" must occur at an updated time. |
||
| 349 | Smr\Epoch::update(); |
||
| 350 | $session->setCurrentVar($container, false); |
||
| 351 | acquire_lock($player->getSectorID()); // Lock now to skip var update in do_voodoo |
||
| 352 | do_voodoo(); |
||
| 353 | } |
||
| 354 | |||
| 355 | function sleepNPC() { |
||
| 356 | usleep(rand(MIN_SLEEP_TIME, MAX_SLEEP_TIME)); //Sleep for a random time |
||
| 357 | } |
||
| 358 | |||
| 359 | // Releases an NPC when it is done working |
||
| 360 | function releaseNPC() { |
||
| 361 | $session = Smr\Session::getInstance(); |
||
| 362 | if (!$session->hasAccount()) { |
||
| 363 | debug('releaseNPC: no NPC to release'); |
||
| 364 | return; |
||
| 365 | } |
||
| 366 | $login = $session->getAccount()->getLogin(); |
||
| 367 | $db = Smr\Database::getInstance(); |
||
| 368 | $db->query('UPDATE npc_logins SET working=' . $db->escapeBoolean(false) . ' WHERE login=' . $db->escapeString($login)); |
||
| 369 | if ($db->getChangedRows() > 0) { |
||
| 370 | debug('Released NPC: ' . $login); |
||
| 371 | } else { |
||
| 372 | debug('Failed to release NPC: ' . $login); |
||
| 373 | } |
||
| 374 | } |
||
| 375 | |||
| 376 | function exitNPC() { |
||
| 377 | debug('Exiting NPC script.'); |
||
| 378 | releaseNPC(); |
||
| 379 | release_lock(); |
||
| 380 | exit; |
||
| 381 | } |
||
| 382 | |||
| 383 | function changeNPCLogin() { |
||
| 384 | global $actions, $previousContainer; |
||
| 385 | if ($actions > 0) { |
||
| 386 | debug('We have taken actions and now want to change NPC, let\'s exit and let next script choose a new NPC to reset execution time', getrusage()); |
||
| 387 | exitNPC(); |
||
| 388 | } |
||
| 389 | |||
| 390 | $actions = -1; |
||
| 391 | $GLOBALS['TRADE_ROUTE'] = null; |
||
| 392 | |||
| 393 | // Release previous NPC, if any |
||
| 394 | releaseNPC(); |
||
| 395 | |||
| 396 | // We chose a new NPC, we don't care what we were doing beforehand. |
||
| 397 | $previousContainer = null; |
||
| 398 | |||
| 399 | // Lacking a convenient way to get up-to-date turns, order NPCs by how |
||
| 400 | // recently they have taken an action. |
||
| 401 | debug('Choosing new NPC'); |
||
| 402 | static $availableNpcs = null; |
||
| 403 | |||
| 404 | $db = Smr\Database::getInstance(); |
||
| 405 | $session = Smr\Session::getInstance(); |
||
| 406 | |||
| 407 | if (is_null($availableNpcs)) { |
||
| 408 | // Make sure to select NPCs from active games only |
||
| 409 | $db->query('SELECT account_id, game_id FROM player JOIN account USING(account_id) JOIN npc_logins USING(login) JOIN game USING(game_id) WHERE active=\'TRUE\' AND working=\'FALSE\' AND start_time < ' . $db->escapeNumber(Smr\Epoch::time()) . ' AND end_time > ' . $db->escapeNumber(Smr\Epoch::time()) . ' ORDER BY last_turn_update ASC'); |
||
| 410 | while ($db->nextRecord()) { |
||
| 411 | $availableNpcs[] = [ |
||
| 412 | 'account_id' => $db->getInt('account_id'), |
||
| 413 | 'game_id' => $db->getInt('game_id'), |
||
| 414 | ]; |
||
| 415 | } |
||
| 416 | } |
||
| 417 | |||
| 418 | if (empty($availableNpcs)) { |
||
| 419 | debug('No free NPCs'); |
||
| 420 | exitNPC(); |
||
| 421 | } |
||
| 422 | |||
| 423 | // Pop an NPC off the top of the stack to activate |
||
| 424 | $npc = array_shift($availableNpcs); |
||
| 425 | |||
| 426 | // Update session info for this chosen NPC |
||
| 427 | $account = SmrAccount::getAccount($npc['account_id']); |
||
| 428 | $session->setAccount($account); |
||
| 429 | $session->updateGame($npc['game_id']); |
||
| 430 | |||
| 431 | $db->query('UPDATE npc_logins SET working=' . $db->escapeBoolean(true) . ' WHERE login=' . $db->escapeString($account->getLogin())); |
||
| 432 | debug('Chosen NPC: ' . $account->getLogin() . ' (game ' . $session->getGameID() . ')'); |
||
| 433 | |||
| 434 | throw new ForwardException; |
||
| 435 | } |
||
| 436 | |||
| 437 | function canWeUNO(AbstractSmrPlayer $player, $oppurtunisticOnly) { |
||
| 438 | if ($player->getCredits() < MINUMUM_RESERVE_CREDITS) { |
||
| 439 | return false; |
||
| 440 | } |
||
| 441 | $ship = $player->getShip(); |
||
| 442 | if ($ship->hasMaxShields() && $ship->hasMaxArmour() && $ship->hasMaxCargoHolds()) { |
||
| 443 | return false; |
||
| 444 | } |
||
| 445 | $sector = $player->getSector(); |
||
| 446 | |||
| 447 | // We buy armour in preference to shields as it's cheaper. |
||
| 448 | // We buy cargo holds last if we have no newbie turns because we'd rather not die |
||
| 449 | $hardwareArray = array(HARDWARE_ARMOUR, HARDWARE_SHIELDS, HARDWARE_CARGO); |
||
| 450 | |||
| 451 | $amount = 0; |
||
| 452 | |||
| 453 | foreach ($sector->getLocations() as $location) { |
||
| 454 | if ($location->isHardwareSold()) { |
||
| 455 | $hardwareSold = $location->getHardwareSold(); |
||
| 456 | if ($player->getNewbieTurns() > MIN_NEWBIE_TURNS_TO_BUY_CARGO && !$ship->hasMaxCargoHolds() && isset($hardwareSold[HARDWARE_CARGO]) && ($amount = floor(($player->getCredits() - MINUMUM_RESERVE_CREDITS) / Globals::getHardwareCost(HARDWARE_CARGO))) > 0) { // Buy cargo holds first if we have plenty of newbie turns left. |
||
| 457 | $hardwareID = HARDWARE_CARGO; |
||
| 458 | } else { |
||
| 459 | foreach ($hardwareArray as $hardwareArrayID) { |
||
| 460 | if (!$ship->hasMaxHardware($hardwareArrayID) && isset($hardwareSold[$hardwareArrayID]) && ($amount = floor(($player->getCredits() - MINUMUM_RESERVE_CREDITS) / Globals::getHardwareCost($hardwareArrayID))) > 0) { |
||
| 461 | $hardwareID = $hardwareArrayID; |
||
| 462 | break; |
||
| 463 | } |
||
| 464 | } |
||
| 465 | } |
||
| 466 | if (isset($hardwareID)) { |
||
| 467 | return doUNO($hardwareID, min($ship->getMaxHardware($hardwareID) - $ship->getHardware($hardwareID), $amount)); |
||
| 468 | } |
||
| 469 | } |
||
| 470 | } |
||
| 471 | |||
| 472 | if ($oppurtunisticOnly === true) { |
||
| 473 | return false; |
||
| 474 | } |
||
| 475 | |||
| 476 | if ($player->getCredits() - $ship->getCostToUNO() < MINUMUM_RESERVE_CREDITS) { |
||
| 477 | return false; //Only do non-oppurtunistic UNO if we have the money to do it properly! |
||
| 478 | } |
||
| 479 | |||
| 480 | foreach ($hardwareArray as $hardwareArrayID) { |
||
| 481 | if (!$ship->hasMaxHardware($hardwareArrayID)) { |
||
| 482 | $hardwareNeededID = $hardwareArrayID; |
||
| 483 | return plotToNearest($player, Globals::getHardwareTypes($hardwareArrayID)); |
||
| 484 | } |
||
| 485 | } |
||
| 486 | } |
||
| 487 | |||
| 488 | function doUNO($hardwareID, $amount) { |
||
| 489 | debug('Buying ' . $amount . ' units of "' . Globals::getHardwareName($hardwareID) . '"'); |
||
| 490 | $_REQUEST = [ |
||
| 491 | 'amount' => $amount, |
||
| 492 | 'action' => 'Buy', |
||
| 493 | ]; |
||
| 494 | return Page::create('shop_hardware_processing.php', '', array('hardware_id'=>$hardwareID)); |
||
| 495 | } |
||
| 496 | |||
| 497 | function tradeGoods($goodID, AbstractSmrPlayer $player, SmrPort $port) { |
||
| 498 | sleepNPC(); //We have an extra sleep at port to make the NPC more vulnerable. |
||
| 499 | $ship = $player->getShip(); |
||
| 500 | $relations = $player->getRelation($port->getRaceID()); |
||
| 501 | |||
| 502 | $transaction = $port->getGoodTransaction($goodID); |
||
| 503 | |||
| 504 | if ($transaction === TRADER_BUYS) { |
||
| 505 | $amount = $ship->getEmptyHolds(); |
||
| 506 | } else { |
||
| 507 | $amount = $ship->getCargo($goodID); |
||
| 508 | } |
||
| 509 | |||
| 510 | $idealPrice = $port->getIdealPrice($goodID, $transaction, $amount, $relations); |
||
| 511 | $offeredPrice = $port->getOfferPrice($idealPrice, $relations, $transaction); |
||
| 512 | |||
| 513 | $_REQUEST = ['action' => $transaction]; |
||
| 514 | return Page::create('shop_goods_processing.php', '', array('offered_price'=>$offeredPrice, 'ideal_price'=>$idealPrice, 'amount'=>$amount, 'good_id'=>$goodID, 'bargain_price'=>$offeredPrice)); |
||
| 515 | } |
||
| 516 | |||
| 517 | function dumpCargo($player) { |
||
| 518 | $ship = $player->getShip(); |
||
| 519 | $cargo = $ship->getCargo(); |
||
| 520 | debug('Ship Cargo', $cargo); |
||
| 521 | foreach ($cargo as $goodID => $amount) { |
||
| 522 | if ($amount > 0) { |
||
| 523 | return Page::create('cargo_dump_processing.php', '', array('good_id'=>$goodID, 'amount'=>$amount)); |
||
| 524 | } |
||
| 525 | } |
||
| 526 | } |
||
| 527 | |||
| 528 | function plotToSector($player, $sectorID) { |
||
| 529 | return Page::create('course_plot_processing.php', '', array('from'=>$player->getSectorID(), 'to'=>$sectorID)); |
||
| 530 | } |
||
| 531 | |||
| 532 | function plotToFed($player, $plotToHQ = false) { |
||
| 533 | debug('Plotting To Fed', $plotToHQ); |
||
| 534 | |||
| 535 | // Always drop illegal goods before heading to fed space |
||
| 536 | if ($player->getShip()->hasIllegalGoods()) { |
||
| 537 | debug('Dumping illegal goods'); |
||
| 538 | processContainer(dumpCargo($player)); |
||
| 539 | } |
||
| 540 | |||
| 541 | $fedLocID = $player->getRaceID() + ($plotToHQ ? LOCATION_GROUP_RACIAL_HQS : LOCATION_GROUP_RACIAL_BEACONS); |
||
| 542 | if ($player->getSector()->hasLocation($fedLocID)) { |
||
| 543 | debug('Plotted to fed whilst in fed, switch NPC and wait for turns'); |
||
| 544 | changeNPCLogin(); |
||
| 545 | return true; |
||
| 546 | } |
||
| 547 | return plotToNearest($player, SmrLocation::getLocation($fedLocID)); |
||
| 548 | } |
||
| 549 | |||
| 550 | function plotToNearest(AbstractSmrPlayer $player, $realX) { |
||
| 551 | debug('Plotting To: ', $realX); //TODO: Can we make the debug output a bit nicer? |
||
| 552 | |||
| 553 | if ($player->getSector()->hasX($realX)) { //Check if current sector has what we're looking for before we attempt to plot and get error. |
||
| 554 | debug('Already available in sector'); |
||
| 555 | return true; |
||
| 556 | } |
||
| 557 | |||
| 558 | return Page::create('course_plot_nearest_processing.php', '', array('RealX'=>$realX)); |
||
| 559 | } |
||
| 560 | function moveToSector($player, $targetSector) { |
||
| 561 | debug('Moving from #' . $player->getSectorID() . ' to #' . $targetSector); |
||
| 562 | return Page::create('sector_move_processing.php', '', array('target_sector'=>$targetSector, 'target_page'=>'')); |
||
| 563 | } |
||
| 564 | |||
| 565 | function checkForShipUpgrade(AbstractSmrPlayer $player) { |
||
| 566 | foreach (SHIP_UPGRADE_PATH[$player->getRaceID()] as $upgradeShipID) { |
||
| 567 | if ($player->getShipTypeID() == $upgradeShipID) { |
||
| 568 | //We can't upgrade, only downgrade. |
||
| 569 | return false; |
||
| 570 | } |
||
| 571 | $cost = $player->getShip()->getCostToUpgrade($upgradeShipID); |
||
| 572 | if ($cost <= 0 || $player->getCredits() - $cost > MINUMUM_RESERVE_CREDITS) { |
||
| 573 | return doShipUpgrade($player, $upgradeShipID); |
||
| 574 | } |
||
| 575 | } |
||
| 576 | debug('Could not find a ship on the upgrade path.'); |
||
| 577 | return false; |
||
| 578 | } |
||
| 579 | |||
| 580 | function doShipUpgrade(AbstractSmrPlayer $player, $upgradeShipID) { |
||
| 581 | $plotNearest = plotToNearest($player, AbstractSmrShip::getBaseShip($upgradeShipID)); |
||
| 582 | |||
| 583 | if ($plotNearest == true) { //We're already there! |
||
| 584 | //TODO: We're going to want to UNO after upgrading |
||
| 585 | return Page::create('shop_ship_processing.php', '', array('ship_id'=>$upgradeShipID)); |
||
| 586 | } //Otherwise return the plot |
||
| 587 | return $plotNearest; |
||
| 588 | } |
||
| 589 | |||
| 590 | function &changeRoute(array &$tradeRoutes) { |
||
| 591 | $false = false; |
||
| 592 | if (count($tradeRoutes) == 0) { |
||
| 593 | return $false; |
||
| 594 | } |
||
| 595 | $routeKey = array_rand($tradeRoutes); |
||
| 596 | $tradeRoute =& $tradeRoutes[$routeKey]; |
||
| 597 | unset($tradeRoutes[$routeKey]); |
||
| 598 | $GLOBALS['TRADE_ROUTE'] =& $tradeRoute; |
||
| 599 | debug('Switched route', $tradeRoute); |
||
| 600 | return $tradeRoute; |
||
| 601 | } |
||
| 602 | |||
| 603 | function &findRoutes($player) { |
||
| 604 | debug('Finding Routes'); |
||
| 605 | |||
| 606 | $tradeGoods = array(GOODS_NOTHING => false); |
||
| 607 | foreach (Globals::getGoods() as $goodID => $good) { |
||
| 608 | if ($player->meetsAlignmentRestriction($good['AlignRestriction'])) { |
||
| 609 | $tradeGoods[$goodID] = true; |
||
| 610 | } else { |
||
| 611 | $tradeGoods[$goodID] = false; |
||
| 612 | } |
||
| 613 | } |
||
| 614 | |||
| 615 | // Only allow NPCs to trade at ports of their race and neutral ports |
||
| 616 | $tradeRaces = array(); |
||
| 617 | foreach (Globals::getRaces() as $raceID => $race) { |
||
| 618 | $tradeRaces[$raceID] = false; |
||
| 619 | } |
||
| 620 | $tradeRaces[$player->getRaceID()] = true; |
||
| 621 | $tradeRaces[RACE_NEUTRAL] = true; |
||
| 622 | |||
| 623 | $galaxy = $player->getSector()->getGalaxy(); |
||
| 624 | |||
| 625 | $maxNumberOfPorts = 2; |
||
| 626 | $routesForPort = -1; |
||
| 627 | $numberOfRoutes = 100; |
||
| 628 | $maxDistance = 15; |
||
| 629 | |||
| 630 | $startSectorID = $galaxy->getStartSector(); |
||
| 631 | $endSectorID = $galaxy->getEndSector(); |
||
| 632 | |||
| 633 | $db = Smr\Database::getInstance(); |
||
| 634 | $db->query('SELECT routes FROM route_cache WHERE game_id=' . $db->escapeNumber($player->getGameID()) . ' AND max_ports=' . $db->escapeNumber($maxNumberOfPorts) . ' AND goods_allowed=' . $db->escapeObject($tradeGoods) . ' AND races_allowed=' . $db->escapeObject($tradeRaces) . ' AND start_sector_id=' . $db->escapeNumber($startSectorID) . ' AND end_sector_id=' . $db->escapeNumber($endSectorID) . ' AND routes_for_port=' . $db->escapeNumber($routesForPort) . ' AND max_distance=' . $db->escapeNumber($maxDistance)); |
||
| 635 | if ($db->nextRecord()) { |
||
| 636 | $routes = $db->getObject('routes', true); |
||
| 637 | debug('Using Cached Routes: #' . count($routes)); |
||
| 638 | return $routes; |
||
| 639 | } else { |
||
| 640 | debug('Generating Routes'); |
||
| 641 | $allSectors = array(); |
||
| 642 | foreach (SmrGalaxy::getGameGalaxies($player->getGameID()) as $galaxy) { |
||
| 643 | $allSectors += $galaxy->getSectors(); //Merge arrays |
||
| 644 | } |
||
| 645 | |||
| 646 | $distances = Plotter::calculatePortToPortDistances($allSectors, $maxDistance, $startSectorID, $endSectorID); |
||
| 647 | |||
| 648 | if ($maxNumberOfPorts == 1) { |
||
| 649 | $allRoutes = \Routes\RouteGenerator::generateOneWayRoutes($allSectors, $distances, $tradeGoods, $tradeRaces, $routesForPort); |
||
| 650 | } else { |
||
| 651 | $allRoutes = \Routes\RouteGenerator::generateMultiPortRoutes($maxNumberOfPorts, $allSectors, $tradeGoods, $tradeRaces, $distances, $routesForPort, $numberOfRoutes); |
||
| 652 | } |
||
| 653 | |||
| 654 | unset($distances); |
||
| 655 | |||
| 656 | $routesMerged = array(); |
||
| 657 | foreach ($allRoutes[\Routes\RouteGenerator::MONEY_ROUTE] as $multi => $routesByMulti) { |
||
| 658 | $routesMerged += $routesByMulti; //Merge arrays |
||
| 659 | } |
||
| 660 | |||
| 661 | unset($allSectors); |
||
| 662 | SmrPort::clearCache(); |
||
| 663 | SmrSector::clearCache(); |
||
| 664 | |||
| 665 | if (count($routesMerged) == 0) { |
||
| 666 | debug('Could not find any routes! Try another NPC.'); |
||
| 667 | changeNPCLogin(); |
||
| 668 | } |
||
| 669 | |||
| 670 | $db->query('INSERT INTO route_cache ' . |
||
| 671 | '(game_id, max_ports, goods_allowed, races_allowed, start_sector_id, end_sector_id, routes_for_port, max_distance, routes)' . |
||
| 672 | ' VALUES (' . $db->escapeNumber($player->getGameID()) . ', ' . $db->escapeNumber($maxNumberOfPorts) . ', ' . $db->escapeObject($tradeGoods) . ', ' . $db->escapeObject($tradeRaces) . ', ' . $db->escapeNumber($startSectorID) . ', ' . $db->escapeNumber($endSectorID) . ', ' . $db->escapeNumber($routesForPort) . ', ' . $db->escapeNumber($maxDistance) . ', ' . $db->escapeObject($routesMerged, true) . ')'); |
||
| 673 | debug('Found Routes: #' . count($routesMerged)); |
||
| 674 | return $routesMerged; |
||
| 675 | } |
||
| 676 | } |
||
| 677 |