|
19 | 19 | $action = $_GET['action'] ?? ''; |
20 | 20 |
|
21 | 21 | match ($action) { |
22 | | - 'get_prices' => action_get_prices($db, $uid), |
23 | | - 'buy' => action_buy($db, $uid), |
24 | | - 'sell' => action_sell($db, $uid), |
25 | | - 'get_history' => action_get_history($db, $uid), |
26 | | - 'get_active_events'=> action_get_active_events($db), |
27 | | - default => json_error('Unknown action: ' . $action, 400), |
| 22 | + 'get_prices' => action_get_prices($db, $uid), |
| 23 | + 'get_region_prices' => action_get_region_prices($db, $uid), |
| 24 | + 'buy' => action_buy($db, $uid), |
| 25 | + 'sell' => action_sell($db, $uid), |
| 26 | + 'get_history' => action_get_history($db, $uid), |
| 27 | + 'get_active_events' => action_get_active_events($db), |
| 28 | + default => json_error('Unknown action: ' . $action, 400), |
28 | 29 | }; |
29 | 30 |
|
30 | 31 | // --------------------------------------------------------------------------- |
@@ -254,6 +255,134 @@ function action_get_prices(PDO $db, int $uid): never { |
254 | 255 | json_ok(['prices' => $filter ? ($prices[$filter] ?? []) : array_values($prices)]); |
255 | 256 | } |
256 | 257 |
|
| 258 | +// --------------------------------------------------------------------------- |
| 259 | +// Regional market constants |
| 260 | +// --------------------------------------------------------------------------- |
| 261 | + |
| 262 | +/** |
| 263 | + * Pre-defined market regions. |
| 264 | + * |
| 265 | + * Each region has: |
| 266 | + * - transport_cost_mult: surcharge applied on top of the global price formula |
| 267 | + * - supply_bias: per-good initial supply multiplier seeded into market_region_quotes |
| 268 | + * |
| 269 | + * Core Worlds — manufacturing hub; raw-material importer. |
| 270 | + * Frontier Sectors — resource-rich frontier; finished-goods importer (+20% surcharge). |
| 271 | + */ |
| 272 | +const MARKET_REGIONS = [ |
| 273 | + 'core_worlds' => [ |
| 274 | + 'label' => 'Kernwelten', |
| 275 | + 'transport_cost_mult' => 1.0, |
| 276 | + 'supply_bias' => [ |
| 277 | + 'metal' => 0.7, 'crystal' => 0.8, 'deuterium' => 0.8, 'rare_earth' => 0.6, 'food' => 0.9, |
| 278 | + 'steel_alloy' => 1.3, 'focus_crystals' => 1.2, 'reactor_fuel' => 1.1, |
| 279 | + 'consumer_goods' => 1.4, 'luxury_goods' => 1.3, 'research_kits' => 1.2, |
| 280 | + ], |
| 281 | + ], |
| 282 | + 'frontier_sectors' => [ |
| 283 | + 'label' => 'Grenzgebiete', |
| 284 | + 'transport_cost_mult' => 1.20, |
| 285 | + 'supply_bias' => [ |
| 286 | + 'metal' => 1.4, 'crystal' => 1.3, 'deuterium' => 1.2, 'rare_earth' => 1.5, 'food' => 0.7, |
| 287 | + 'steel_alloy' => 0.7, 'focus_crystals' => 0.6, 'reactor_fuel' => 0.8, |
| 288 | + 'consumer_goods' => 0.5, 'luxury_goods' => 0.4, 'research_kits' => 0.6, |
| 289 | + ], |
| 290 | + ], |
| 291 | +]; |
| 292 | + |
| 293 | +/** |
| 294 | + * Compute the current regional market price for a good. |
| 295 | + * |
| 296 | + * Regional price = global_price × transport_cost_mult × supply_bias_adj |
| 297 | + * |
| 298 | + * The supply_bias_adj is derived from the regional supply/demand row in |
| 299 | + * market_region_quotes (if present) or estimated from supply_bias defaults. |
| 300 | + * |
| 301 | + * @param PDO $db |
| 302 | + * @param string $goodType |
| 303 | + * @param string $regionId 'core_worlds' | 'frontier_sectors' |
| 304 | + * @return float |
| 305 | + */ |
| 306 | +function compute_regional_price(PDO $db, string $goodType, string $regionId): float { |
| 307 | + $region = MARKET_REGIONS[$regionId] ?? null; |
| 308 | + if (!$region) return compute_price($db, $goodType); |
| 309 | + |
| 310 | + $tcm = (float)$region['transport_cost_mult']; |
| 311 | + |
| 312 | + // Try market_region_quotes for live supply/demand |
| 313 | + $stmt = $db->prepare(<<<SQL |
| 314 | + SELECT supply, demand, active_events_mult |
| 315 | + FROM market_region_quotes |
| 316 | + WHERE region_id = ? AND good_type = ? |
| 317 | + SQL); |
| 318 | + $stmt->execute([$regionId, $goodType]); |
| 319 | + $row = $stmt->fetch(PDO::FETCH_ASSOC); |
| 320 | + |
| 321 | + if ($row) { |
| 322 | + $supply = max(1.0, (float)$row['supply']); |
| 323 | + $demand = max(1.0, (float)$row['demand']); |
| 324 | + $mult = pow($demand / $supply, PRICE_ELASTICITY); |
| 325 | + $mult = max(PRICE_MULT_MIN, min(PRICE_MULT_MAX * $tcm, $mult)); |
| 326 | + $evMult = (float)($row['active_events_mult'] ?? 1.0); |
| 327 | + $base = BASE_PRICES[$goodType] ?? 10.0; |
| 328 | + return round($base * $tcm * $mult * $evMult, 2); |
| 329 | + } |
| 330 | + |
| 331 | + // Fallback: scale global price by transport cost and supply_bias estimate |
| 332 | + $globalPrice = compute_price($db, $goodType); |
| 333 | + $bias = (float)($region['supply_bias'][$goodType] ?? 1.0); |
| 334 | + // bias > 1 → surplus → lower price; bias < 1 → scarcity → higher price |
| 335 | + $biasAdj = 1.0 / max(0.1, $bias); |
| 336 | + return round($globalPrice * $tcm * $biasAdj, 2); |
| 337 | +} |
| 338 | + |
| 339 | +/** |
| 340 | + * GET market.php?action=get_region_prices[&good_type=X] |
| 341 | + * |
| 342 | + * Returns current market prices per region (core_worlds, frontier_sectors) |
| 343 | + * for all goods, or for a single good if good_type is specified. |
| 344 | + * |
| 345 | + * Response shape: |
| 346 | + * { |
| 347 | + * "regions": { |
| 348 | + * "core_worlds": { |
| 349 | + * "label": "Kernwelten", |
| 350 | + * "transport_cost_mult": 1.0, |
| 351 | + * "prices": [ { good_type, base_price, price, supply, demand } ] |
| 352 | + * }, |
| 353 | + * "frontier_sectors": { … } |
| 354 | + * } |
| 355 | + * } |
| 356 | + */ |
| 357 | +function action_get_region_prices(PDO $db, int $uid): never { |
| 358 | + $filter = $_GET['good_type'] ?? null; |
| 359 | + $goods = $filter ? [$filter] : array_keys(BASE_PRICES); |
| 360 | + |
| 361 | + if ($filter && !isset(BASE_PRICES[$filter])) { |
| 362 | + json_error('Unknown good_type: ' . $filter, 400); |
| 363 | + } |
| 364 | + |
| 365 | + $result = []; |
| 366 | + foreach (MARKET_REGIONS as $regionId => $regionDef) { |
| 367 | + $prices = []; |
| 368 | + foreach ($goods as $good) { |
| 369 | + $regionalPrice = compute_regional_price($db, $good, $regionId); |
| 370 | + $prices[] = [ |
| 371 | + 'good_type' => $good, |
| 372 | + 'base_price' => BASE_PRICES[$good] ?? 0, |
| 373 | + 'price' => $regionalPrice, |
| 374 | + ]; |
| 375 | + } |
| 376 | + $result[$regionId] = [ |
| 377 | + 'label' => $regionDef['label'], |
| 378 | + 'transport_cost_mult' => $regionDef['transport_cost_mult'], |
| 379 | + 'prices' => $filter ? ($prices[0] ?? []) : $prices, |
| 380 | + ]; |
| 381 | + } |
| 382 | + |
| 383 | + json_ok(['regions' => $result]); |
| 384 | +} |
| 385 | + |
257 | 386 | /** |
258 | 387 | * POST market.php?action=buy |
259 | 388 | * Body: { colony_id, good_type, quantity } |
|
0 commit comments