Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Failed Conditions
Push — dependabot/docker/api-docs/php... ( c1a0bb...e0e71c )
by
unknown
20:07 queued 14:31
created

doUNO()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 8
c 1
b 1
f 0
nc 1
nop 3
dl 0
loc 11
rs 10
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
// Use this exception to indicate that an NPC has taken its final action
7
class FinalActionException extends Exception {}
8
9
function overrideForward(Page $container) : never {
0 ignored issues
show
Bug introduced by
The type never was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
	global $forwardedContainer;
11
	$forwardedContainer = $container;
12
	if ($container['body'] == 'error.php') {
13
		// We hit a create_error - this shouldn't happen for an NPC often,
14
		// for now we want to throw an exception for it for testing.
15
		debug('Hit an error');
16
		throw new Exception($container['message']);
17
	}
18
	// We have to throw the exception to get back up the stack,
19
	// otherwise we quickly hit problems of overflowing the stack.
20
	throw new ForwardException;
21
}
22
const OVERRIDE_FORWARD = true;
23
24
// Must be defined before anything that might throw an exception
25
const NPC_SCRIPT = true;
26
27
// global config
28
require_once(realpath(dirname(__FILE__)) . '/../../bootstrap.php');
29
// bot config
30
require_once(CONFIG . 'npc/config.specific.php');
31
// needed libs
32
require_once(get_file_loc('smr.inc.php'));
33
require_once(get_file_loc('shop_goods.inc.php'));
34
35
// Raise exceptions for all types of errors for improved error reporting
36
// and to attempt to shut down the NPCs cleanly on errors.
37
set_error_handler("exception_error_handler");
38
39
const SHIP_UPGRADE_PATH = array(
40
	RACE_ALSKANT => array(
41
		SHIP_TYPE_TRADE_MASTER,
42
		SHIP_TYPE_DEEP_SPACER,
43
		SHIP_TYPE_DEAL_MAKER,
44
		SHIP_TYPE_TRIP_MAKER,
45
		SHIP_TYPE_SMALL_TIMER
46
	),
47
	RACE_CREONTI => array(
48
		SHIP_TYPE_DEVASTATOR,
49
		SHIP_TYPE_JUGGERNAUT,
50
		SHIP_TYPE_GOLIATH,
51
		SHIP_TYPE_LEVIATHAN,
52
		SHIP_TYPE_MEDIUM_CARGO_HULK
53
	),
54
	RACE_HUMAN => array(
55
		SHIP_TYPE_DESTROYER,
56
		SHIP_TYPE_BORDER_CRUISER,
57
		SHIP_TYPE_AMBASSADOR,
58
		SHIP_TYPE_RENAISSANCE,
59
		SHIP_TYPE_LIGHT_FREIGHTER
60
	),
61
	RACE_IKTHORNE => array(
62
		SHIP_TYPE_MOTHER_SHIP,
63
		SHIP_TYPE_ADVANCED_CARRIER,
64
		SHIP_TYPE_FAVOURED_OFFSPRING,
65
		SHIP_TYPE_PROTO_CARRIER,
66
		SHIP_TYPE_TINY_DELIGHT
67
	),
68
	RACE_SALVENE => array(
69
		SHIP_TYPE_EATER_OF_SOULS,
70
		SHIP_TYPE_RAVAGER,
71
		SHIP_TYPE_PREDATOR,
72
		SHIP_TYPE_DRUDGE,
73
		SHIP_TYPE_HATCHLINGS_DUE
74
	),
75
	RACE_THEVIAN => array(
76
		SHIP_TYPE_ASSAULT_CRAFT,
77
		SHIP_TYPE_CARAPACE,
78
		SHIP_TYPE_BOUNTY_HUNTER,
79
		SHIP_TYPE_EXPEDITER,
80
		SHIP_TYPE_SWIFT_VENTURE
81
	),
82
	RACE_WQHUMAN => array(
83
		SHIP_TYPE_DARK_MIRAGE,
84
		SHIP_TYPE_BLOCKADE_RUNNER,
85
		SHIP_TYPE_ROGUE,
86
		SHIP_TYPE_RESISTANCE,
87
		SHIP_TYPE_SLIP_FREIGHTER
88
	),
89
	RACE_NIJARIN => array(
90
		SHIP_TYPE_FURY,
91
		SHIP_TYPE_VINDICATOR,
92
		SHIP_TYPE_VENGEANCE,
93
		SHIP_TYPE_RETALIATION,
94
		SHIP_TYPE_REDEEMER
95
	)
96
);
97
98
99
try {
100
	NPCStuff();
101
} catch (Throwable $e) {
102
	logException($e);
103
	// Try to shut down cleanly
104
	exitNPC();
105
}
106
107
108
function NPCStuff() : void {
109
	global $previousContainer;
110
111
	$session = Smr\Session::getInstance();
112
	$session->setCurrentVar(new Page()); // initialize empty var
113
114
	debug('Script started');
115
116
	// Load the first available NPC
117
	$changeNPC = true;
118
119
	while (true) {
120
		if ($changeNPC) {
121
			changeNPCLogin();
122
123
			// Reset tracking variables
124
			$changeNPC = false;
125
			$allTradeRoutes = [];
126
			$tradeRoute = null;
127
			$underAttack = false;
128
			$actions = 0;
129
130
			// We chose a new NPC, we don't care what we were doing beforehand.
131
			$previousContainer = null;
132
		}
133
134
		try {
135
			// Avoid infinite loops by restricting the number of actions
136
			if ($actions > NPC_MAX_ACTIONS) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $actions does not seem to be defined for all execution paths leading up to this point.
Loading history...
137
				debug('Reached maximum number of actions: ' . NPC_MAX_ACTIONS);
138
				throw new FinalActionException;
139
			}
140
141
			debug('Action #' . $actions);
142
143
			//We have to reload player on each loop
144
			$player = $session->getPlayer(true);
145
			// Sanity check to be certain we actually have an NPC
146
			if (!$player->isNPC()) {
147
				throw new Exception('Player is not an NPC!');
148
			}
149
			$player->updateTurns();
150
151
			// Are we starting with a new NPC?
152
			if ($actions == 0) {
153
				if ($player->getTurns() <= rand($player->getMaxTurns() / 2, $player->getMaxTurns()) && ($player->hasNewbieTurns() || $player->hasFederalProtection())) {
154
					debug('We don\'t have enough turns to bother starting trading, and we are protected: ' . $player->getTurns());
155
					throw new FinalActionException;
156
				}
157
158
				// Ensure the NPC doesn't think it's under attack at startup,
159
				// since this could cause it to get stuck in a loop in Fed.
160
				$player->removeUnderAttack();
161
162
				// Initialize the trade route for this NPC
163
				$allTradeRoutes = findRoutes($player);
164
				$tradeRoute = changeRoute($allTradeRoutes);
165
166
				// Upgrade ships if we can
167
				checkForShipUpgrade($player);
168
169
				// Start the NPC with max hardware
170
				$player->getShip()->setHardwareToMax();
171
172
				// Equip the ship with as many lasers as it can hold
173
				$weaponIDs = [
174
					WEAPON_TYPE_PLANETARY_PULSE_LASER,
175
					WEAPON_TYPE_HUGE_PULSE_LASER,
176
					WEAPON_TYPE_HUGE_PULSE_LASER,
177
					WEAPON_TYPE_LARGE_PULSE_LASER,
178
					WEAPON_TYPE_LARGE_PULSE_LASER,
179
					WEAPON_TYPE_LARGE_PULSE_LASER,
180
					WEAPON_TYPE_LASER,
181
				];
182
				$player->getShip()->removeAllWeapons();
183
				while ($player->getShip()->hasOpenWeaponSlots()) {
184
					$weapon = SmrWeapon::getWeapon(array_shift($weaponIDs));
185
					$player->getShip()->addWeapon($weapon);
186
				}
187
188
				// Update database (not essential to have a lock here)
189
				$player->update();
190
				$player->getShip()->update();
191
			}
192
193
			if ($player->isDead()) {
194
				debug('Some evil person killed us, let\'s move on now.');
195
				$previousContainer = null; //We died, we don't care what we were doing beforehand.
196
				$tradeRoute = changeRoute($allTradeRoutes, $tradeRoute);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tradeRoute does not seem to be defined for all execution paths leading up to this point.
Loading history...
197
				processContainer(Page::create('death_processing.php'));
198
			}
199
			if ($player->getNewbieTurns() <= NEWBIE_TURNS_WARNING_LIMIT && $player->getNewbieWarning()) {
200
				processContainer(Page::create('newbie_warning_processing.php'));
201
			}
202
203
			if (!$underAttack && $player->isUnderAttack() === true
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $underAttack does not seem to be defined for all execution paths leading up to this point.
Loading history...
204
				&& ($player->hasPlottedCourse() === false || $player->getPlottedCourse()->getEndSector()->offersFederalProtection() === false)) {
205
				// We're under attack and need to plot course to fed.
206
				debug('Under Attack');
207
				$underAttack = true;
208
				processContainer(plotToFed($player));
209
			} elseif ($player->hasPlottedCourse() === true && $player->getPlottedCourse()->getEndSector()->offersFederalProtection()) {
210
				// We have a route to fed to follow
211
				debug('Follow Course: ' . $player->getPlottedCourse()->getNextOnPath());
212
				processContainer(moveToSector($player, $player->getPlottedCourse()->getNextOnPath()));
213
			} elseif ($player->hasPlottedCourse() === true) {
214
				// We have a route to follow
215
				debug('Follow Course: ' . $player->getPlottedCourse()->getNextOnPath());
216
				processContainer(moveToSector($player, $player->getPlottedCourse()->getNextOnPath()));
217
			} elseif ($player->getTurns() < NPC_LOW_TURNS || ($player->getTurns() < $player->getMaxTurns() / 2 && $player->getNewbieTurns() < NPC_LOW_NEWBIE_TURNS) || $underAttack) {
218
				// We're low on turns or have been under attack and need to plot course to fed
219
				if ($player->getTurns() < NPC_LOW_TURNS) {
220
					debug('Low Turns:' . $player->getTurns());
221
				}
222
				if ($underAttack) {
223
					debug('Fedding after attack.');
224
				}
225
				if ($player->hasNewbieTurns()) { //We have newbie turns, we can just wait here.
226
					debug('We have newbie turns, let\'s just switch to another NPC.');
227
					throw new FinalActionException;
228
				}
229
				if ($player->hasFederalProtection()) {
230
					debug('We are in fed, time to switch to another NPC.');
231
					throw new FinalActionException;
232
				}
233
				processContainer(plotToFed($player));
234
			} elseif ($tradeRoute instanceof \Routes\Route) {
235
				debug('Trade Route');
236
				$forwardRoute = $tradeRoute->getForwardRoute();
237
				$returnRoute = $tradeRoute->getReturnRoute();
238
				if ($forwardRoute->getBuySectorId() == $player->getSectorID() || $returnRoute->getBuySectorId() == $player->getSectorID()) {
239
					if ($forwardRoute->getBuySectorId() == $player->getSectorID()) {
240
						$buyRoute = $forwardRoute;
241
						$sellRoute = $returnRoute;
242
					} elseif ($returnRoute->getBuySectorId() == $player->getSectorID()) {
243
						$buyRoute = $returnRoute;
244
						$sellRoute = $forwardRoute;
245
					}
246
247
					$ship = $player->getShip();
248
					if ($ship->getUsedHolds() > 0) {
249
						if ($ship->hasCargo($sellRoute->getGoodID())) { //Sell goods
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sellRoute does not seem to be defined for all execution paths leading up to this point.
Loading history...
250
							$goodID = $sellRoute->getGoodID();
251
252
							$port = $player->getSector()->getPort();
253
							$tradeRestriction = $port->getTradeRestriction($player);
254
255
							if ($tradeRestriction === false && $port->getGoodAmount($goodID) >= $ship->getCargo($sellRoute->getGoodID())) { //TODO: Sell what we can rather than forcing sell all at once?
256
								//Sell goods
257
								debug('Sell Goods');
258
								processContainer(tradeGoods($goodID, $player, $port));
259
							} else {
260
								//Move to next route or fed.
261
								if (($tradeRoute = changeRoute($allTradeRoutes)) === null) {
262
									debug('Changing Route Failed');
263
									processContainer(plotToFed($player));
264
								} else {
265
									debug('Route Changed');
266
									throw new ForwardException;
267
								}
268
							}
269
						} elseif ($ship->hasCargo($buyRoute->getGoodID()) === true) { //We've bought goods, plot to sell
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $buyRoute does not seem to be defined for all execution paths leading up to this point.
Loading history...
270
							debug('Plot To Sell: ' . $buyRoute->getSellSectorId());
271
							processContainer(plotToSector($player, $buyRoute->getSellSectorId()));
272
						} else {
273
							//Dump goods
274
							debug('Dump Goods');
275
							processContainer(dumpCargo($player));
276
						}
277
					} else { //Buy goods
278
						$goodID = $buyRoute->getGoodID();
279
280
						$port = $player->getSector()->getPort();
281
						$tradeRestriction = $port->getTradeRestriction($player);
282
283
						if ($tradeRestriction === false && $port->getGoodAmount($goodID) >= $ship->getEmptyHolds()) { //Buy goods
284
							debug('Buy Goods');
285
							processContainer(tradeGoods($goodID, $player, $port));
286
						} else {
287
							//Move to next route or fed.
288
							if (($tradeRoute = changeRoute($allTradeRoutes)) === null) {
289
								debug('Changing Route Failed');
290
								processContainer(plotToFed($player));
291
							} else {
292
								debug('Route Changed');
293
								throw new ForwardException;
294
							}
295
						}
296
					}
297
				} else {
298
					debug('Plot To Buy: ' . $forwardRoute->getBuySectorId());
299
					processContainer(plotToSector($player, $forwardRoute->getBuySectorId()));
300
				}
301
			} else { //Something weird is going on.. Let's fed and wait.
302
				debug('No actual action? Wtf?');
303
				processContainer(plotToFed($player));
304
			}
305
			/*
306
			else { //Otherwise let's run around at random.
307
				$links = $player->getSector()->getLinks();
308
				$moveTo = $links[array_rand($links)];
309
				debug('Random Wanderings: '.$moveTo);
310
				processContainer(moveToSector($player,$moveTo));
311
			}
312
			*/
313
			throw new Exception('NPC failed to perform any action');
314
		} catch (ForwardException $e) {
315
			$actions++; // we took an action
316
		} catch (FinalActionException $e) {
317
			if ($player->getSector()->offersFederalProtection() && !$player->hasFederalProtection()) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $player does not seem to be defined for all execution paths leading up to this point.
Loading history...
318
				debug('Disarming so we can get Fed protection');
319
				$player->getShip()->setCDs(0);
320
				$player->getShip()->removeAllWeapons();
321
				$player->getShip()->update();
322
			}
323
			// switch to a new NPC if we haven't taken any actions yet
324
			if ($actions > 0) {
325
				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());
326
				exitNPC();
327
			}
328
			$changeNPC = true;
329
		}
330
331
		// Save any changes that we made during this action
332
		global $lock;
333
		if ($lock) { //only save if we have the lock.
334
			SmrSector::saveSectors();
335
			SmrShip::saveShips();
336
			SmrPlayer::savePlayers();
337
			SmrForce::saveForces();
338
			SmrPort::savePorts();
339
			if (class_exists('WeightedRandom', false)) {
340
				WeightedRandom::saveWeightedRandoms();
341
			}
342
			release_lock();
343
		}
344
345
		//Clean up the caches as the data may get changed by other players
346
		clearCaches();
347
348
		//Clear up some global vars to avoid contaminating subsequent pages
349
		global $locksFailed;
350
		$locksFailed = array();
351
		$_REQUEST = array();
352
353
		//Have a sleep between actions
354
		sleepNPC();
355
	}
356
	debug('Actions Finished.');
357
	exitNPC();
358
}
359
360
function clearCaches() : void {
361
	SmrSector::clearCache();
362
	SmrPlayer::clearCache();
363
	SmrShip::clearCache();
364
	SmrForce::clearCache();
365
	SmrPort::clearCache();
366
}
367
368
function debug(string $message, mixed $debugObject = null) : void {
369
	echo date('Y-m-d H:i:s - ') . $message . ($debugObject !== null ?EOL.var_export($debugObject, true) : '') . EOL;
370
	if (NPC_LOG_TO_DATABASE) {
371
		$session = Smr\Session::getInstance();
372
		$accountID = $session->getAccountID();
373
		$var = $session->getCurrentVar();
374
		$db = Smr\Database::getInstance();
375
		$db->write('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)) . ')');
376
377
		// On the first call to debug, we need to update the script_id retroactively
378
		if (!defined('SCRIPT_ID')) {
379
			define('SCRIPT_ID', $db->getInsertID());
380
			$db->write('UPDATE npc_logs SET script_id=' . SCRIPT_ID . ' WHERE log_id=' . SCRIPT_ID);
381
		}
382
	}
383
}
384
385
function processContainer(Page $container) : never {
386
	global $forwardedContainer, $previousContainer;
387
	$session = Smr\Session::getInstance();
388
	$player = $session->getPlayer();
389
	if ($container == $previousContainer && $forwardedContainer['body'] != 'forces_attack.php') {
390
		debug('We are executing the same container twice?', array('ForwardedContainer' => $forwardedContainer, 'Container' => $container));
391
		if ($player->hasNewbieTurns() || $player->hasFederalProtection()) {
392
			// 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.
393
			throw new Exception('We are executing the same container twice?');
394
		}
395
	}
396
	clearCaches(); //Clear caches of anything we have used for decision making before processing container and getting lock.
397
	$previousContainer = $container;
398
	debug('Executing container', $container);
399
	// The next "page request" must occur at an updated time.
400
	Smr\Epoch::update();
401
	$session->setCurrentVar($container);
402
	acquire_lock($player->getSectorID()); // Lock now to skip var update in do_voodoo
403
	do_voodoo();
404
}
405
406
function sleepNPC() : void {
407
	usleep(rand(MIN_SLEEP_TIME, MAX_SLEEP_TIME)); //Sleep for a random time
408
}
409
410
// Releases an NPC when it is done working
411
function releaseNPC() : void {
412
	$session = Smr\Session::getInstance();
413
	if (!$session->hasAccount()) {
414
		debug('releaseNPC: no NPC to release');
415
		return;
416
	}
417
	$login = $session->getAccount()->getLogin();
418
	$db = Smr\Database::getInstance();
419
	$db->write('UPDATE npc_logins SET working=' . $db->escapeBoolean(false) . ' WHERE login=' . $db->escapeString($login));
420
	if ($db->getChangedRows() > 0) {
421
		debug('Released NPC: ' . $login);
422
	} else {
423
		debug('Failed to release NPC: ' . $login);
424
	}
425
}
426
427
function exitNPC() : void {
428
	debug('Exiting NPC script.');
429
	releaseNPC();
430
	release_lock();
431
	exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
432
}
433
434
function changeNPCLogin() : void {
435
	// Release previous NPC, if any
436
	releaseNPC();
437
438
	// Lacking a convenient way to get up-to-date turns, order NPCs by how
439
	// recently they have taken an action.
440
	debug('Choosing new NPC');
441
	static $availableNpcs = null;
442
443
	$db = Smr\Database::getInstance();
444
	$session = Smr\Session::getInstance();
445
446
	if (is_null($availableNpcs)) {
447
		// Make sure NPC's have been set up in the database
448
		$dbResult = $db->read('SELECT 1 FROM npc_logins LIMIT 1');
449
		if (!$dbResult->hasRecord()) {
450
			debug('No NPCs have been created yet!');
451
			exitNPC();
452
		}
453
454
		// Make sure to select NPCs from active games only
455
		$dbResult = $db->read('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');
456
		foreach ($dbResult->records() as $dbRecord) {
457
			$availableNpcs[] = [
458
				'account_id' => $dbRecord->getInt('account_id'),
459
				'game_id' => $dbRecord->getInt('game_id'),
460
			];
461
		}
462
	}
463
464
	if (empty($availableNpcs)) {
465
		debug('No free NPCs');
466
		exitNPC();
467
	}
468
469
	// Pop an NPC off the top of the stack to activate
470
	$npc = array_shift($availableNpcs);
471
472
	// Update session info for this chosen NPC
473
	$account = SmrAccount::getAccount($npc['account_id']);
474
	$session->setAccount($account);
475
	$session->updateGame($npc['game_id']);
476
477
	$db->write('UPDATE npc_logins SET working=' . $db->escapeBoolean(true) . ' WHERE login=' . $db->escapeString($account->getLogin()));
478
	debug('Chosen NPC: ' . $account->getLogin() . ' (game ' . $session->getGameID() . ')');
479
}
480
481
function tradeGoods(int $goodID, AbstractSmrPlayer $player, SmrPort $port) : Page {
482
	sleepNPC(); //We have an extra sleep at port to make the NPC more vulnerable.
483
	$ship = $player->getShip();
484
	$relations = $player->getRelation($port->getRaceID());
485
486
	$transaction = $port->getGoodTransaction($goodID);
487
488
	if ($transaction === TRADER_BUYS) {
489
		$amount = $ship->getEmptyHolds();
490
	} else {
491
		$amount = $ship->getCargo($goodID);
492
	}
493
494
	$idealPrice = $port->getIdealPrice($goodID, $transaction, $amount, $relations);
495
	$offeredPrice = $port->getOfferPrice($idealPrice, $relations, $transaction);
496
497
	$_REQUEST = ['action' => $transaction];
498
	return Page::create('shop_goods_processing.php', '', array('offered_price'=>$offeredPrice, 'ideal_price'=>$idealPrice, 'amount'=>$amount, 'good_id'=>$goodID, 'bargain_price'=>$offeredPrice));
499
}
500
501
function dumpCargo(SmrPlayer $player) : Page {
502
	$ship = $player->getShip();
503
	$cargo = $ship->getCargo();
504
	debug('Ship Cargo', $cargo);
505
	foreach ($cargo as $goodID => $amount) {
506
		if ($amount > 0) {
507
			return Page::create('cargo_dump_processing.php', '', array('good_id'=>$goodID, 'amount'=>$amount));
508
		}
509
	}
510
	throw new Exception('Called dumpCargo without any cargo!');
511
}
512
513
function plotToSector(SmrPlayer $player, int $sectorID) : Page {
514
	return Page::create('course_plot_processing.php', '', array('from'=>$player->getSectorID(), 'to'=>$sectorID));
515
}
516
517
function plotToFed(SmrPlayer $player) : Page {
518
	debug('Plotting To Fed');
519
520
	// Always drop illegal goods before heading to fed space
521
	if ($player->getShip()->hasIllegalGoods()) {
522
		debug('Dumping illegal goods');
523
		processContainer(dumpCargo($player));
524
	}
525
526
	$fedLocID = $player->getRaceID() + LOCATION_GROUP_RACIAL_BEACONS;
527
	$container = plotToNearest($player, SmrLocation::getLocation($fedLocID));
528
	if ($container === false) {
529
		debug('Plotted to fed whilst in fed, switch NPC and wait for turns');
530
		throw new FinalActionException;
531
	}
532
	return $container;
533
}
534
535
function plotToNearest(AbstractSmrPlayer $player, mixed $realX) : Page|false {
536
	debug('Plotting To: ', $realX); //TODO: Can we make the debug output a bit nicer?
537
538
	if ($player->getSector()->hasX($realX)) { //Check if current sector has what we're looking for before we attempt to plot and get error.
539
		debug('Already available in sector');
540
		return false;
541
	}
542
543
	return Page::create('course_plot_nearest_processing.php', '', array('RealX'=>$realX));
544
}
545
546
function moveToSector(SmrPlayer $player, int $targetSector) : Page {
547
	debug('Moving from #' . $player->getSectorID() . ' to #' . $targetSector);
548
	return Page::create('sector_move_processing.php', '', array('target_sector'=>$targetSector, 'target_page'=>''));
549
}
550
551
function checkForShipUpgrade(AbstractSmrPlayer $player) : void {
552
	foreach (SHIP_UPGRADE_PATH[$player->getRaceID()] as $upgradeShipID) {
553
		if ($player->getShipTypeID() == $upgradeShipID) {
554
			//We can't upgrade, only downgrade.
555
			return;
556
		}
557
		$cost = $player->getShip()->getCostToUpgrade($upgradeShipID);
558
		$balance = $player->getCredits() - $cost;
559
		if ($balance > MINUMUM_RESERVE_CREDITS) {
560
			debug('Upgrading to ship type: ' . $upgradeShipID);
561
			$player->setCredits($balance);
562
			$player->getShip()->setTypeID($upgradeShipID);
563
			return;
564
		}
565
	}
566
}
567
568
function changeRoute(array &$tradeRoutes, Routes\Route $routeToAvoid = null) : ?Routes\Route {
569
	// Remove any route from the pool of available routes if it contains
570
	// either of the sectors in the $routeToAvoid (i.e. we died on it,
571
	// so don't go back!).
572
	if ($routeToAvoid !== null) {
573
		$avoidSectorIDs = array_unique([
574
			$routeToAvoid->getForwardRoute()->getSellSectorId(),
575
			$routeToAvoid->getForwardRoute()->getBuySectorId(),
576
			$routeToAvoid->getReturnRoute()->getSellSectorId(),
577
			$routeToAvoid->getReturnRoute()->getBuySectorId(),
578
		]);
579
		foreach ($tradeRoutes as $key => $route) {
580
			foreach ($avoidSectorIDs as $avoidSectorID) {
581
				if ($route->containsPort($avoidSectorID)) {
582
					unset($tradeRoutes[$key]);
583
					break;
584
				}
585
			}
586
		}
587
	}
588
589
	if (count($tradeRoutes) == 0) {
590
		return null;
591
	}
592
593
	// Pick a random route
594
	$routeKey = array_rand($tradeRoutes);
595
	$tradeRoute = $tradeRoutes[$routeKey];
596
597
	// Remove the route we chose so that we don't pick it again later.
598
	unset($tradeRoutes[$routeKey]);
599
600
	debug('Switched route', $tradeRoute);
601
	return $tradeRoute;
602
}
603
604
function findRoutes(SmrPlayer $player) : array {
605
	debug('Finding Routes');
606
607
	$tradeGoods = array(GOODS_NOTHING => false);
608
	foreach (Globals::getGoods() as $goodID => $good) {
609
		if ($player->meetsAlignmentRestriction($good['AlignRestriction'])) {
610
			$tradeGoods[$goodID] = true;
611
		} else {
612
			$tradeGoods[$goodID] = false;
613
		}
614
	}
615
616
	// Only allow NPCs to trade at ports of their race and neutral ports
617
	$tradeRaces = array();
618
	foreach (Smr\Race::getAllIDs() as $raceID) {
619
		$tradeRaces[$raceID] = false;
620
	}
621
	$tradeRaces[$player->getRaceID()] = true;
622
	$tradeRaces[RACE_NEUTRAL] = true;
623
624
	$galaxy = $player->getSector()->getGalaxy();
625
626
	$maxNumberOfPorts = 2;
627
	$routesForPort = -1;
628
	$numberOfRoutes = 100;
629
	$maxDistance = 15;
630
631
	$startSectorID = $galaxy->getStartSector();
632
	$endSectorID = $galaxy->getEndSector();
633
634
	$db = Smr\Database::getInstance();
635
	$dbResult = $db->read('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));
636
	if ($dbResult->hasRecord()) {
637
		$routes = $dbResult->record()->getObject('routes', true);
638
		debug('Using Cached Routes: #' . count($routes));
639
		return $routes;
640
	} else {
641
		debug('Generating Routes');
642
		$allSectors = array();
643
		foreach (SmrGalaxy::getGameGalaxies($player->getGameID()) as $galaxy) {
644
			$allSectors += $galaxy->getSectors(); //Merge arrays
645
		}
646
647
		$distances = Plotter::calculatePortToPortDistances($allSectors, $maxDistance, $startSectorID, $endSectorID);
648
649
		if ($maxNumberOfPorts == 1) {
0 ignored issues
show
introduced by
The condition $maxNumberOfPorts == 1 is always false.
Loading history...
650
			$allRoutes = \Routes\RouteGenerator::generateOneWayRoutes($allSectors, $distances, $tradeGoods, $tradeRaces, $routesForPort);
651
		} else {
652
			$allRoutes = \Routes\RouteGenerator::generateMultiPortRoutes($maxNumberOfPorts, $allSectors, $tradeGoods, $tradeRaces, $distances, $routesForPort, $numberOfRoutes);
653
		}
654
655
		unset($distances);
656
657
		$routesMerged = array();
658
		foreach ($allRoutes[\Routes\RouteGenerator::MONEY_ROUTE] as $multi => $routesByMulti) {
659
			$routesMerged += $routesByMulti; //Merge arrays
660
		}
661
662
		unset($allSectors);
663
		SmrPort::clearCache();
664
		SmrSector::clearCache();
665
666
		if (count($routesMerged) == 0) {
667
			debug('Could not find any routes! Try another NPC.');
668
			throw new FinalActionException;
669
		}
670
671
		$db->write('INSERT INTO route_cache ' .
672
				'(game_id, max_ports, goods_allowed, races_allowed, start_sector_id, end_sector_id, routes_for_port, max_distance, routes)' .
673
				' 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) . ')');
674
		debug('Found Routes: #' . count($routesMerged));
675
		return $routesMerged;
676
	}
677
}
678