1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906
|
#include "ServerApp.h"
#include <ctime>
#include <numeric>
#include <stdexcept>
#include <thread>
#include <boost/date_time/posix_time/time_formatters.hpp>
#include <boost/filesystem/exception.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/functional/hash.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "SaveLoad.h"
#include "ServerFSM.h"
#include "UniverseGenerator.h"
#include "../combat/CombatEvents.h"
#include "../combat/CombatLogManager.h"
#include "../combat/CombatSystem.h"
#include "../Empire/Empire.h"
#include "../Empire/Government.h"
#include "../parse/Parse.h"
#include "../parse/PythonParser.h"
#include "../universe/Building.h"
#include "../universe/Condition.h"
#include "../universe/Fleet.h"
#include "../universe/FleetPlan.h"
#include "../universe/Planet.h"
#include "../universe/ShipDesign.h"
#include "../universe/Ship.h"
#include "../universe/Special.h"
#include "../universe/Species.h"
#include "../universe/System.h"
#include "../universe/Tech.h"
#include "../universe/UnlockableItem.h"
#include "../universe/ValueRef.h"
#include "../util/Directories.h"
#include "../util/GameRules.h"
#include "../util/i18n.h"
#include "../util/Logger.h"
#include "../util/LoggerWithOptionsDB.h"
#include "../util/OptionsDB.h"
#include "../util/Order.h"
#include "../util/OrderSet.h"
#include "../util/Pending.h"
#include "../util/Random.h"
#include "../util/SaveGamePreviewUtils.h"
#include "../util/ScopedTimer.h"
#include "../util/SitRepEntry.h"
#include "../util/ThreadPool.h"
#include "../util/Version.h"
namespace fs = boost::filesystem;
namespace {
DeclareThreadSafeLogger(effects);
DeclareThreadSafeLogger(combat);
//If there's only one other empire, return their ID:
int EnemyId(int empire_id, const std::set<int> &empire_ids) {
if (empire_ids.size() == 2) {
for (int enemy_id : empire_ids) {
if (enemy_id != empire_id)
return enemy_id;
}
}
return ALL_EMPIRES;
}
template <typename T>
auto to_span(const boost::container::flat_set<T>& rhs)
{ return std::span<const T>{rhs.begin(), rhs.end()}; }
};
void Seed(unsigned int seed);
////////////////////////////////////////////////
// ServerApp
////////////////////////////////////////////////
ServerApp::ServerApp() :
IApp(),
m_signals(m_io_context, SIGINT, SIGTERM),
m_timer(m_io_context),
m_networking(m_io_context,
boost::bind(&ServerApp::HandleNonPlayerMessage, this, boost::placeholders::_1, boost::placeholders::_2),
boost::bind(&ServerApp::HandleMessage, this, boost::placeholders::_1, boost::placeholders::_2),
boost::bind(&ServerApp::PlayerDisconnected, this, boost::placeholders::_1)),
m_fsm(std::make_unique<ServerFSM>(*this)),
m_chat_history(1000)
{
// Force the log file if requested.
if (GetOptionsDB().Get<std::string>("log-file").empty()) {
const std::string SERVER_LOG_FILENAME(PathToString(GetUserDataDir() / "freeoriond.log"));
GetOptionsDB().Set("log-file", SERVER_LOG_FILENAME);
}
// Force the log threshold if requested.
auto force_log_level = GetOptionsDB().Get<std::string>("log-level");
if (!force_log_level.empty())
OverrideAllLoggersThresholds(to_LogLevel(force_log_level));
InitLoggingSystem(GetOptionsDB().Get<std::string>("log-file"), "Server");
InitLoggingOptionsDBSystem();
InfoLogger() << FreeOrionVersionString();
LogDependencyVersions();
m_galaxy_setup_data.seed = GetOptionsDB().Get<std::string>("setup.seed");
m_galaxy_setup_data.size = GetOptionsDB().Get<int>("setup.star.count");
m_galaxy_setup_data.shape = GetOptionsDB().Get<Shape>("setup.galaxy.shape");
m_galaxy_setup_data.age = GetOptionsDB().Get<GalaxySetupOptionGeneric>("setup.galaxy.age");
m_galaxy_setup_data.starlane_freq = GetOptionsDB().Get<GalaxySetupOptionGeneric>("setup.starlane.frequency");
m_galaxy_setup_data.planet_density = GetOptionsDB().Get<GalaxySetupOptionGeneric>("setup.planet.density");
m_galaxy_setup_data.specials_freq = GetOptionsDB().Get<GalaxySetupOptionGeneric>("setup.specials.frequency");
m_galaxy_setup_data.monster_freq = GetOptionsDB().Get<GalaxySetupOptionMonsterFreq>("setup.monster.frequency");
m_galaxy_setup_data.native_freq = GetOptionsDB().Get<GalaxySetupOptionGeneric>("setup.native.frequency");
m_galaxy_setup_data.ai_aggr = GetOptionsDB().Get<Aggression>("setup.ai.aggression");
m_galaxy_setup_data.game_uid = GetOptionsDB().Get<std::string>("setup.game.uid");
// Initialize Python before FSM initialization
// to be able use it for parsing
InitializePython();
if (!m_python_server.IsPythonRunning())
throw std::runtime_error("Python not initialized");
if (GetOptionsDB().Get<int>("network.server.python.asyncio-interval") > 0) {
m_timer.expires_after(std::chrono::seconds(GetOptionsDB().Get<int>("network.server.python.asyncio-interval")));
m_timer.async_wait(boost::bind(&ServerApp::AsyncIOTimedoutHandler,
this,
boost::asio::placeholders::error));
}
// Start parsing content before FSM initialization
// to have data initialized before autostart execution
std::promise<void> barrier;
std::future<void> barrier_future = barrier.get_future();
StartBackgroundParsing(PythonParser(m_python_server, GetResourceDir() / "scripting"), std::move(barrier));
barrier_future.wait();
m_fsm->initiate();
namespace ph = boost::placeholders;
m_empires.DiplomaticStatusChangedSignal.connect(
boost::bind(&ServerApp::HandleDiplomaticStatusChange, this, ph::_1, ph::_2));
m_empires.DiplomaticMessageChangedSignal.connect(
boost::bind(&ServerApp::HandleDiplomaticMessageChange,this, ph::_1, ph::_2));
m_networking.MessageSentSignal.connect(
boost::bind(&ServerApp::UpdateEmpireTurnReceived, this, ph::_1, ph::_2, ph::_3));
m_signals.async_wait(boost::bind(&ServerApp::SignalHandler, this, ph::_1, ph::_2));
}
ServerApp::~ServerApp() {
DebugLogger() << "ServerApp::~ServerApp";
// Calling Py_Finalize here causes segfault when m_python_server destructing with its python
// object fields
CleanupAIs();
DebugLogger() << "Server exited cleanly.";
}
void ServerApp::operator()()
{ Run(); }
void ServerApp::SignalHandler(const boost::system::error_code& error, int signal_number) {
if (error)
ErrorLogger() << "Exiting due to OS error (" << error.value() << ") " << error.message();
m_fsm->process_event(ShutdownServer());
}
namespace {
std::string AIClientExe() {
static constexpr auto ai_client_exe_filename =
#ifdef FREEORION_WIN32
"freeorionca.exe";
#else
"freeorionca";
#endif
return PathToString(GetBinDir() / ai_client_exe_filename);
}
}
#ifdef FREEORION_MACOSX
#include <stdlib.h>
#endif
void ServerApp::StartBackgroundParsing(const PythonParser& python, std::promise<void>&& barrier) {
IApp::StartBackgroundParsing(python, std::move(barrier));
const auto& rdir = GetResourceDir();
if (fs::exists(rdir / "scripting/starting_unlocks/items.inf"))
m_universe.SetInitiallyUnlockedItems(Pending::StartAsyncParsing(parse::items, rdir / "scripting/starting_unlocks/items.inf"));
else
ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/starting_unlocks/items.inf").string();
if (fs::exists(rdir / "scripting/starting_unlocks/buildings.inf"))
m_universe.SetInitiallyUnlockedBuildings(Pending::StartAsyncParsing(parse::starting_buildings, rdir / "scripting/starting_unlocks/buildings.inf"));
else
ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/starting_unlocks/buildings.inf").string();
if (fs::exists(rdir / "scripting/starting_unlocks/fleets.inf"))
m_universe.SetInitiallyUnlockedFleetPlans(Pending::StartAsyncParsing(parse::fleet_plans, rdir / "scripting/starting_unlocks/fleets.inf"));
else
ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/starting_unlocks/fleets.inf").string();
if (fs::exists(rdir / "scripting/monster_fleets.inf"))
m_universe.SetMonsterFleetPlans(Pending::StartAsyncParsing(parse::monster_fleet_plans, rdir / "scripting/monster_fleets.inf"));
else
ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/monster_fleets.inf").string();
if (fs::exists(rdir / "scripting/empire_statistics"))
m_universe.SetEmpireStats(Pending::StartAsyncParsing(parse::statistics, rdir / "scripting/empire_statistics"));
else
ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/empire_statistics").string();
}
void ServerApp::CreateAIClients(const std::vector<PlayerSetupData>& player_setup_data, int max_aggression) {
DebugLogger() << "ServerApp::CreateAIClients: " << player_setup_data.size() << " player (maybe not all AIs) at max aggression: " << max_aggression;
// check if AI clients are needed for given setup data
bool need_AIs = false;
for (const PlayerSetupData& psd : player_setup_data) {
if (psd.client_type == Networking::ClientType::CLIENT_TYPE_AI_PLAYER) {
need_AIs = true;
break;
}
}
if (need_AIs)
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::STARTING_AIS));
// disconnect any old AI clients
CleanupAIs();
if (!need_AIs)
return;
#ifdef FREEORION_MACOSX
// On OSX set environment variable DYLD_LIBRARY_PATH to python framework folder
// bundled with app, so the dynamic linker uses the bundled python library.
// Otherwise the dynamic linker will look for a correct python lib in system
// paths, and if it can't find it, throw an error and terminate!
// Setting environment variable here, spawned child processes will inherit it.
setenv("DYLD_LIBRARY_PATH", GetPythonHome().string().c_str(), 1);
#endif
// binary / executable to run for AI clients
const std::string AI_CLIENT_EXE = AIClientExe();
// TODO: add other command line args to AI client invocation as needed
std::vector<std::string> args, arg;
args.push_back("\"" + AI_CLIENT_EXE + "\"");
args.push_back("place_holder");
std::size_t player_pos = args.size()-1;
std::stringstream max_aggr_str;
max_aggr_str << max_aggression;
args.push_back(max_aggr_str.str());
args.push_back("--resource.path");
args.push_back("\"" + GetOptionsDB().Get<std::string>("resource.path") + "\"");
auto force_log_level = GetOptionsDB().Get<std::string>("log-level");
if (!force_log_level.empty()) {
args.push_back("--log-level");
args.push_back(GetOptionsDB().Get<std::string>("log-level"));
}
if (GetOptionsDB().Get<bool>("testing")) {
args.push_back("--testing");
#ifdef FREEORION_LINUX
// Dirty hack to output log to console.
args.push_back("--log-file");
args.push_back("/proc/self/fd/1");
#endif
}
args.push_back("--ai-path");
args.push_back(GetOptionsDB().Get<std::string>("ai-path"));
DebugLogger() << "starting AIs with " << AI_CLIENT_EXE ;
DebugLogger() << "ai-aggression set to " << max_aggression;
DebugLogger() << "ai-path set to '" << GetOptionsDB().Get<std::string>("ai-path") << "'";
std::string ai_config = GetOptionsDB().Get<std::string>("ai-config");
if (!ai_config.empty()) {
args.push_back("--ai-config");
args.push_back(ai_config);
DebugLogger() << "ai-config set to '" << ai_config << "'";
} else {
DebugLogger() << "ai-config not set.";
}
std::string ai_log_dir = GetOptionsDB().Get<std::string>("ai-log-dir");
if (!ai_log_dir.empty()) {
args.push_back("--ai-log-dir");
args.push_back(ai_log_dir);
DebugLogger() << "ai-log-dir set to '" << ai_log_dir << "'";
} else {
DebugLogger() << "ai-log-dir not set.";
}
// for each AI client player, create a new AI client process
for (const PlayerSetupData& psd : player_setup_data) {
if (psd.client_type != Networking::ClientType::CLIENT_TYPE_AI_PLAYER)
continue;
// check that AIs have a name, as they will be sorted later based on it
std::string player_name = psd.player_name;
if (player_name.empty()) {
ErrorLogger() << "ServerApp::CreateAIClients can't create a player with no name.";
return;
}
args[player_pos] = player_name;
m_ai_client_processes.insert_or_assign(player_name, Process(AI_CLIENT_EXE, args));
DebugLogger() << "done starting AI " << player_name;
}
// set initial AI process priority to low
SetAIsProcessPriorityToLow(true);
}
Empire* ServerApp::GetEmpire(int id)
{ return m_empires.GetEmpire(id).get(); }
std::string ServerApp::GetVisibleObjectName(const UniverseObject& object)
{ return object.Name(); }
void ServerApp::Run() {
DebugLogger() << "FreeOrion server waiting for network events";
try {
while (1) {
if (m_io_context.run_one())
m_networking.HandleNextEvent();
else
break;
}
} catch (const NormalExitException&)
{}
}
void ServerApp::InitializePython() {
if (m_python_server.IsPythonRunning())
return;
if (m_python_server.Initialize())
return;
ErrorLogger() << "Server's python interpreter failed to initialize.";
}
void ServerApp::AsyncIOTimedoutHandler(const boost::system::error_code& error) {
if (error) {
DebugLogger() << "Turn timed out cancelled";
return;
}
bool success = false;
try {
success = m_python_server.AsyncIOTick();
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Attempting to restart.";
if (m_python_server.Initialize()) {
ErrorLogger() << "Python interpreter successfully restarted.";
} else {
ErrorLogger() << "Python interpreter failed to restart. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
}
if (success) {
if (GetOptionsDB().Get<int>("network.server.python.asyncio-interval") > 0) {
m_timer.expires_after(std::chrono::seconds(GetOptionsDB().Get<int>("network.server.python.asyncio-interval")));
m_timer.async_wait(boost::bind(&ServerApp::AsyncIOTimedoutHandler,
this,
boost::asio::placeholders::error));
}
} else {
ErrorLogger() << "Python scripted authentication failed.";
ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"),
false));
}
}
void ServerApp::UpdateEmpireTurnReceived(bool success, int empire_id, int turn) {
if (success) {
if (auto empire = m_empires.GetEmpire(empire_id)) {
empire->SetLastTurnReceived(turn);
}
}
}
void ServerApp::CleanupAIs() {
if (m_ai_client_processes.empty() && m_networking.empty())
return;
DebugLogger() << "ServerApp::CleanupAIs() telling AIs game is ending";
bool ai_connection_lingering = false;
try {
for (PlayerConnectionPtr player : m_networking) {
if (player->GetClientType() == Networking::ClientType::CLIENT_TYPE_AI_PLAYER) {
player->SendMessage(EndGameMessage(Message::EndGameReason::PLAYER_DISCONNECT));
ai_connection_lingering = true;
}
}
} catch (...) {
ErrorLogger() << "ServerApp::CleanupAIs() exception while sending end game messages";
}
if (ai_connection_lingering) {
// time for AIs to react?
DebugLogger() << "ServerApp::CleanupAIs() waiting 1 second for AI processes to clean up...";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
DebugLogger() << "ServerApp::CleanupAIs() killing " << m_ai_client_processes.size() << " AI clients.";
try {
for (auto& process : m_ai_client_processes)
{ process.second.Kill(); }
} catch (...) {
ErrorLogger() << "ServerApp::CleanupAIs() exception while killing processes";
}
m_ai_client_processes.clear();
}
void ServerApp::SetAIsProcessPriorityToLow(bool set_to_low) {
for (auto& process : m_ai_client_processes) {
if(!(process.second.SetLowPriority(set_to_low))) {
if (set_to_low)
ErrorLogger() << "ServerApp::SetAIsProcessPriorityToLow : failed to lower priority for AI process";
else
#ifdef FREEORION_WIN32
ErrorLogger() << "ServerApp::SetAIsProcessPriorityToLow : failed to raise priority for AI process";
#else
ErrorLogger() << "ServerApp::SetAIsProcessPriorityToLow : cannot raise priority for AI process, requires superuser privileges on this system";
#endif
}
}
}
void ServerApp::HandleMessage(const Message& msg, PlayerConnectionPtr player_connection) {
//DebugLogger() << "ServerApp::HandleMessage type " << msg.Type();
m_networking.UpdateCookie(player_connection->Cookie()); // update cookie expire date
switch (msg.Type()) {
case Message::MessageType::HOST_SP_GAME: m_fsm->process_event(HostSPGame(msg, player_connection)); break;
case Message::MessageType::START_MP_GAME: m_fsm->process_event(StartMPGame(msg, player_connection)); break;
case Message::MessageType::LOBBY_UPDATE: m_fsm->process_event(LobbyUpdate(msg, player_connection)); break;
case Message::MessageType::SAVE_GAME_INITIATE: m_fsm->process_event(SaveGameRequest(msg, player_connection)); break;
case Message::MessageType::TURN_ORDERS: m_fsm->process_event(TurnOrders(msg, player_connection)); break;
case Message::MessageType::TURN_PARTIAL_ORDERS: m_fsm->process_event(TurnPartialOrders(msg, player_connection));break;
case Message::MessageType::UNREADY: m_fsm->process_event(RevokeReadiness(msg, player_connection)); break;
case Message::MessageType::PLAYER_CHAT: m_fsm->process_event(PlayerChat(msg, player_connection)); break;
case Message::MessageType::DIPLOMACY: m_fsm->process_event(Diplomacy(msg, player_connection)); break;
case Message::MessageType::MODERATOR_ACTION: m_fsm->process_event(ModeratorAct(msg, player_connection)); break;
case Message::MessageType::ELIMINATE_SELF: m_fsm->process_event(EliminateSelf(msg, player_connection)); break;
case Message::MessageType::AUTO_TURN: m_fsm->process_event(AutoTurn(msg, player_connection)); break;
case Message::MessageType::REVERT_ORDERS: m_fsm->process_event(RevertOrders(msg, player_connection)); break;
case Message::MessageType::ERROR_MSG:
case Message::MessageType::DEBUG: break;
case Message::MessageType::SHUT_DOWN_SERVER: HandleShutdownMessage(msg, player_connection); break;
case Message::MessageType::AI_END_GAME_ACK: m_fsm->process_event(LeaveGame(msg, player_connection)); break;
case Message::MessageType::REQUEST_SAVE_PREVIEWS: UpdateSavePreviews(msg, player_connection); break;
case Message::MessageType::REQUEST_COMBAT_LOGS: m_fsm->process_event(RequestCombatLogs(msg, player_connection));break;
case Message::MessageType::LOGGER_CONFIG: HandleLoggerConfig(msg, player_connection); break;
default:
ErrorLogger() << "ServerApp::HandleMessage : Received an unknown message type \"" << msg.Type() << "\". Terminating connection.";
m_networking.Disconnect(player_connection);
break;
}
}
void ServerApp::HandleShutdownMessage(const Message& msg, PlayerConnectionPtr player_connection) {
int player_id = player_connection->PlayerID();
bool is_host = m_networking.PlayerIsHost(player_id);
if (!is_host) {
DebugLogger() << "ServerApp::HandleShutdownMessage rejecting shut down message from non-host player";
return;
}
DebugLogger() << "ServerApp::HandleShutdownMessage shutting down";
m_fsm->process_event(ShutdownServer());
}
void ServerApp::HandleLoggerConfig(const Message& msg, PlayerConnectionPtr player_connection) {
int player_id = player_connection->PlayerID();
bool is_host = m_networking.PlayerIsHost(player_id);
if (!is_host && m_networking.HostPlayerID() != Networking::INVALID_PLAYER_ID) {
WarnLogger() << "ServerApp::HandleLoggerConfig rejecting message from non-host player id = " << player_id;
return;
}
DebugLogger() << "Handling logging config message from the host.";
auto options = ExtractLoggerConfigMessageData(msg);
SetLoggerThresholds(options);
// Forward the message to all the AIs
const auto relay_options_message = LoggerConfigMessage(Networking::INVALID_PLAYER_ID, options);
for (auto players_it = m_networking.established_begin();
players_it != m_networking.established_end(); ++players_it)
{
if ((*players_it)->GetClientType() == Networking::ClientType::CLIENT_TYPE_AI_PLAYER) {
DebugLogger() << "Forwarding logging thresholds to AI " << (*players_it)->PlayerID();
(*players_it)->SendMessage(relay_options_message);
}
}
}
void ServerApp::HandleNonPlayerMessage(const Message& msg, PlayerConnectionPtr player_connection) {
switch (msg.Type()) {
case Message::MessageType::HOST_SP_GAME: m_fsm->process_event(HostSPGame(msg, player_connection)); break;
case Message::MessageType::HOST_MP_GAME: m_fsm->process_event(HostMPGame(msg, player_connection)); break;
case Message::MessageType::JOIN_GAME: m_fsm->process_event(JoinGame(msg, player_connection)); break;
case Message::MessageType::AUTH_RESPONSE: m_fsm->process_event(AuthResponse(msg, player_connection)); break;
case Message::MessageType::ERROR_MSG: m_fsm->process_event(Error(msg, player_connection)); break;
case Message::MessageType::DEBUG: break;
default:
if ((m_networking.size() == 1) &&
(player_connection->IsLocalConnection()) &&
(msg.Type() == Message::MessageType::SHUT_DOWN_SERVER))
{
DebugLogger() << "ServerApp::HandleNonPlayerMessage received Message::SHUT_DOWN_SERVER from the sole "
<< "connected player, who is local and so the request is being honored; server shutting down.";
m_fsm->process_event(ShutdownServer());
} else {
ErrorLogger() << "ServerApp::HandleNonPlayerMessage : Received an invalid message type \""
<< msg.Type() << "\" for a non-player Message. Terminating connection.";
m_networking.Disconnect(player_connection);
break;
}
}
}
void ServerApp::PlayerDisconnected(PlayerConnectionPtr player_connection)
{ m_fsm->process_event(Disconnection(player_connection)); }
void ServerApp::ShutdownTimedoutHandler(boost::system::error_code error) {
if (error)
DebugLogger() << "Shutdown timed out cancelled";
DebugLogger() << "Shutdown timed out. Disconnecting remaining clients.";
m_fsm->process_event(DisconnectClients());
}
void ServerApp::SelectNewHost() {
int new_host_id = Networking::INVALID_PLAYER_ID;
int old_host_id = m_networking.HostPlayerID();
DebugLogger() << "ServerApp::SelectNewHost old host id: " << old_host_id;
// scan through players for a human to host
for (auto players_it = m_networking.established_begin();
players_it != m_networking.established_end(); ++players_it)
{
PlayerConnectionPtr player_connection = *players_it;
if (player_connection->GetClientType() == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER ||
player_connection->GetClientType() == Networking::ClientType::CLIENT_TYPE_HUMAN_OBSERVER ||
player_connection->GetClientType() == Networking::ClientType::CLIENT_TYPE_HUMAN_MODERATOR)
{ new_host_id = player_connection->PlayerID(); }
}
if (new_host_id == Networking::INVALID_PLAYER_ID) {
// couldn't find a host... abort
DebugLogger() << "ServerApp::SelectNewHost : Host disconnected and couldn't find a replacement.";
m_networking.SendMessageAll(ErrorMessage(UserStringNop("SERVER_UNABLE_TO_SELECT_HOST"), false));
}
// set new host ID
m_networking.SetHostPlayerID(new_host_id);
// inform players.
for (PlayerConnectionPtr player : m_networking) {
if (player->PlayerID() != old_host_id)
player->SendMessage(HostIDMessage(new_host_id));
}
}
void ServerApp::NewSPGameInit(const SinglePlayerSetupData& single_player_setup_data) {
// associate player IDs with player setup data. the player connection with
// id == m_networking.HostPlayerID() should be the human player in
// PlayerSetupData. AI player connections are assigned one of the remaining
// PlayerSetupData entries that is for an AI player.
const auto& player_setup_data = single_player_setup_data.players;
NewGameInitConcurrentWithJoiners(single_player_setup_data, player_setup_data);
}
bool ServerApp::VerifySPGameAIs(const SinglePlayerSetupData& single_player_setup_data) {
const auto& player_setup_data = single_player_setup_data.players;
return NewGameInitVerifyJoiners(player_setup_data);
}
void ServerApp::NewMPGameInit(const MultiplayerLobbyData& multiplayer_lobby_data) {
// associate player IDs with player setup data by matching player IDs when
// available (human) and names (for AI clients which didn't have an ID
// before now because the lobby data was set up without connected/established
// clients for the AIs.
const auto& player_setup_data = multiplayer_lobby_data.players;
std::vector<PlayerSetupData> psds;
for (const auto& entry : player_setup_data) {
const PlayerSetupData& psd = entry.second;
if (psd.client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER ||
psd.client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_OBSERVER ||
psd.client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_MODERATOR)
{
// Human players have consistent IDs, so these can be easily
// matched between established player connections and setup data.
// find player connection with same ID as this player setup data
bool found_matched_id_connection = false;
int player_id = entry.first;
for (auto established_player_it = m_networking.established_begin();
established_player_it != m_networking.established_end(); ++established_player_it)
{
const PlayerConnectionPtr player_connection = *established_player_it;
if (player_connection->PlayerID() == player_id)
{
PlayerSetupData new_psd = psd;
new_psd.player_id = player_id;
psds.push_back(std::move(new_psd));
found_matched_id_connection = true;
break;
}
}
if (!found_matched_id_connection) {
if (player_id != Networking::INVALID_PLAYER_ID) {
ErrorLogger() << "ServerApp::NewMPGameInit couldn't find player setup data for human player with id: " << player_id << " player name: " << psd.player_name;
} else {
// There is no player currently connected for the current setup data. A player
// may connect later, at which time they may be assigned to this data or the
// corresponding empire.
psds.push_back(psd);
}
}
} else if (psd.client_type == Networking::ClientType::CLIENT_TYPE_AI_PLAYER) {
// All AI player setup data, as determined from their client type, is
// assigned to player IDs of established AI players with the appropriate names
// find player connection with same name as this player setup data
bool found_matched_name_connection = false;
const std::string& player_name = psd.player_name;
for (auto established_player_it = m_networking.established_begin();
established_player_it != m_networking.established_end(); ++established_player_it)
{
const PlayerConnectionPtr player_connection = *established_player_it;
if (player_connection->GetClientType() == Networking::ClientType::CLIENT_TYPE_AI_PLAYER &&
player_connection->PlayerName() == player_name)
{
// assign name-matched AI client's player setup data to appropriate AI connection
int player_id = player_connection->PlayerID();
PlayerSetupData new_psd = psd;
new_psd.player_id = player_id;
psds.push_back(std::move(new_psd));
found_matched_name_connection = true;
break;
}
}
if (!found_matched_name_connection)
ErrorLogger() << "ServerApp::NewMPGameInit couldn't find player setup data for AI player with name: " << player_name;
} else {
// do nothing for any other player type, until another player type
// is implemented. human observers don't need to be put into the
// map of id to player setup data, as they don't need empires to be
// created for them.
ErrorLogger() << "ServerApp::NewMPGameInit skipping unsupported client type in player setup data";
}
}
NewGameInitConcurrentWithJoiners(multiplayer_lobby_data, psds);
if (NewGameInitVerifyJoiners(psds))
SendNewGameStartMessages();
}
namespace {
constexpr auto uneliminated = [](const auto& id_empire) { return !id_empire.second->Eliminated(); };
void UpdateEmpireSupply(ScriptingContext& context, SupplyManager& supply, bool precombat) {
// Determine initial supply distribution and exchanging and resource pools for empires
for (auto& empire : context.Empires() | range_filter(uneliminated) | range_values) {
// determine which systems can propagate fleet and resource (same for both)
empire->UpdateSupplyUnobstructedSystems(context, precombat); // TODO: pass empire ID to use for known objects lookup?
// set range systems can propagate fleet and resourse supply (separately)
empire->UpdateSystemSupplyRanges(context.ContextUniverse());
}
supply.Update(context); // must call after updating supply ranges for all empires
}
void UpdateResourcePools(ScriptingContext& context,
const std::map<int, std::vector<std::tuple<std::string_view, double, int>>>& tech_costs_times,
const std::map<int, std::vector<std::pair<int, double>>>& annex_costs,
const std::map<int, std::vector<std::pair<std::string_view, double>>>& policy_costs,
const std::map<int, std::vector<std::tuple<std::string_view, int, float, int>>>& prod_costs)
{
const unsigned int num_threads = static_cast<unsigned int>(std::max(1, EffectsProcessingThreads()));
boost::asio::thread_pool thread_pool(num_threads);
for (auto& [empire_id, empire] : context.Empires() | range_filter(uneliminated)) {
const auto tct_it = std::find_if(tech_costs_times.begin(), tech_costs_times.end(),
[empire_id{empire_id}](const auto& tct) { return empire_id == tct.first; });
if (tct_it == tech_costs_times.end()) {
ErrorLogger() << "UpdateResourcePools in ServerApp couldn't find tech costs/times for empire " << empire_id;
continue;
}
const auto ac_it = std::find_if(annex_costs.begin(), annex_costs.end(),
[empire_id{empire_id}](const auto& ac) { return empire_id == ac.first; });
if (ac_it == annex_costs.end()) {
ErrorLogger() << "UpdateResourcePools in ServerApp couldn't find annex costs for empire " << empire_id;
continue;
}
const auto pc_it = std::find_if(policy_costs.begin(), policy_costs.end(),
[empire_id{empire_id}](const auto& pc) { return empire_id == pc.first; });
if (pc_it == policy_costs.end()) {
ErrorLogger() << "UpdateResourcePools in ServerApp couldn't find policy costs for empire " << empire_id;
continue;
}
const auto pct_it = std::find_if(prod_costs.begin(), prod_costs.end(),
[empire_id{empire_id}](const auto& pct) { return empire_id == pct.first; });
if (pct_it == prod_costs.end()) {
ErrorLogger() << "UpdateResourcePools in ServerApp couldn't find production costs/times for empire " << empire_id;
continue;
}
boost::asio::post(thread_pool, [&context, empire{empire.get()}, tct_it, ac_it, pc_it, pct_it]() {
// determine population centers and resource centers of empire, tells resource pools
// the centers and groups of systems that can share resources (note that being able to
// share resources doesn't mean a system produces resources)
empire->InitResourcePools(context.ContextObjects(), context.supply);
// determine how much of each resources is available in each resource sharing group
empire->UpdateResourcePools(context, tct_it->second, ac_it->second, pc_it->second, pct_it->second);
});
}
thread_pool.join();
}
}
void ServerApp::NewGameInitConcurrentWithJoiners(
const GalaxySetupData& galaxy_setup_data,
const std::vector<PlayerSetupData>& player_setup_data_in)
{
DebugLogger() << "ServerApp::NewGameInitConcurrentWithJoiners";
m_galaxy_setup_data = galaxy_setup_data;
// set game rules for server based on those specified in setup data
GetGameRules().SetFromStrings(m_galaxy_setup_data.GetGameRules());
// validate some connection info / determine which players need empires created
std::map<int, PlayerSetupData> active_empire_id_setup_data;
int next_empire_id = 1;
for (const auto& psd : player_setup_data_in) {
if (!psd.player_name.empty()
&& (psd.client_type == Networking::ClientType::CLIENT_TYPE_AI_PLAYER
|| psd.client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER))
{
active_empire_id_setup_data[next_empire_id++] = psd;
}
}
if (active_empire_id_setup_data.empty()) {
ErrorLogger() << "ServerApp::NewGameInitConcurrentWithJoiners found no active players!";
m_networking.SendMessageAll(ErrorMessage(UserStringNop("SERVER_FOUND_NO_ACTIVE_PLAYERS"), true));
return;
}
// clear previous game player state info
m_turn_sequence.clear();
m_player_empire_ids.clear();
m_empires.Clear();
// set server state info for new game
m_current_turn = BEFORE_FIRST_TURN;
m_turn_expired = false;
// create universe and empires for players
DebugLogger() << "ServerApp::NewGameInitConcurrentWithJoiners: Creating Universe";
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::GENERATING_UNIVERSE));
// m_current_turn set above so that every UniverseObject created before game
// starts will have m_created_on_turn BEFORE_FIRST_TURN
GenerateUniverse(active_empire_id_setup_data);
// after all game initialization stuff has been created, set current turn to 0 and apply only GenerateSitRep Effects
// so that a set of SitReps intended as the player's initial greeting will be segregated
m_current_turn = 0;
ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data,
m_species_manager, m_supply_manager};
m_universe.ApplyGenerateSitRepEffects(context);
//can set current turn to 1 for start of game
m_current_turn = 1;
// record empires for each active player. Note: active_empire_id_setup_data
// contains only data of players who control an empire; observers and
// moderators are not included.
for (auto& [empire_id, psd] : active_empire_id_setup_data) {
if (psd.player_id != Networking::INVALID_PLAYER_ID)
m_player_empire_ids[psd.player_id] = empire_id;
// add empires to turn processing
if (auto empire = m_empires.GetEmpire(empire_id)) {
AddEmpireTurn(empire_id, PlayerSaveGameData(psd.player_name, empire_id,
nullptr, nullptr, std::string(),
psd.client_type));
empire->SetReady(false);
}
}
// update visibility information to ensure data sent out is up-to-date
DebugLogger() << "ServerApp::NewGameInitConcurrentWithJoiners: Updating first-turn Empire stuff";
m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(context.current_turn);
// initialize empire owned object counters
for (auto& entry : m_empires)
entry.second->UpdateOwnedObjectCounters(m_universe);
UpdateEmpireSupply(context, m_supply_manager, false);
CacheCostsTimes(context);
UpdateResourcePools(context, m_cached_empire_research_costs_times,
m_cached_empire_annexation_costs, m_cached_empire_policy_adoption_costs,
m_cached_empire_production_costs_times);
m_universe.UpdateStatRecords(context);
}
bool ServerApp::NewGameInitVerifyJoiners(const std::vector<PlayerSetupData>& player_setup_data) {
DebugLogger() << "ServerApp::NewGameInitVerifyJoiners";
// associate player IDs with player setup data. the player connection with
// id == m_networking.HostPlayerID() should be the human player in
// PlayerSetupData. AI player connections are assigned one of the remaining
// PlayerSetupData entries that is for an AI player.
std::map<int, PlayerSetupData> player_id_setup_data;
bool host_in_player_id_setup_data = false;
for (const auto& psd : player_setup_data) {
if (psd.client_type == Networking::ClientType::INVALID_CLIENT_TYPE) {
ErrorLogger() << "Player with id " << psd.player_id << " has invalid client type";
continue;
}
player_id_setup_data[psd.player_id] = psd;
if (m_networking.HostPlayerID() == psd.player_id)
host_in_player_id_setup_data = true;
}
// ensure some reasonable inputs
if (player_id_setup_data.empty()) {
ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners passed empty player_id_setup_data. Aborting";
m_networking.SendMessageAll(ErrorMessage(UserStringNop("SERVER_FOUND_NO_ACTIVE_PLAYERS"), true));
return false;
}
if (!host_in_player_id_setup_data && !IsHostless()) {
ErrorLogger() << "NewGameInitVerifyJoiners : Host id " << m_networking.HostPlayerID()
<< " is not a valid player id.";
return false;
}
// ensure number of players connected and for which data are provided are consistent
if (m_networking.NumEstablishedPlayers() != player_id_setup_data.size() && !IsHostless()) {
ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners has " << m_networking.NumEstablishedPlayers()
<< " established players but " << player_id_setup_data.size() << " players in setup data.";
return false;
}
// validate some connection info / determine which players need empires created
for (auto player_connection_it = m_networking.established_begin();
player_connection_it != m_networking.established_end(); ++player_connection_it)
{
const PlayerConnectionPtr player_connection = *player_connection_it;
Networking::ClientType client_type = player_connection->GetClientType();
int player_id = player_connection->PlayerID();
auto player_id_setup_data_it = player_id_setup_data.find(player_id);
if (player_id_setup_data_it == player_id_setup_data.end()) {
ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners couldn't find player setup data for player with ID " << player_id;
return false;
}
const PlayerSetupData& psd = player_id_setup_data_it->second;
if (psd.client_type != client_type) {
ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners found inconsistent client type between player connection (" << client_type << ") and player setup data (" << psd.client_type << ")";
return false;
}
if (psd.player_name != player_connection->PlayerName()) {
ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners found inconsistent player names: " << psd.player_name << " and " << player_connection->PlayerName();
return false;
}
if (player_connection->PlayerName().empty()) {
ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners found player connection with empty name!";
return false;
}
if (!(client_type == Networking::ClientType::CLIENT_TYPE_AI_PLAYER
|| client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER
|| client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_OBSERVER
|| client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_MODERATOR))
{
ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners found player connection with unsupported client type.";
}
}
return true;
}
void ServerApp::SendNewGameStartMessages() {
std::map<int, PlayerInfo> player_info_map = GetPlayerInfoMap();
const ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data,
m_species_manager, m_supply_manager};
for (auto& empire : m_empires | range_values) {
empire->UpdateOwnedObjectCounters(m_universe);
empire->PrepQueueAvailabilityInfoForSerialization(context);
empire->PrepPolicyInfoForSerialization(context);
}
// send new game start messages
DebugLogger() << "SendGameStartMessages: Sending GameStartMessages to players";
for (auto player_connection_it = m_networking.established_begin(); // can't easily use range for loop due to non-standard begin and end
player_connection_it != m_networking.established_end(); ++player_connection_it)
{
const PlayerConnectionPtr player_connection = *player_connection_it;
int player_id = player_connection->PlayerID();
int empire_id = PlayerEmpireID(player_id);
bool use_binary_serialization = player_connection->IsBinarySerializationUsed();
player_connection->SendMessage(GameStartMessage(m_single_player_game, empire_id,
m_current_turn, m_empires,
m_universe, m_species_manager,
GetCombatLogManager(), m_supply_manager,
player_info_map, m_galaxy_setup_data,
use_binary_serialization,!player_connection->IsLocalConnection()),
empire_id, m_current_turn);
}
}
void ServerApp::LoadSPGameInit(const std::vector<PlayerSaveGameData>& player_save_game_data,
std::shared_ptr<ServerSaveGameData> server_save_game_data)
{
// Need to determine which data in player_save_game_data should be assigned to which established player
std::vector<std::pair<int, int>> player_id_to_save_game_data_index;
// assign all saved game data to a player ID
for (int i = 0; i < static_cast<int>(player_save_game_data.size()); ++i) {
const PlayerSaveGameData& psgd = player_save_game_data[i];
if (psgd.client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER) {
// In a single player game, the host player is always the human player, so
// this is just a matter of finding which entry in player_save_game_data was
// a human player, and assigning that saved player data to the host player ID
player_id_to_save_game_data_index.emplace_back(m_networking.HostPlayerID(), i);
} else if (psgd.client_type == Networking::ClientType::CLIENT_TYPE_AI_PLAYER) {
// All saved AI player data, as determined from their client type, is
// assigned to player IDs of established AI players
// cycle to find next established AI player
for (auto established_it = m_networking.established_begin(); established_it != m_networking.established_end(); ++established_it)
{
const PlayerConnectionPtr player_connection = *established_it;
if (player_connection->GetClientType() != Networking::ClientType::CLIENT_TYPE_AI_PLAYER
|| player_connection->PlayerName() != psgd.name)
continue;
int player_id = player_connection->PlayerID();
player_id_to_save_game_data_index.emplace_back(player_id, i);
break;
}
} else {
// do nothing for any other player type, until another player type is implemented
ErrorLogger() << "ServerApp::LoadSPGameInit skipping unsupported client type in player save game data";
}
}
LoadGameInit(player_save_game_data, player_id_to_save_game_data_index, server_save_game_data);
}
namespace {
/** Check that \p path is a file or directory in the server save
directory. */
bool IsInServerSaveDir(const fs::path& path) {
if (!fs::exists(path))
return false;
return IsInDir(GetServerSaveDir(),
(fs::is_regular_file(path) ? path.parent_path() : path));
}
/// Generates information on the subdirectories of \p directory
std::vector<std::string> ListSaveSubdirectories(const fs::path& directory) {
std::vector<std::string> list;
if (!fs::is_directory(directory))
return list;
auto server_dir_str = PathToString(fs::canonical(GetServerSaveDir()));
// Adds \p subdir to the list
auto add_to_list = [&list, &server_dir_str](const fs::path& subdir) {
auto subdir_str = PathToString(fs::canonical(subdir));
auto rel_path = subdir_str.substr(server_dir_str.length());
TraceLogger() << "Added relative path " << rel_path << " in " << subdir
<< " to save preview directories";
list.push_back(std::move(rel_path));
};
// Add parent dir if still within server_dir_str
auto parent = directory / "..";
if (IsInServerSaveDir(parent))
add_to_list(parent);
// Add all directories to list
fs::directory_iterator end;
for (fs::directory_iterator it(fs::canonical(directory)); it != end; ++it) {
if (!fs::is_directory(it->path()) || !IsInServerSaveDir(it->path()))
continue;
add_to_list(it->path());
}
return list;
}
}
void ServerApp::UpdateSavePreviews(const Message& msg,
PlayerConnectionPtr player_connection)
{
// Only relative paths are allowed to prevent client from list arbitrary
// directories, or knowing the absolute path of the server save directory.
std::string relative_directory_name;
ExtractRequestSavePreviewsMessageData(msg, relative_directory_name);
DebugLogger() << "ServerApp::UpdateSavePreviews: Preview request for sub directory: " << relative_directory_name;
fs::path directory = GetServerSaveDir() / FilenameToPath(relative_directory_name);
// Do not allow a relative path to explore outside the save directory.
bool contains_dot_dot = relative_directory_name.find("..") != std::string::npos;
if (contains_dot_dot || !IsInServerSaveDir(directory)) {
directory = GetServerSaveDir();
ErrorLogger() << "ServerApp::UpdateSavePreviews: Tried to load previews from "
<< relative_directory_name
<< " which is outside the allowed save directory. Defaulted to the save directory, "
<< directory;
relative_directory_name = ".";
}
PreviewInformation preview_information;
preview_information.folder = std::move(relative_directory_name);
preview_information.subdirectories = ListSaveSubdirectories(directory);
LoadSaveGamePreviews(
directory,
m_single_player_game? SP_SAVE_FILE_EXTENSION : MP_SAVE_FILE_EXTENSION,
preview_information.previews);
DebugLogger() << "ServerApp::UpdateSavePreviews: Sending " << preview_information.previews.size()
<< " previews in response.";
player_connection->SendMessage(DispatchSavePreviewsMessage(preview_information));
}
void ServerApp::UpdateCombatLogs(const Message& msg, PlayerConnectionPtr player_connection) {
std::vector<int> ids;
ExtractRequestCombatLogsMessageData(msg, ids);
// Compose a vector of the requested ids and logs
std::vector<std::pair<int, const CombatLog>> logs;
logs.reserve(ids.size());
for (auto it = ids.begin(); it != ids.end(); ++it) {
auto log = GetCombatLogManager().GetLog(*it);
if (!log) {
ErrorLogger() << "UpdateCombatLogs can't fetch log with id = "<< *it << " ... skipping.";
continue;
}
logs.emplace_back(*it, *log);
}
// Return them to the client
DebugLogger() << "UpdateCombatLogs returning " << logs.size()
<< " logs to player " << player_connection->PlayerID();
try {
bool use_binary_serialization = player_connection->IsBinarySerializationUsed();
player_connection->SendMessage(DispatchCombatLogsMessage(logs, use_binary_serialization,
!player_connection->IsLocalConnection()));
} catch (const std::exception& e) {
ErrorLogger() << "caught exception sending combat logs message: " << e.what();
std::vector<std::pair<int, const CombatLog>> empty_logs;
player_connection->SendMessage(DispatchCombatLogsMessage(empty_logs, false,
!player_connection->IsLocalConnection()));
}
}
void ServerApp::LoadChatHistory() {
// don't load history if it was already loaded
if (!m_chat_history.empty())
return;
bool success = false;
try {
m_python_server.SetCurrentDir(GetPythonChatDir());
// Call the Python load_history function
success = m_python_server.LoadChatHistory(m_chat_history);
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Attempting to restart.";
if (m_python_server.Initialize()) {
ErrorLogger() << "Python interpreter successfully restarted.";
} else {
ErrorLogger() << "Python interpreter failed to restart. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
}
if (!success) {
ErrorLogger() << "Python scripted chat failed.";
ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"),
false));
}
}
void ServerApp::PushChatMessage(std::string text, std::string player_name, std::array<uint8_t, 4> text_color,
const boost::posix_time::ptime timestamp)
{
ChatHistoryEntity chat{std::move(player_name), std::move(text), timestamp, text_color};
m_chat_history.push_back(chat);
bool success = false;
try {
m_python_server.SetCurrentDir(GetPythonChatDir());
// Call the Python load_history function
success = m_python_server.PutChatHistoryEntity(chat);
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Attempting to restart.";
if (m_python_server.Initialize()) {
ErrorLogger() << "Python interpreter successfully restarted.";
} else {
ErrorLogger() << "Python interpreter failed to restart. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
}
if (!success) {
ErrorLogger() << "Python scripted chat failed.";
ServerApp::GetApp()->Networking().SendMessageAll(
ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), false));
}
}
void ServerApp::ExpireTurn() {
InfoLogger() << "Turn was set to expired";
m_turn_expired = true;
}
bool ServerApp::IsHaveWinner() const
{ return std::any_of(m_empires.begin(), m_empires.end(), [](const auto& e) { return e.second->Won(); }); }
namespace {
/** Verifies that a human player is connected with the indicated \a id. */
bool HumanPlayerWithIdConnected(const ServerNetworking& sn, int id) {
// make sure there is a human player connected with the player id
// matching what this PlayerSetupData say
auto established_player_it = sn.GetPlayer(id);
if (established_player_it == sn.established_end()) {
ErrorLogger() << "ServerApp::LoadMPGameInit couldn't find player connection for "
<< "human player setup data with player id: " << id;
return false;
}
const PlayerConnectionPtr player_connection = *established_player_it;
if (player_connection->GetClientType() != Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER) {
ErrorLogger() << "ServerApp::LoadMPGameInit found player connection of wrong type "
<< "for human player setup data with player id: " << id;
return false;
}
return true;
}
/** Returns index into vector parameter that matches parameter empire id. */
int VectorIndexForPlayerSaveGameDataForEmpireID(const std::vector<PlayerSaveGameData>& player_save_game_data,
int empire_id)
{
if (empire_id == ALL_EMPIRES)
return -1;
// find save game data vector index that has requested empire id
for (int i = 0; i < static_cast<int>(player_save_game_data.size()); ++i) {
const PlayerSaveGameData& psgd = player_save_game_data.at(i);
if (psgd.empire_id == empire_id)
return i;
}
return -1;
}
/** Adds entry to \a player_id_to_save_game_data_index after validation. */
void GetSaveGameDataIndexForHumanPlayer(std::vector<std::pair<int, int>>& player_id_to_save_game_data_index,
const PlayerSetupData& psd, int setup_data_player_id,
const std::vector<PlayerSaveGameData>& player_save_game_data,
const ServerNetworking& sn)
{
// safety check: setup data has valid empire assigned
if (psd.save_game_empire_id == ALL_EMPIRES) {
ErrorLogger() << "ServerApp::LoadMPGameInit got player setup data for human player "
<< "with no empire assigned...";
return;
}
// safety check: id-matched player is connected
bool consistent_human_player_connected = HumanPlayerWithIdConnected(sn, setup_data_player_id);
if (!consistent_human_player_connected)
return; // error message logged in HumanPlayerWithIdConnected
// determine and store save game data index for this player
int index = VectorIndexForPlayerSaveGameDataForEmpireID(player_save_game_data, psd.save_game_empire_id);
if (index != -1) {
player_id_to_save_game_data_index.push_back({setup_data_player_id, index});
} else {
ErrorLogger() << "ServerApp::LoadMPGameInit couldn't find save game data for "
<< "human player with assigned empire id: " << psd.save_game_empire_id;
}
}
/** Returns ID of AI player with the indicated \a player_name. */
int AIPlayerIDWithName(const ServerNetworking& sn, const std::string& player_name) {
if (player_name.empty())
return Networking::INVALID_PLAYER_ID;
for (auto established_player_it = sn.established_begin();
established_player_it != sn.established_end(); ++established_player_it)
{
const PlayerConnectionPtr player_connection = *established_player_it;
if (player_connection->PlayerName() == player_name &&
player_connection->GetClientType() == Networking::ClientType::CLIENT_TYPE_AI_PLAYER)
{ return player_connection->PlayerID(); }
}
return Networking::INVALID_PLAYER_ID;
}
/** Adds entry to \a player_id_to_save_game_data_index after validation. */
void GetSaveGameDataIndexForAIPlayer(std::vector<std::pair<int, int>>& player_id_to_save_game_data_index,
const PlayerSetupData& psd,
const std::vector<PlayerSaveGameData>& player_save_game_data,
const ServerNetworking& sn)
{
// For AI players, the multplayer setup data does not specify a
// player ID because the AI processes aren't run until after the
// game settings are confirmed and the game started in the UI,
// and thus the AI clients don't connect and get assigned player
// ids until after the lobby setup is done.
//
// In order to assign save game data to players (ie. determine
// the save game data vector index for each player id), need to
// match another property in the setup data: the AI player names.
//
// So: attempt to find player connections that have the same name
// as is listed in the player setup data for AI players.
// safety check: setup data has valid empire assigned
if (psd.save_game_empire_id == ALL_EMPIRES) {
ErrorLogger() << "ServerApp::LoadMPGameInit got player setup data for AI player "
<< "with no empire assigned...";
return;
}
// get ID of name-matched AI player
const int player_id = AIPlayerIDWithName(sn, psd.player_name);
if (player_id == Networking::INVALID_PLAYER_ID) {
ErrorLogger() << "ServerApp::LoadMPGameInit couldn't find expected AI player with name " << psd.player_name;
return;
}
DebugLogger() << "ServerApp::LoadMPGameInit matched player named " << psd.player_name
<< " to setup data player id " << player_id
<< " with setup data empire id " << psd.save_game_empire_id;
// determine and store save game data index for this player
int index = VectorIndexForPlayerSaveGameDataForEmpireID(player_save_game_data, psd.save_game_empire_id);
if (index != -1) {
player_id_to_save_game_data_index.push_back({player_id, index});
} else {
ErrorLogger() << "ServerApp::LoadMPGameInit couldn't find save game data for "
<< "human player with assigned empire id: " << psd.save_game_empire_id;
}
}
}
void ServerApp::LoadMPGameInit(const MultiplayerLobbyData& lobby_data,
const std::vector<PlayerSaveGameData>& player_save_game_data,
std::shared_ptr<ServerSaveGameData> server_save_game_data)
{
// Need to determine which data in player_save_game_data should be assigned to which established player
std::vector<std::pair<int, int>> player_id_to_save_game_data_index;
const auto& player_setup_data = lobby_data.players;
// * Multiplayer lobby data has a map from player ID to PlayerSetupData.
// * PlayerSetupData contains an empire ID that the player will be controlling.
// * PlayerSaveGameData in a vector contain empire ID members.
// * LoadGameInit (called below) need an index in the PlayerSaveGameData vector
// for each player ID
// => Need to find which index into the PlayerSaveGameData vector has the right
// empire id for each player id.
// for every player setup data entry that represents an empire in the game,
// assign saved game data to the player ID of an established human or AI player
for (const auto& [setup_data_player_id, psd] : player_setup_data) {
if (psd.client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER) {
GetSaveGameDataIndexForHumanPlayer(player_id_to_save_game_data_index, psd,
setup_data_player_id, player_save_game_data,
m_networking);
} else if (psd.client_type == Networking::ClientType::CLIENT_TYPE_AI_PLAYER) {
// AI clients have no player id in setup data (even though humans do)
GetSaveGameDataIndexForAIPlayer(player_id_to_save_game_data_index, psd,
player_save_game_data, m_networking);
}
// do nothing for any other player type, until another player type
// is implemented. human observers and moderators don't need to be
// put into the map of id to player setup data, as they don't need
// empires to be created for them.
}
LoadGameInit(player_save_game_data, player_id_to_save_game_data_index, server_save_game_data);
}
void ServerApp::LoadGameInit(const std::vector<PlayerSaveGameData>& player_save_game_data,
const std::vector<std::pair<int, int>>& player_id_to_save_game_data_index,
std::shared_ptr<ServerSaveGameData> server_save_game_data)
{
DebugLogger() << "ServerApp::LoadGameInit";
// ensure some reasonable inputs
if (player_save_game_data.empty()) {
ErrorLogger() << "ServerApp::LoadGameInit passed empty player save game data. Aborting";
m_networking.SendMessageAll(ErrorMessage(UserStringNop("SERVER_FOUND_NO_ACTIVE_PLAYERS"), true));
return;
}
// ensure number of players connected and for which data are provided are consistent
if (player_id_to_save_game_data_index.size() != player_save_game_data.size())
ErrorLogger() << "ServerApp::LoadGameInit passed index mapping and player save game data are of different sizes...";
if (m_networking.NumEstablishedPlayers() != player_save_game_data.size())
ErrorLogger() << "ServerApp::LoadGameInit has " << m_networking.NumEstablishedPlayers()
<< " established players but " << player_save_game_data.size()
<< " entries in player save game data. Could be ok... so not aborting, but might crash";
// set game rules for server based on those specified in setup data
GetGameRules().SetFromStrings(m_galaxy_setup_data.GetGameRules());
// validate some connection info
for (auto player_connection_it = m_networking.established_begin();
player_connection_it != m_networking.established_end(); ++player_connection_it)
{
const PlayerConnectionPtr player_connection = *player_connection_it;
Networking::ClientType client_type = player_connection->GetClientType();
if (client_type != Networking::ClientType::CLIENT_TYPE_AI_PLAYER &&
client_type != Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER)
{
ErrorLogger() << "ServerApp::LoadGameInit found player connection with unsupported client type.";
}
if (player_connection->PlayerName().empty())
ErrorLogger() << "ServerApp::LoadGameInit found player connection with empty name!";
}
// clear previous game player state info
m_turn_sequence.clear();
m_player_empire_ids.clear();
// restore server state info from save
m_current_turn = server_save_game_data->current_turn;
std::map<int, PlayerSaveGameData> player_id_save_game_data;
// add empires to turn processing and record empires for each player
for (auto player_connection_it = m_networking.established_begin();
player_connection_it != m_networking.established_end(); ++player_connection_it)
{
const PlayerConnectionPtr player_connection = *player_connection_it;
if (!player_connection->IsEstablished()) {
ErrorLogger() << "LoadGameInit got player from connection";
continue;
}
int player_id = player_connection->PlayerID();
// get index into save game data for this player id
int player_save_game_data_index = -1; // default invalid index
for (const std::pair<int, int>& entry : player_id_to_save_game_data_index) {
int index_player_id = entry.first;
if (player_id != index_player_id)
continue;
player_save_game_data_index = entry.second;
break;
}
if (player_save_game_data_index == -1) {
DebugLogger() << "No save game data index for player with id " << player_id;
continue;
}
// get the player's saved game data
int empire_id = ALL_EMPIRES;
try {
const PlayerSaveGameData& psgd = player_save_game_data.at(player_save_game_data_index);
empire_id = psgd.empire_id; // can't use GetPlayerEmpireID here because m_player_empire_ids hasn't been set up yet.
player_id_save_game_data[player_id] = psgd; // store by player ID for easier access later
} catch (...) {
ErrorLogger() << "ServerApp::LoadGameInit couldn't find save game data with index " << player_save_game_data_index;
continue;
}
// record player id to empire id mapping in loaded game. Player IDs
// and empire IDs are not necessarily the same when loading a game as
// the player controlling a particular empire might have a different
// player ID than when the game was first created
m_player_empire_ids[player_id] = empire_id;
// set actual authentication status
if (auto empire = m_empires.GetEmpire(empire_id))
empire->SetAuthenticated(player_connection->IsAuthenticated());
}
for (const auto& psgd : player_save_game_data) {
const int empire_id = psgd.empire_id;
// add empires to turn processing, and restore saved orders and UI data or save state data
if (auto empire = m_empires.GetEmpire(empire_id)) {
if (!empire->Eliminated())
AddEmpireTurn(empire_id, psgd);
} else {
ErrorLogger() << "ServerApp::LoadGameInit couldn't find empire with id " << empire_id << " to add to turn processing";
}
}
// the Universe's system graphs for each empire aren't stored when saving
// so need to be reinitialized when loading based on the gamestate
m_universe.InitializeSystemGraph(m_empires, m_universe.Objects());
m_universe.UpdateEmpireVisibilityFilteredSystemGraphsWithOwnObjectMaps(m_empires);
ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data, m_species_manager,m_supply_manager};
UpdateEmpireSupply(context, m_supply_manager, true); // precombat supply update
CacheCostsTimes(context);
UpdateResourcePools(context, m_cached_empire_research_costs_times,
m_cached_empire_annexation_costs, m_cached_empire_policy_adoption_costs,
m_cached_empire_production_costs_times);
const auto player_info_map = GetPlayerInfoMap();
for (auto& empire : m_empires | range_values) {
empire->UpdateOwnedObjectCounters(m_universe);
empire->PrepQueueAvailabilityInfoForSerialization(context);
empire->PrepPolicyInfoForSerialization(context);
}
// assemble player state information, and send game start messages
DebugLogger() << "ServerApp::CommonGameInit: Sending GameStartMessages to players";
for (auto player_connection_it = m_networking.established_begin();
player_connection_it != m_networking.established_end(); ++player_connection_it)
{
const PlayerConnectionPtr player_connection = *player_connection_it;
const int player_id = player_connection->PlayerID();
Networking::ClientType client_type = player_connection->GetClientType();
// attempt to find saved state data for this player.
PlayerSaveGameData psgd;
const auto save_data_it = player_id_save_game_data.find(player_id);
if (save_data_it != player_id_save_game_data.end())
psgd = save_data_it->second;
if (!psgd.orders)
psgd.orders = std::make_shared<OrderSet>(); // need an empty order set pointed to for serialization in case no data is loaded but the game start message wants orders to send
// get empire ID for player. safety check on it.
const int empire_id = PlayerEmpireID(player_id);
if (empire_id != psgd.empire_id)
ErrorLogger() << "LoadGameInit got inconsistent empire ids between player save game data and result of PlayerEmpireID";
// Revoke readiness only for online players so they can redo orders for the current turn.
// Without doing it, server would immediatly advance the turn because saves are made when
// all players sent orders and became ready.
RevokeEmpireTurnReadyness(empire_id);
// restore saved orders. these will be re-executed on client and
// re-sent to the server (after possibly modification) by clients
// when they end their turn
const auto orders{psgd.orders};
const bool use_binary_serialization = player_connection->IsBinarySerializationUsed();
if (client_type == Networking::ClientType::CLIENT_TYPE_AI_PLAYER) {
// get save state string
const std::string* sss = nullptr;
if (!psgd.save_state_string.empty())
sss = &psgd.save_state_string;
player_connection->SendMessage(GameStartMessage(m_single_player_game, empire_id,
m_current_turn, m_empires, m_universe,
m_species_manager, GetCombatLogManager(),
m_supply_manager, player_info_map, *orders, sss,
m_galaxy_setup_data, use_binary_serialization,
!player_connection->IsLocalConnection()),
empire_id, m_current_turn);
} else if (client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER) {
player_connection->SendMessage(GameStartMessage(m_single_player_game, empire_id,
m_current_turn, m_empires, m_universe,
m_species_manager, GetCombatLogManager(),
m_supply_manager, player_info_map, *orders,
psgd.ui_data.get(), m_galaxy_setup_data,
use_binary_serialization,
!player_connection->IsLocalConnection()),
empire_id, m_current_turn);
} else if (client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_OBSERVER ||
client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_MODERATOR)
{
player_connection->SendMessage(GameStartMessage(m_single_player_game, ALL_EMPIRES,
m_current_turn, m_empires, m_universe,
m_species_manager, GetCombatLogManager(),
m_supply_manager, player_info_map,
m_galaxy_setup_data, use_binary_serialization,
!player_connection->IsLocalConnection()));
} else {
ErrorLogger() << "ServerApp::CommonGameInit unsupported client type: skipping game start message.";
}
}
}
void ServerApp::GenerateUniverse(std::map<int, PlayerSetupData>& player_setup_data) {
// Set game UID. Needs to be done first so we can use ClockSeed to
// prevent reproducible UIDs.
ClockSeed();
if (GetOptionsDB().Get<std::string>("setup.game.uid").empty())
m_galaxy_setup_data.SetGameUID(boost::uuids::to_string(boost::uuids::random_generator()()));
// Initialize RNG with provided seed to get reproducible universes
int seed = 0;
try {
seed = boost::lexical_cast<unsigned int>(m_galaxy_setup_data.seed);
} catch (...) {
try {
boost::hash<std::string> string_hash;
std::size_t h = string_hash(m_galaxy_setup_data.seed);
seed = static_cast<unsigned int>(h);
} catch (...) {}
}
if (m_galaxy_setup_data.GetSeed().empty() || m_galaxy_setup_data.GetSeed() == "RANDOM") {
//ClockSeed();
// replicate ClockSeed code here so can log the seed used
boost::posix_time::ptime ltime = boost::posix_time::microsec_clock::local_time();
std::string new_seed = boost::posix_time::to_simple_string(ltime);
boost::hash<std::string> string_hash;
std::size_t h = string_hash(new_seed);
DebugLogger() << "GenerateUniverse using clock for seed:" << new_seed;
seed = static_cast<unsigned int>(h);
// store seed in galaxy setup data
m_galaxy_setup_data.SetSeed(std::to_string(seed));
}
Seed(seed);
DebugLogger() << "GenerateUniverse with seed: " << seed;
// Reset the universe object for a new universe
m_universe.Clear();
m_species_manager.ClearSpeciesHomeworlds();
// Reset the object id manager for the new empires.
std::vector<int> empire_ids(player_setup_data.size());
std::transform(player_setup_data.begin(), player_setup_data.end(), empire_ids.begin(),
[](const auto& ii) { return ii.first; });
m_universe.ResetAllIDAllocation(empire_ids);
// Add predefined ship designs to universe
GetPredefinedShipDesignManager().AddShipDesignsToUniverse(m_universe);
// Initialize empire objects for each player
InitEmpires(player_setup_data, m_empires);
bool success{false};
try {
// Set Python current work directory to directory containing
// the universe generation Python scripts
m_python_server.SetCurrentDir(GetPythonUniverseGeneratorDir());
// Call the main Python universe generator function
success = m_python_server.CreateUniverse(player_setup_data);
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
if (!success)
ServerApp::GetApp()->Networking().SendMessageAll(
ErrorMessage(UserStringNop("SERVER_UNIVERSE_GENERATION_ERRORS"), false));
for (auto& empire : m_empires) {
empire.second->ApplyNewTechs(m_universe, m_current_turn);
empire.second->ApplyPolicies(m_universe, m_current_turn);
}
DebugLogger() << "Applying first turn effects and updating meters";
ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data, m_species_manager, m_supply_manager};
// Apply effects for 1st turn.
m_universe.ApplyAllEffectsAndUpdateMeters(context, false);
TraceLogger(effects) << "After First turn meter effect applying: " << m_universe.Objects().Dump();
// Set active meters to targets or maxes after first meter effects application
m_universe.BackPropagateObjectMeters();
m_species_manager.BackPropagateOpinions();
SetActiveMetersToTargetMaxCurrentValues(m_universe.Objects());
m_universe.UpdateMeterEstimates(context);
m_universe.BackPropagateObjectMeters();
m_species_manager.BackPropagateOpinions();
SetActiveMetersToTargetMaxCurrentValues(m_universe.Objects());
m_universe.BackPropagateObjectMeters();
m_species_manager.BackPropagateOpinions();
TraceLogger(effects) << "After First active set to target/max: " << m_universe.Objects().Dump();
m_universe.BackPropagateObjectMeters();
m_empires.BackPropagateMeters();
m_species_manager.BackPropagateOpinions();
DebugLogger() << "Re-applying first turn meter effects and updating meters";
// Re-apply meter effects, so that results depending on meter values can be
// re-checked after initial setting of those meter values
m_universe.ApplyMeterEffectsAndUpdateMeters(context, false);
// Re-set active meters to targets after re-application of effects
SetActiveMetersToTargetMaxCurrentValues(m_universe.Objects());
// Set the population of unowned planets to a random fraction of their target values.
SetNativePopulationValues(m_universe.Objects());
m_universe.BackPropagateObjectMeters();
m_empires.BackPropagateMeters();
m_species_manager.BackPropagateOpinions();
TraceLogger() << "!!!!!!!!!!!!!!!!!!! After setting active meters to targets";
TraceLogger() << m_universe.Objects().Dump();
m_universe.UpdateEmpireObjectVisibilities(context);
}
void ServerApp::ExecuteScriptedTurnEvents() {
bool success(false);
try {
m_python_server.SetCurrentDir(GetPythonTurnEventsDir());
// Call the main Python turn events function
success = m_python_server.ExecuteTurnEvents();
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Attempting to restart.";
if (m_python_server.Initialize()) {
ErrorLogger() << "Python interpreter successfully restarted.";
} else {
ErrorLogger() << "Python interpreter failed to restart. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
}
if (!success) {
ErrorLogger() << "Python scripted turn events failed.";
ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), false));
}
}
std::map<int, PlayerInfo> ServerApp::GetPlayerInfoMap() const {
// compile information about players to send out to other players at start of game.
DebugLogger() << "ServerApp::GetPlayerInfoMap: Compiling PlayerInfo for each player";
std::map<int, PlayerInfo> player_info_map;
for (auto player_connection_it = m_networking.established_begin();
player_connection_it != m_networking.established_end(); ++player_connection_it)
{
const PlayerConnectionPtr player_connection = *player_connection_it;
int player_id = player_connection->PlayerID();
int empire_id = PlayerEmpireID(player_id);
if (empire_id == ALL_EMPIRES)
ErrorLogger() << "ServerApp::GetPlayerInfoMap: couldn't find an empire for player with id " << player_id;
// validate some connection info
Networking::ClientType client_type = player_connection->GetClientType();
if (client_type != Networking::ClientType::CLIENT_TYPE_AI_PLAYER && client_type != Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER) {
ErrorLogger() << "ServerApp::GetPlayerInfoMap found player connection with unsupported client type.";
}
if (player_connection->PlayerName().empty()) {
ErrorLogger() << "ServerApp::GetPlayerInfoMap found player connection with empty name!";
}
// assemble player info for all players
player_info_map[player_id] = PlayerInfo{player_connection->PlayerName(),
empire_id,
player_connection->GetClientType(),
m_networking.PlayerIsHost(player_connection->PlayerID())};
}
return player_info_map;
}
int ServerApp::PlayerEmpireID(int player_id) const {
auto it = m_player_empire_ids.find(player_id);
if (it != m_player_empire_ids.end())
return it->second;
else
return ALL_EMPIRES;
}
int ServerApp::EmpirePlayerID(int empire_id) const {
for (const auto& entry : m_player_empire_ids)
if (entry.second == empire_id)
return entry.first;
return Networking::INVALID_PLAYER_ID;
}
bool ServerApp::IsLocalHumanPlayer(int player_id) {
auto it = m_networking.GetPlayer(player_id);
if (it == m_networking.established_end()) {
ErrorLogger() << "ServerApp::IsLocalHumanPlayer : could not get player connection for player id " << player_id;
return false;
}
PlayerConnectionPtr player_connection = *it;
return ((player_connection->GetClientType() == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER) &&
player_connection->IsLocalConnection());
}
bool ServerApp::IsAvailableName(const std::string& player_name) const {
if (player_name.empty())
return false;
for (auto it = m_networking.established_begin();
it != m_networking.established_end(); ++it)
{
if ((*it)->PlayerName() == player_name)
return false;
}
// check if some name reserved with cookie
return m_networking.IsAvailableNameInCookies(player_name);
}
bool ServerApp::IsAuthRequiredOrFillRoles(const std::string& player_name, const std::string& ip_address, Networking::AuthRoles& roles) {
bool result = false;
bool success = false;
try {
m_python_server.SetCurrentDir(GetPythonAuthDir());
// Call the main Python turn events function
success = m_python_server.IsRequireAuthOrReturnRoles(player_name, ip_address, result, roles);
if (GetOptionsDB().Get<bool>("network.server.allow-observers")) {
roles.SetRole(Networking::RoleType::ROLE_CLIENT_TYPE_OBSERVER, true);
}
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Attempting to restart.";
if (m_python_server.Initialize()) {
ErrorLogger() << "Python interpreter successfully restarted.";
} else {
ErrorLogger() << "Python interpreter failed to restart. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
}
if (!success) {
ErrorLogger() << "Python scripted authentication failed.";
ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"),
false));
}
return result;
}
bool ServerApp::IsAuthSuccessAndFillRoles(const std::string& player_name,
const std::string& auth, Networking::AuthRoles& roles)
{
bool result = false;
bool success = false;
try {
m_python_server.SetCurrentDir(GetPythonAuthDir());
// Call the main Python turn events function
success = m_python_server.IsSuccessAuthAndReturnRoles(player_name, auth, result, roles);
if (GetOptionsDB().Get<bool>("network.server.allow-observers")) {
roles.SetRole(Networking::RoleType::ROLE_CLIENT_TYPE_OBSERVER, true);
}
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Attempting to restart.";
if (m_python_server.Initialize()) {
ErrorLogger() << "Python interpreter successfully restarted.";
} else {
ErrorLogger() << "Python interpreter failed to restart. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
}
if (!success) {
ErrorLogger() << "Python scripted authentication failed.";
ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"),
false));
}
return result;
}
std::vector<PlayerSetupData> ServerApp::FillListPlayers() {
std::vector<PlayerSetupData> result;
bool success = false;
try {
m_python_server.SetCurrentDir(GetPythonAuthDir());
success = m_python_server.FillListPlayers(result);
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Attempting to restart.";
if (m_python_server.Initialize()) {
ErrorLogger() << "Python interpreter successfully restarted.";
} else {
ErrorLogger() << "Python interpreter failed to restart. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
}
if (!success) {
ErrorLogger() << "Python scripted player list failed.";
ServerApp::GetApp()->Networking().SendMessageAll(
ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), false));
}
return result;
}
void ServerApp::AddObserverPlayerIntoGame(const PlayerConnectionPtr& player_connection) {
std::map<int, PlayerInfo> player_info_map = GetPlayerInfoMap();
Networking::ClientType client_type = player_connection->GetClientType();
bool use_binary_serialization = player_connection->IsBinarySerializationUsed();
if (client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_OBSERVER ||
client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_MODERATOR)
{
// simply sends GAME_START message so established player will known he is in the game now
player_connection->SendMessage(GameStartMessage(m_single_player_game, ALL_EMPIRES,
m_current_turn, m_empires, m_universe,
m_species_manager, GetCombatLogManager(),
m_supply_manager, player_info_map,
m_galaxy_setup_data, use_binary_serialization,
!player_connection->IsLocalConnection()));
} else {
ErrorLogger() << "ServerApp::CommonGameInit unsupported client type: skipping game start message.";
}
}
bool ServerApp::EliminatePlayer(const PlayerConnectionPtr& player_connection) {
if (!GetGameRules().Get<bool>("RULE_ALLOW_CONCEDE")) {
player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CONCEDE_DISABLED"), false));
return false;
}
const int player_id = player_connection->PlayerID();
const int empire_id = PlayerEmpireID(player_id);
if (empire_id == ALL_EMPIRES) {
player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_NONPLAYER_CANNOT_CONCEDE"), false));
return false;
}
// test if there other human or disconnected players in the game
bool other_human_player = false;
for (auto& [loop_empire_id, loop_empire] : m_empires) {
if (!loop_empire->Eliminated() && empire_id != loop_empire_id) {
const auto other_client_type = GetEmpireClientType(loop_empire_id);
if (other_client_type == Networking::ClientType::CLIENT_TYPE_HUMAN_PLAYER ||
other_client_type == Networking::ClientType::INVALID_CLIENT_TYPE)
{
other_human_player = true;
break;
}
}
}
if (!other_human_player) {
player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CONCEDE_LAST_HUMAN_PLAYER"), false));
return false;
}
auto empire = m_empires.GetEmpire(empire_id);
if (!empire) {
player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_NONPLAYER_CANNOT_CONCEDE"), false));
return false;
}
auto is_owned = [empire_id](const UniverseObject* obj) noexcept { return obj->OwnedBy(empire_id); };
// test for colonies count
auto planets = m_universe.Objects().findRaw<Planet>(is_owned);
if (planets.size() > static_cast<std::size_t>(GetGameRules().Get<int>("RULE_CONCEDE_COLONIES_THRESHOLD"))) {
player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CONCEDE_EXCEED_COLONIES"), false));
return false;
}
// empire elimination
empire->Eliminate(m_empires, m_current_turn);
const auto recurse_des = [this](int id) {
#if (!defined(__clang_major__) || (__clang_major__ >= 16)) && (BOOST_VERSION >= 107700)
m_universe.RecursiveDestroy(id, m_empires.EmpireIDs());
#else
const auto& empire_ids = m_empires.EmpireIDs();
const std::vector<int> empire_ids_vec(empire_ids.begin(), empire_ids.end());
const std::span<const int> empire_ids_span(empire_ids_vec);
m_universe.RecursiveDestroy(id, empire_ids_span);
#endif
};
const bool destroy_ships = GetGameRules().Get<bool>("RULE_CONCEDE_DESTROY_SHIPS");
const bool destroy_buildings = GetGameRules().Get<bool>("RULE_CONCEDE_DESTROY_BUILDINGS");
const bool depop_planets = GetGameRules().Get<bool>("RULE_CONCEDE_DESTROY_COLONIES");
for (auto* obj : m_universe.Objects().findRaw<Ship>(is_owned))
obj->SetOwner(ALL_EMPIRES);
for (auto* obj : m_universe.Objects().findRaw<Fleet>(is_owned)) {
obj->SetOwner(ALL_EMPIRES);
if (destroy_ships)
recurse_des(obj->ID());
}
for (auto* obj : m_universe.Objects().findRaw<Building>(is_owned)) {
obj->SetOwner(ALL_EMPIRES);
if (destroy_buildings)
recurse_des(obj->ID());
}
for (auto* planet : planets) {
planet->SetOwner(ALL_EMPIRES);
if (depop_planets)
planet->Reset(m_universe.Objects());
}
// Don't wait for turn
RemoveEmpireTurn(empire_id);
// break link between player and empire
m_player_empire_ids.erase(player_id);
// notify other player that this empire finished orders
// so them don't think player of eliminated empire is making its turn too long
for (auto player_it = m_networking.established_begin();
player_it != m_networking.established_end(); ++player_it)
{
PlayerConnectionPtr player_ctn = *player_it;
player_ctn->SendMessage(PlayerStatusMessage(Message::PlayerStatus::WAITING, empire_id));
}
return true;
}
void ServerApp::DropPlayerEmpireLink(int player_id)
{ m_player_empire_ids.erase(player_id); }
int ServerApp::AddPlayerIntoGame(const PlayerConnectionPtr& player_connection, int target_empire_id) {
std::shared_ptr<Empire> empire;
int empire_id = ALL_EMPIRES;
auto delegation = GetPlayerDelegation(player_connection->PlayerName());
if (GetOptionsDB().Get<bool>("network.server.take-over-ai")) {
for (auto& e : m_empires) {
if (!e.second->Eliminated() &&
GetEmpireClientType(e.first) == Networking::ClientType::CLIENT_TYPE_AI_PLAYER)
{
delegation.push_back(e.second->PlayerName());
}
}
}
if (target_empire_id == ALL_EMPIRES) {
// search empire by player name
for (auto& [loop_empire_id, loop_empire] : m_empires) {
if (loop_empire->PlayerName() == player_connection->PlayerName()) {
empire_id = loop_empire_id;
empire = loop_empire;
break;
}
}
// Assign player to empire if he doesn't have own empire and delegates signle
if (delegation.size() == 1 && !empire) {
for (auto& [loop_empire_id, loop_empire] : m_empires) {
if (loop_empire->PlayerName() == delegation.front()) {
empire_id = loop_empire_id;
empire = loop_empire;
break;
}
}
}
if (!delegation.empty()) {
DebugLogger() << "ServerApp::AddPlayerIntoGame(...): Player should choose between delegates.";
return ALL_EMPIRES;
}
} else {
// use provided empire and test if it's player himself or one of delegated
empire_id = target_empire_id;
empire = m_empires.GetEmpire(target_empire_id);
if (!empire)
return ALL_EMPIRES;
if (empire->PlayerName() != player_connection->PlayerName()) {
bool matched = false;
for (const auto& delegated : delegation) {
if (empire->PlayerName() == delegated) {
matched = true;
break;
}
}
if (!matched)
return ALL_EMPIRES;
}
}
if (empire_id == ALL_EMPIRES || !empire)
return ALL_EMPIRES;
if (empire->Eliminated())
return ALL_EMPIRES;
auto orders_it = m_turn_sequence.find(empire_id);
if (orders_it == m_turn_sequence.end()) {
WarnLogger() << "ServerApp::AddPlayerIntoGame empire " << empire_id
<< " for \"" << player_connection->PlayerName()
<< "\" doesn't wait for orders";
return ALL_EMPIRES;
}
int previous_player_id = EmpirePlayerID(empire_id);
// make a link to new connection
m_player_empire_ids[player_connection->PlayerID()] = empire_id;
empire->SetAuthenticated(player_connection->IsAuthenticated());
// drop previous connection to that empire
if (previous_player_id != Networking::INVALID_PLAYER_ID && previous_player_id != player_connection->PlayerID()) {
WarnLogger() << "ServerApp::AddPlayerIntoGame empire " << empire_id
<< " previous player " << previous_player_id
<< " was kicked.";
DropPlayerEmpireLink(previous_player_id);
auto previous_it = m_networking.GetPlayer(previous_player_id);
if (previous_it != m_networking.established_end()) {
const Networking::ClientType previous_client_type = (*previous_it)->GetClientType();
const std::string previous_player_name = (*previous_it)->PlayerName();
m_networking.Disconnect(previous_player_id);
if (previous_client_type == Networking::ClientType::CLIENT_TYPE_AI_PLAYER) {
// change empire's player so after reload the player still could connect
// to the empire
empire->SetPlayerName(player_connection->PlayerName());
// kill unneeded AI process
auto it = m_ai_client_processes.find(previous_player_name);
if (it != m_ai_client_processes.end()) {
it->second.Kill();
m_ai_client_processes.erase(it);
}
}
}
}
InfoLogger() << "ServerApp::AddPlayerIntoGame empire " << empire_id << " connected to " << player_connection->PlayerID();
const OrderSet dummy;
const OrderSet& orders = orders_it->second && orders_it->second->orders ? *(orders_it->second->orders) : dummy;
const SaveGameUIData* ui_data = orders_it->second ? orders_it->second->ui_data.get() : nullptr;
if (GetOptionsDB().Get<bool>("network.server.drop-empire-ready")) {
// drop ready status
empire->SetReady(false);
m_networking.SendMessageAll(PlayerStatusMessage(Message::PlayerStatus::PLAYING_TURN, empire_id));
}
const auto player_info_map = GetPlayerInfoMap();
const bool use_binary_serialization = player_connection->IsBinarySerializationUsed();
const ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data,
m_species_manager, m_supply_manager};
for (auto& empire : m_empires | range_values) {
empire->UpdateOwnedObjectCounters(m_universe);
empire->PrepQueueAvailabilityInfoForSerialization(context);
empire->PrepPolicyInfoForSerialization(context);
}
player_connection->SendMessage(GameStartMessage(
m_single_player_game, empire_id,
m_current_turn, m_empires, m_universe,
m_species_manager, GetCombatLogManager(),
m_supply_manager, player_info_map, orders,
ui_data,
m_galaxy_setup_data,
use_binary_serialization,
!player_connection->IsLocalConnection()),
empire_id,
m_current_turn);
return empire_id;
}
std::vector<std::string> ServerApp::GetPlayerDelegation(const std::string& player_name) {
std::vector<std::string> result;
bool success = false;
try {
m_python_server.SetCurrentDir(GetPythonAuthDir());
// Call the auth provider function get_player_delegation
success = m_python_server.GetPlayerDelegation(player_name, result);
} catch (const boost::python::error_already_set&) {
success = false;
m_python_server.HandleErrorAlreadySet();
if (!m_python_server.IsPythonRunning()) {
ErrorLogger() << "Python interpreter is no longer running. Attempting to restart.";
if (m_python_server.Initialize()) {
ErrorLogger() << "Python interpreter successfully restarted.";
} else {
ErrorLogger() << "Python interpreter failed to restart. Exiting.";
m_fsm->process_event(ShutdownServer());
}
}
}
if (!success) {
ErrorLogger() << "Python scripted authentication failed.";
ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"),
false));
}
return {result.begin(), result.end()};
}
bool ServerApp::IsHostless() const
{ return GetOptionsDB().Get<bool>("hostless"); }
const boost::circular_buffer<ChatHistoryEntity>& ServerApp::GetChatHistory() const
{ return m_chat_history; }
std::vector<PlayerSaveGameData> ServerApp::GetPlayerSaveGameData() const {
std::vector<PlayerSaveGameData> player_save_game_data;
for (const auto& [empire_id, save_data] : m_turn_sequence) {
DebugLogger() << "ServerApp::GetPlayerSaveGameData() Empire " << empire_id
<< " type: " << to_string(save_data->client_type)
<< " save_game_data state string size: " << save_data->save_state_string.size()
<< " UI data?: " << save_data->ui_data;
if (save_data)
player_save_game_data.push_back(*save_data);
}
return player_save_game_data;
}
Networking::ClientType ServerApp::GetEmpireClientType(int empire_id) const
{ return GetPlayerClientType(ServerApp::EmpirePlayerID(empire_id)); }
Networking::ClientType ServerApp::GetPlayerClientType(int player_id) const {
if (player_id == Networking::INVALID_PLAYER_ID)
return Networking::ClientType::INVALID_CLIENT_TYPE;
const auto it = m_networking.GetPlayer(player_id);
if (it == m_networking.established_end())
return Networking::ClientType::INVALID_CLIENT_TYPE;
const auto& player_connection = *it;
return player_connection->GetClientType();
}
int ServerApp::EffectsProcessingThreads() const
{ return GetOptionsDB().Get<int>("effects.server.threads"); }
void ServerApp::AddEmpireTurn(int empire_id, const PlayerSaveGameData& psgd)
{ m_turn_sequence[empire_id] = std::make_unique<PlayerSaveGameData>(psgd); }
void ServerApp::RemoveEmpireTurn(int empire_id)
{ m_turn_sequence.erase(empire_id); }
void ServerApp::ClearEmpireTurnOrders(int empire_id) {
for (auto& [stored_empire_id, save_game_data] : m_turn_sequence) {
if (empire_id != ALL_EMPIRES && stored_empire_id != empire_id)
continue; // all empires, unless a single one was specified
if (save_game_data) {
// reset only orders
// left UI data and AI state intact
save_game_data->orders.reset();
}
}
}
void ServerApp::SetEmpireSaveGameData(int empire_id, std::unique_ptr<PlayerSaveGameData>&& save_game_data)
{ m_turn_sequence[empire_id] = std::move(save_game_data); }
void ServerApp::UpdatePartialOrders(int empire_id, const OrderSet& added,
const std::set<int>& deleted)
{
const auto& psgd = m_turn_sequence[empire_id];
if (psgd) {
if (psgd->orders) {
for (int del_id : deleted)
psgd->orders->erase(del_id);
for (auto& add_set : added)
psgd->orders->insert(add_set);
} else {
psgd->orders = std::make_shared<OrderSet>(added);
}
}
}
void ServerApp::RevokeEmpireTurnReadyness(int empire_id) {
if (auto empire = m_empires.GetEmpire(empire_id))
empire->SetReady(false);
}
bool ServerApp::AllOrdersReceived() {
// debug output
DebugLogger() << "ServerApp::AllOrdersReceived for turn: " << m_current_turn
<< (m_turn_expired ? " (expired)" : "");
bool all_orders_received = true;
for (const auto& [empire_id, save_data] : m_turn_sequence) {
bool empire_orders_received = true;
const auto empire = m_empires.GetEmpire(empire_id);
if (!empire) {
ErrorLogger() << " ... invalid empire id in turn sequence: "<< empire_id;
continue;
} else if (empire->Eliminated()) {
ErrorLogger() << " ... eliminated empire in turn sequence: " << empire_id;
continue;
} else if (!empire->Ready()) {
DebugLogger() << " ... not ready empire id: " << empire_id;
empire_orders_received = false;
} else if (!save_data) {
DebugLogger() << " ... no orders from empire id: " << empire_id;
empire_orders_received = false;
} else if (!save_data->orders) {
DebugLogger() << " ... no orders from empire id: " << empire_id;
empire_orders_received = false;
} else {
DebugLogger() << " ... have orders from empire id: " << empire_id;
}
if (!empire_orders_received) {
if (GetEmpireClientType(empire_id) != Networking::ClientType::CLIENT_TYPE_AI_PLAYER
&& m_turn_expired)
{
DebugLogger() << " ...... turn expired for empire id: " << empire_id;
} else {
all_orders_received = false;
}
}
}
return all_orders_received;
}
namespace {
/** Returns true if \a empire has been eliminated by the applicable
* definition of elimination. As of this writing, elimination means
* having no ships and no population on planets. */
bool EmpireEliminated(int empire_id, const ObjectMap& objects) {
// are there any populated planets? if so, not eliminated
// are there any ships? if so, not eliminated
return !objects.check_if_any<Planet>([empire_id](const auto* p)
{ return p->OwnedBy(empire_id) && p->Populated(); }) &&
!objects.check_if_any<Ship>([empire_id](const auto* s)
{ return s->OwnedBy(empire_id); });
}
void Uniquify(auto& vec) {
if (vec.empty())
return;
std::stable_sort(vec.begin(), vec.end());
const auto unique_it = std::unique(vec.begin(), vec.end());
vec.erase(unique_it, vec.end());
}
std::string to_string(const auto& stuff) {
if (stuff.empty())
return "(none)";
std::string retval;
retval.reserve(stuff.size() * 24); // guesstimate
for (const auto thing : stuff)
if constexpr (std::is_same_v<std::decay_t<decltype(thing)>, int>) {
retval.append(std::to_string(thing)).append(" ");
} else {
retval.append(thing->Name())
.append(" (id: ").append(std::to_string(thing->ID()))
.append(" owner: ").append(std::to_string(thing->Owner())).append(") ");
}
return retval;
};
constexpr auto not_null = [](const auto* p) noexcept { return !!p; };
constexpr auto to_owner = [](const auto* p) noexcept { return p->Owner(); };
// .first: IDs of all empires with fleets at system with id \a system_id
// .second.first: IDs of empires with aggressive-fleet combat-capable ships at that system
// .second.second: IDs of empires with obstructive-fleet combat-capable ships at that system
// empire IDs may include ALL_EMPIRES for non-empire-owned ships
std::pair<std::vector<int>, std::pair<std::vector<int>, std::vector<int>>> GetEmpiresWithFleetsAtSystem(
int system_id, const ScriptingContext& context)
{
const ObjectMap& objects = context.ContextObjects();
const auto* system = objects.getRaw<System>(system_id);
if (!system)
return {};
const auto fleets = objects.findRaw<Fleet>(system->FleetIDs());
const auto fleets_owner_ids = [&fleets]() -> std::vector<int> {
std::vector<int> retval;
retval.reserve(fleets.size());
range_copy(fleets | range_filter(not_null) | range_transform(to_owner),
std::back_inserter(retval));
Uniquify(retval);
return retval;
};
const auto aggressive_obstructive_combat_fleet_owner_ids =
[&fleets, &context, system_id, system]() -> std::pair<std::vector<int>, std::vector<int>>
{
if (fleets.empty())
return {};
// unarmed empire ships can trigger combat, but
// an unarmed Monster will not trigger combat
const auto owned_or_can_damage_ships = [&context](const Fleet* fleet) -> bool
{ return !fleet->Unowned() || fleet->CanDamageShips(context); };
auto combat_provoking_fleets_rng = fleets | range_filter(not_null)
| range_filter(owned_or_can_damage_ships);
static constexpr auto is_aggressive = [](const Fleet* fleet) noexcept -> bool
{ return fleet && fleet->Aggression() == FleetAggression::FLEET_AGGRESSIVE; };
static constexpr auto is_obstructive = [](const Fleet* fleet) noexcept -> bool
{ return fleet && fleet->Aggression() == FleetAggression::FLEET_OBSTRUCTIVE; };
DebugLogger(combat) << "CombatConditionsInSystem() for system (" << system_id << ") " << system->Name();
DebugLogger(combat) << " fleets here: " << [&fleets]() {
std::string retval;
for (auto& f : fleets)
retval.append(f->Name()).append(" ( id: ").append(std::to_string(f->ID()))
.append(" owner: ").append(std::to_string(f->Owner()))
.append(" aggression: ").append(to_string(f->Aggression()))
.append(" ) ");
return retval;
}();
std::vector<int> retval_aggressive;
retval_aggressive.reserve(fleets.size());
range_copy(combat_provoking_fleets_rng | range_filter(is_aggressive) | range_transform(to_owner),
std::back_inserter(retval_aggressive));
Uniquify(retval_aggressive);
std::vector<int> retval_obstructive;
retval_obstructive.reserve(fleets.size());
range_copy(combat_provoking_fleets_rng | range_filter(is_obstructive) | range_transform(to_owner),
std::back_inserter(retval_obstructive));
Uniquify(retval_obstructive);
return {retval_aggressive, retval_obstructive};
};
return {fleets_owner_ids(), aggressive_obstructive_combat_fleet_owner_ids()};
}
std::vector<int> GetEmpiresWithPlanetsAtSystem(int system_id, const ObjectMap& objects) {
const auto* system = objects.getRaw<System>(system_id);
if (!system)
return {};
static constexpr auto is_owned = [](const Planet* p) noexcept { return p && !p->Unowned(); };
static constexpr auto is_unowned_and_populated = [](const Planet* p) noexcept
{ return p && p->Unowned() && p->GetMeter(MeterType::METER_POPULATION)->Initial() > 0.0f; };
const auto id_to_planet = [&objects](int id) { return objects.getRaw<Planet>(id); };
const auto& planet_ids = system->PlanetIDs();
std::vector<int> empire_ids;
empire_ids.reserve(planet_ids.size());
auto plt_rng = planet_ids | range_transform(id_to_planet);
if (range_any_of(plt_rng, is_unowned_and_populated))
empire_ids.push_back(ALL_EMPIRES);
range_copy(plt_rng | range_filter(is_owned) | range_transform(to_owner),
std::back_inserter(empire_ids));
Uniquify(empire_ids);
return empire_ids;
}
template <typename FleetOrPlanet>
[[nodiscard]] const auto& GetIDs(const System* system) noexcept
requires(std::is_same_v<FleetOrPlanet, Fleet> || std::is_same_v<FleetOrPlanet, Planet>)
{
if constexpr (std::is_same_v<FleetOrPlanet, Fleet>)
return system->FleetIDs();
else if constexpr (std::is_same_v<FleetOrPlanet, Planet>)
return system->PlanetIDs();
}
template <typename FleetOrPlanet>
[[nodiscard]] std::vector<const FleetOrPlanet*> GetObjsVisibleToEmpireOrNeutralsAtSystem(
int empire_id, int system_id, const auto& override_vis_ids, const ScriptingContext& context)
requires(std::is_same_v<int, std::decay_t<decltype(override_vis_ids.front())>> &&
(std::is_same_v<FleetOrPlanet, Fleet> || std::is_same_v<FleetOrPlanet, Planet>))
{
if (empire_id != ALL_EMPIRES && !context.GetEmpire(empire_id))
return {}; // no such empire but id was not ALL_EMPIRES so should have been one...
const ObjectMap& objects{context.ContextObjects()};
auto* system = objects.getRaw<System>(system_id);
if (!system)
return {}; // no such system
TraceLogger(combat) << "\t** GetObjsVisibleToEmpire<" << typeid(FleetOrPlanet).name()
<< "> " << empire_id << " at system " << system->Name();
// check visibility of object by empire/neutrals
const auto is_visible_to_empire = [&context, &override_vis_ids, empire_id](const auto* obj) {
if (!obj) return
false;
const auto id = obj->ID();
if (std::any_of(override_vis_ids.begin(), override_vis_ids.end(),
[id](int oid) { return id == oid; }))
{ return true; }
if constexpr (std::is_same_v<FleetOrPlanet, Planet>) {
// skip planets that have no owner and that are unpopulated;
// these don't matter for combat conditions tests
if (obj->Unowned() && obj->GetMeter(MeterType::METER_POPULATION)->Initial() <= 0.0f)
return false;
}
return context.ContextVis(id, empire_id) >= Visibility::VIS_BASIC_VISIBILITY;
};
auto objs = objects.findRaw<FleetOrPlanet>(GetIDs<FleetOrPlanet>(system));
if (objs.empty())
return objs;
const auto part_it = std::partition(objs.begin(), objs.end(), is_visible_to_empire);
objs.erase(part_it, objs.end());
Uniquify(objs);
return objs;
}
/** Returns true iff there is an appropriate combination of objects in the
* system with id \a system_id for a combat to occur. */
[[nodiscard]] bool CombatConditionsInSystem(int system_id, const ScriptingContext& context,
const auto& empire_vis_overrides)
requires(std::is_same_v<int, std::decay_t<decltype(empire_vis_overrides.begin()->first)>> &&
std::is_same_v<int, std::decay_t<decltype(*empire_vis_overrides.begin()->second.begin())>>)
{
const auto& objects{context.ContextObjects()};
// which empires have aggressive ships here? (including monsters as empire with id ALL_EMPIRES)
// combats occur if:
// 1) empires A and B are at war, and
// 2) a) empires A and B both have fleets in a system, or
// 2) b) empire A has a fleet and empire B has a planet in a system
// 3) empire A can see the fleet or planet of empire B
// 4) empire A's fleet is set to aggressive
// 5) empire A is monsters, its fleet has at least one armed ship (unarmed monsters don't trigger combat)
//
// monster ships are treated as owned by an empire at war with all other empires (may be passive or aggressive)
// native planets are treated as owned by an empire at war with all other empires
// "can see" means has visibility due to normal detection mechanics, or possibly due to the
// seen empire B fleet being involved in a blockade of empire A's fleet(s)
// what empires have fleets here? (including monsters as id ALL_EMPIRES)
const auto [empires_with_fleets_here, aggressive_obstructive_fleets_here] =
GetEmpiresWithFleetsAtSystem(system_id, context);
const auto& [empires_with_aggressive_armed_fleets_here, empires_with_obstructive_armed_fleets_here] =
aggressive_obstructive_fleets_here;
if (empires_with_fleets_here.empty() || empires_with_aggressive_armed_fleets_here.empty())
return false;
DebugLogger(combat) << " Empires with at least one armed aggressive fleet present: "
<< to_string(empires_with_aggressive_armed_fleets_here);
DebugLogger(combat) << " Empires with at least one armed obstructive fleet present: "
<< to_string(empires_with_obstructive_armed_fleets_here);
DebugLogger(combat) << " Empires with any fleet present: "
<< to_string(empires_with_fleets_here);
// what empires have planets or fleets here?
// Unowned planets are included for ALL_EMPIRES if they have population > 0
auto empires_here = GetEmpiresWithPlanetsAtSystem(system_id, objects);
if (empires_here.empty() && empires_with_fleets_here.size() < 2) {
DebugLogger(combat) << " Only one combatant present: no combat.";
return false;
}
DebugLogger(combat) << " Empires with planets (or populated unowned) present: " << to_string(empires_here);
empires_here.insert(empires_here.end(),
empires_with_fleets_here.begin(), empires_with_fleets_here.end());
Uniquify(empires_here);
// what combinations of present empires are at war?
std::map<int, std::set<int>> empires_here_at_war; // for each empire, what other empires here is it at war with?
for (auto emp1_it = empires_here.begin(); emp1_it != empires_here.end(); ++emp1_it) {
auto emp2_it = emp1_it;
++emp2_it;
for (; emp2_it != empires_here.end(); ++emp2_it) {
if (*emp1_it == ALL_EMPIRES || *emp2_it == ALL_EMPIRES ||
context.ContextDiploStatus(*emp1_it, *emp2_it) == DiplomaticStatus::DIPLO_WAR)
{
empires_here_at_war[*emp1_it].emplace(*emp2_it);
empires_here_at_war[*emp2_it].emplace(*emp1_it);
}
}
}
if (empires_here_at_war.empty()) {
DebugLogger(combat) << " No warring combatants present: no combat.";
return false;
}
static constexpr auto not_null = [](const auto* p) noexcept -> bool { return !!p; };
const auto overrides_for_empire =
[&empire_vis_overrides](const int override_empire_id) -> const std::vector<int>& {
static CONSTEXPR_VEC const std::vector<int> EMPTY_VEC;
auto it = empire_vis_overrides.find(override_empire_id);
return it == empire_vis_overrides.end() ? EMPTY_VEC : it->second;
};
// is an empire with an aggressive fleet here able to see a planet of an
// empire it is at war with here?
for (int aggressive_empire_id : empires_with_aggressive_armed_fleets_here) {
// what empires is the aggressive empire at war with?
const auto& at_war_with_empire_ids = empires_here_at_war[aggressive_empire_id];
// what planets can the aggressive empire see?
const auto aggressive_empire_visible_planets =
GetObjsVisibleToEmpireOrNeutralsAtSystem<Planet>(
aggressive_empire_id, system_id, overrides_for_empire(aggressive_empire_id), context);
// should be all non-null pointers...
// is any planet owned by an empire at war with aggressive empire?
for (const auto* planet : aggressive_empire_visible_planets | range_filter(not_null)) {
const int visible_planet_empire_id = planet->Owner();
if (aggressive_empire_id != visible_planet_empire_id &&
at_war_with_empire_ids.contains(visible_planet_empire_id))
{
DebugLogger(combat) << " Aggressive fleet empire " << aggressive_empire_id
<< " sees at war target planet " << planet->Name()
<< " (owner: " << visible_planet_empire_id << ")";
return true; // an aggressive empire can see a planet onwned by an empire it is at war with
}
}
}
// is an empire with an aggressive fleet here able to see a fleet of an
// empire it is at war with here?
for (int aggressive_empire_id : empires_with_aggressive_armed_fleets_here) {
// what empires is the aggressive empire at war with?
const auto& at_war_with_empire_ids = empires_here_at_war[aggressive_empire_id];
if (at_war_with_empire_ids.empty())
continue;
// what fleets can the aggressive empire see?
const auto& overrides = overrides_for_empire(aggressive_empire_id);
DebugLogger(combat) << " aggressive fleet empire " << aggressive_empire_id
<< " vis overrides here: " << to_string(overrides);
const auto aggressive_empire_visible_fleets =
GetObjsVisibleToEmpireOrNeutralsAtSystem<Fleet>(aggressive_empire_id, system_id,
overrides, context);
DebugLogger(combat) << " aggressive fleet empire " << aggressive_empire_id
<< " can see fleets here: " << to_string(aggressive_empire_visible_fleets);
const auto not_self_owned = [aggressive_empire_id](const UniverseObject* obj) noexcept
{ return obj && obj->Owner() != aggressive_empire_id; };
const auto at_war_with = [&at_war_with_empire_ids](const UniverseObject* obj) noexcept
{ return at_war_with_empire_ids.contains(obj->Owner()); };
// is any fleet owned by an empire at war with aggressive empire?
for (const auto* fleet : aggressive_empire_visible_fleets
| range_filter(not_self_owned) | range_filter(at_war_with))
{
DebugLogger(combat) << " aggressive fleet empire " << aggressive_empire_id
<< " sees at-war target fleet " << fleet->Name()
<< " (" << fleet->ID() << " of empire " << fleet->Owner() << ")";
return true; // an aggressive empire can see a fleet owned by an empire it is at war with
}
}
DebugLogger(combat) << " No aggressive armed fleet empire can see a target: no combat.";
return false; // no possible conditions for combat were found
}
/** Clears and refills \a combats with CombatInfo structs for
* every system where a combat should occur this turn. */
[[nodiscard]] std::vector<CombatInfo> AssembleSystemCombatInfo(ScriptingContext& context,
const auto& empire_vis_overrides)
{
std::vector<CombatInfo> combats;
combats.reserve(context.ContextObjects().size());
// for each system, find if a combat will occur in it, and if so, assemble
// necessary information about that combat in combats
for (const auto* sys : context.ContextObjects().allRaw<System>()) {
if (CombatConditionsInSystem(sys->ID(), context, empire_vis_overrides))
combats.emplace_back(sys->ID(), context.current_turn, context.ContextUniverse(),
context.Empires(), context.diplo_statuses,
context.galaxy_setup_data, context.species,
context.supply);
}
return combats;
}
/** Back project meter values of objects in combat info, so that changes to
* meter values from combat aren't lost when resetting meters during meter
* updating after combat. */
void BackProjectSystemCombatInfoObjectMeters(std::vector<CombatInfo>& combats) {
for (CombatInfo& combat : combats) {
for (auto* object : combat.objects.allRaw())
object->BackPropagateMeters();
}
}
/** Takes contents of CombatInfo struct and puts it into the universe.
* Used to store results of combat in main universe. */
void DisseminateSystemCombatInfo(const std::vector<CombatInfo>& combats, Universe& universe,
const EmpireManager& empires)
{
// As of this writing, pointers to objects are inserted into the combat
// ObjectMap, and these pointers still refer to the main gamestate
// objects. Thus, copying the objects in the main gamestate are already
// modified by the combat, and don't need to be copied from the combat
// ObjectMap. However, the changes to object visibility during combat
// are stored separately, and do need to be copied back to the main
// gamestate. Standard visibility updating will then transfer the
// modified objects / combat results to empires' known gamestate
// ObjectMaps.
#if (!defined(__clang_major__) || (__clang_major__ >= 16)) && (BOOST_VERSION >= 107700)
const auto empire_ids = std::span<const int>(empires.EmpireIDs());
#else
const std::vector<int> empire_ids_vec(empires.EmpireIDs().begin(), empires.EmpireIDs().end());
const auto empire_ids = std::span<const int>(empire_ids_vec);
#endif
for (const CombatInfo& combat_info : combats) {
// update visibilities from combat, in case anything was revealed
// by shooting during combat
for (const auto& [vis_empire_id, empire_vis] : combat_info.empire_object_visibility) {
for (const auto& [vis_object_id, object_vis] : empire_vis | range_filter([](auto id) { return id.first >= 0; }))
universe.SetEmpireObjectVisibility(vis_empire_id, vis_object_id, object_vis); // does not lower visibility
}
// indexed by fleet id, which empire ids to inform that a fleet is destroyed
std::map<int, std::set<int>> empires_to_update_of_fleet_destruction;
// update which empires know objects are destroyed. this may
// duplicate the destroyed object knowledge that is set when the
// object is destroyed with Universe::Destroy, but there may also
// be empires in this battle that otherwise couldn't see the object
// as determined for galaxy map purposes, but which do know it has
// been destroyed from having observed it during the battle.
for (const auto& [empire_id, obj_ids] : combat_info.destroyed_object_knowers) {
for (int object_id : obj_ids) {
//DebugLogger() << "Setting knowledge of destroyed object " << object_id
// << " for empire " << empire_id;
universe.SetEmpireKnowledgeOfDestroyedObject(object_id, empire_id);
// record if empire should be informed of potential fleet
// destruction (which is checked later)
if (const auto* ship = universe.Objects().getRaw<Ship>(object_id)) {
if (ship->FleetID() != INVALID_OBJECT_ID)
empires_to_update_of_fleet_destruction[ship->FleetID()].emplace(empire_id);
}
}
}
// destroy, in main universe, objects that were destroyed in combat,
// and any associated objects that should now logically also be
// destroyed
std::set<int> all_destroyed_object_ids;
for (int destroyed_object_id : combat_info.destroyed_object_ids) {
auto dest_obj_ids = universe.RecursiveDestroy(destroyed_object_id, empire_ids);
all_destroyed_object_ids.insert(dest_obj_ids.begin(), dest_obj_ids.end());
}
// after recursive object destruction, fleets might have been
// destroyed. If so, need to also update empires knowledge of this
for (const auto& fleet_empires : empires_to_update_of_fleet_destruction) {
int fleet_id = fleet_empires.first;
if (!all_destroyed_object_ids.contains(fleet_id))
continue; // fleet wasn't destroyed
// inform empires
for (int empire_id : fleet_empires.second) {
//DebugLogger() << "Setting knowledge of destroyed object " << fleet_id
// << " for empire " << empire_id;
universe.SetEmpireKnowledgeOfDestroyedObject(fleet_id, empire_id);
}
}
// update system ownership after combat. may be necessary if the
// combat caused planets to change ownership.
if (auto system = universe.Objects().get<System>(combat_info.system_id)) {
// ensure all participants get updates on system. this ensures
// that an empire who lose all objects in the system still
// knows about a change in system ownership
for (int empire_id : combat_info.empire_ids)
universe.EmpireKnownObjects(empire_id).CopyObject(system, ALL_EMPIRES, universe);
}
}
}
/** Creates sitreps for all empires involved in a combat. */
void CreateCombatSitReps(std::vector<CombatInfo>& combats) {
CombatLogManager& log_manager = GetCombatLogManager();
for (CombatInfo& combat_info : combats) {
// add combat log entry
int log_id = log_manager.AddNewLog(CombatLog{combat_info});
// basic "combat occured" sitreps
for (int empire_id : combat_info.empire_ids) {
if (auto empire{combat_info.GetEmpire(empire_id)})
empire->AddSitRepEntry(CreateCombatSitRep(
combat_info.system_id, log_id, EnemyId(empire_id, combat_info.empire_ids),
combat_info.turn));
}
// sitreps about destroyed objects
for (auto& [knowing_empire_id, known_destroyed_object_ids] :
combat_info.destroyed_object_knowers)
{
if (auto empire{combat_info.GetEmpire(knowing_empire_id)}) {
for (int dest_obj_id : known_destroyed_object_ids) {
if (auto* obj = combat_info.objects.getRaw(dest_obj_id))
empire->AddSitRepEntry(CreateCombatDestroyedObjectSitRep(
obj, combat_info.system_id, knowing_empire_id, combat_info.turn));
}
}
}
// sitreps about damaged objects
for (int damaged_object_id : combat_info.damaged_object_ids) {
//DebugLogger() << "Checking object " << damaged_object_id << " for damaged sitrep";
// is object destroyed? If so, don't need a damage sitrep
if (combat_info.destroyed_object_ids.contains(damaged_object_id)) {
//DebugLogger() << " ... Object is destroyed and doesn't need a sitrep.";
continue;
}
const auto* obj = combat_info.objects.getRaw(damaged_object_id);
if (!obj) {
ErrorLogger() << "CreateCombatSitreps couldn't find damaged object with id: " << damaged_object_id;
continue;
}
// which empires know about this object?
for (auto& [viewing_empire_id, empire_known_objects] : combat_info.empire_object_visibility) {
// does this empire know about this object?
auto damaged_obj_it = empire_known_objects.find(damaged_object_id);
if (damaged_obj_it == empire_known_objects.end())
continue;
if (damaged_obj_it->second < Visibility::VIS_PARTIAL_VISIBILITY)
continue;
if (auto empire = combat_info.GetEmpire(viewing_empire_id))
empire->AddSitRepEntry(CreateCombatDamagedObjectSitRep(
obj, combat_info.system_id, viewing_empire_id, combat_info.turn));
}
}
}
}
/** De-nests sub-events into a single layer list of events */
std::vector<ConstCombatEventPtr> FlattenEvents(const std::vector<CombatEventPtr>& events1) {
std::vector<ConstCombatEventPtr> flat_events{events1.begin(), events1.end()}; // copy top-level input events
// copy nested sub-events of top-level events
for (const auto& event1 : events1) { // can't modify the input events pointers
auto events2{event1->SubEvents(ALL_EMPIRES)}; // makes movable pointers to event1's sub-events
for (auto& event2 : events2) {
auto events3{event2->SubEvents(ALL_EMPIRES)}; // makes movable pointers to event2's sub-events
flat_events.push_back(std::move(event2)); // can move the pointers to events2 = event1's sub-events
for (auto& event3 : events3) {
auto events4{event3->SubEvents(ALL_EMPIRES)}; // makes movable pointers to event3's sub-events
flat_events.push_back(std::move(event3)); // can move the pointers to events3 = event2's sub-events
for (auto& event4 : events4)
flat_events.push_back(std::move(event4)); // can move the pointers to events4 = event3's sub-events
}
}
}
return flat_events;
}
/** Records info in Empires about what they destroyed or had destroyed during combat. */
void UpdateEmpireCombatDestructionInfo(const std::vector<CombatInfo>& combats,
ScriptingContext& context)
{
for (const CombatInfo& combat_info : combats) {
const auto& combat_objects = combat_info.objects;
std::vector<WeaponFireEvent::ConstWeaponFireEventPtr> events_that_killed;
for (auto& event : FlattenEvents(combat_info.combat_events)) { // TODO: could do the filtering in the call function and avoid some moves later...
auto fire_event = std::dynamic_pointer_cast<const WeaponFireEvent>(std::move(event));
if (fire_event && combat_info.destroyed_object_ids.contains(fire_event->target_id)) {
TraceLogger() << "Kill event: " << fire_event->DebugString(context);
events_that_killed.push_back(std::move(fire_event));
}
}
DebugLogger() << "Combat combat_info system: " << combat_info.system_id
<< " Total Kill Events: " << events_that_killed.size();
// If a ship was attacked multiple times during a combat in which it dies, it will get
// processed multiple times here. The below set will keep it from being logged as
// multiple destroyed ships for its owner.
std::unordered_set<int> already_logged__target_ships;
std::unordered_map<int, int> empire_destroyed_ship_ids;
for (const auto& attack_event : events_that_killed) {
auto attacker = combat_objects.get(attack_event->attacker_id);
if (!attacker)
continue;
const int attacker_empire_id = attacker->Owner();
auto attacker_empire = context.GetEmpire(attacker_empire_id);
auto* target_ship = combat_objects.getRaw<Ship>(attack_event->target_id);
if (!target_ship)
continue;
const int target_empire_id = target_ship->Owner();
auto target_empire = context.GetEmpire(target_empire_id);
const auto attacker_object_type = attacker->ObjectType();
DebugLogger() << "Attacker " << to_string(attacker_object_type)
<< " " << attacker->Name() << " (id: " << attacker->ID()
<< " empire: " << std::to_string(attacker_empire_id)
<< ") attacks " << target_ship->Name()
<< " (id: " << target_ship->ID()
<< " empire: " << std::to_string(target_empire_id)
<< " species: " << target_ship->SpeciesName() << ")";
if (attacker_empire)
attacker_empire->RecordShipShotDown(*target_ship);
if (target_empire) {
if (already_logged__target_ships.contains(attack_event->target_id))
continue;
already_logged__target_ships.insert(attack_event->target_id);
target_empire->RecordShipLost(*target_ship);
}
}
}
}
/** Records info in Empires about where they invaded. */
void UpdateEmpireInvasionInfo(const std::map<int, std::map<int, double>>& planet_empire_invasion_troops,
EmpireManager& empires, const ObjectMap& objects)
{
const auto get_empire = [&empires](const auto empire_id) { return empires.GetEmpire(empire_id); };
const auto get_planet = [&objects](const auto& planet_id_troops)
{ return std::pair(objects.getRaw<Planet>(planet_id_troops.first), planet_id_troops.second); };
static constexpr auto has_species = [](const auto* planet) { return planet && planet->SpeciesName().empty(); };
for (const auto& [planet, empire_troops] : planet_empire_invasion_troops | range_transform(get_planet)) {
if (!has_species(planet)) continue; // if in loop avoids double-transform with range filter
for (const auto& invader_empire : empire_troops | range_keys | range_transform(get_empire))
if (invader_empire) invader_empire->RecordPlanetInvaded(*planet); // if in loop avoids double-transform with a range filter
}
}
/** Does colonization, with safety checks */
bool ColonizePlanet(int ship_id, int planet_id, ScriptingContext& context, const std::span<const int> empire_ids) {
auto& objects = context.ContextObjects();
auto& universe = context.ContextUniverse();
auto* ship = objects.getRaw<Ship>(ship_id);
if (!ship) {
ErrorLogger() << "ColonizePlanet couldn't get ship with id " << ship_id;
return false;
}
auto* planet = objects.getRaw<Planet>(planet_id);
if (!planet) {
ErrorLogger() << "ColonizePlanet couldn't get planet with id " << planet_id;
return false;
}
// get species to colonize with: species of ship
const auto& species_name = ship->SpeciesName();
if (species_name.empty()) {
ErrorLogger() << "ColonizePlanet ship has no species";
return false;
}
const float colonist_capacity = ship->ColonyCapacity(universe);
if (colonist_capacity > 0.0f &&
planet->EnvironmentForSpecies(context.species, species_name) < PlanetEnvironment::PE_HOSTILE)
{
ErrorLogger() << "ColonizePlanet nonzero colonist capacity and planet that ship's species can't colonize";
return false;
}
// get empire to give ownership of planet to
if (ship->Unowned()) {
ErrorLogger() << "ColonizePlanet couldn't get an empire to colonize with";
return false;
}
const int empire_id = ship->Owner();
// all checks passed. proceed with colonization.
// colonize planet by calling Planet class Colonize member function
// do this BEFORE destroying the ship, since species_name is a const reference to Ship::m_species_name
if (!planet->Colonize(empire_id, species_name, colonist_capacity, context)) {
ErrorLogger() << "ColonizePlanet: couldn't colonize planet";
return false;
}
auto* system = objects.getRaw<System>(ship->SystemID());
// destroy colonizing ship, and its fleet if now empty
if (auto* fleet = objects.getRaw<Fleet>(ship->FleetID())) {
fleet->RemoveShips({ship->ID()});
if (fleet->Empty()) {
if (system)
system->Remove(fleet->ID());
universe.Destroy(fleet->ID(), std::span(empire_ids));
}
}
if (system)
system->Remove(ship->ID());
universe.RecursiveDestroy(ship->ID(), std::span(empire_ids)); // does not count as a loss of a ship for the species / empire
return true;
}
/** Determines which ships ordered to colonize planet succeed, does
* appropriate colonization, and cleans up after colonization orders.
* Returns the IDs of planets that were colonized and IDs of ships that
* colonized. */
[[nodiscard]] std::pair<std::vector<int>, std::vector<int>> HandleColonization(ScriptingContext& context) {
Universe& universe = context.ContextUniverse();
ObjectMap& objects = context.ContextObjects();
#if (!defined(__clang_major__) || (__clang_major__ >= 16)) && (BOOST_VERSION >= 107700)
const std::span<const int> empire_ids(context.EmpireIDs());
#else
const auto& empire_ids_fs = context.EmpireIDs();
const std::vector<int> empire_ids_vec(empire_ids_fs.begin(), empire_ids_fs.end());
const std::span<const int> empire_ids(empire_ids_vec);
#endif
// collect, for each planet, what ships have been ordered to colonize it
std::map<int, std::map<int, std::set<int>>> planet_empire_colonization_ship_ids; // map from planet ID to map from empire ID to set of ship IDs
for (auto* ship : objects.allRaw<Ship>()) {
if (ship->Unowned())
continue;
const int owner_empire_id = ship->Owner();
const int ship_id = ship->ID();
if (ship_id == INVALID_OBJECT_ID)
continue;
const int colonize_planet_id = ship->OrderedColonizePlanet();
if (colonize_planet_id == INVALID_OBJECT_ID)
continue;
ship->SetColonizePlanet(INVALID_OBJECT_ID); // reset so failed colonization doesn't leave ship with hanging colonization order set
auto* planet = objects.getRaw<Planet>(colonize_planet_id);
if (!planet)
continue;
if (ship->SystemID() != planet->SystemID() || ship->SystemID() == INVALID_OBJECT_ID)
continue;
planet->ResetIsAboutToBeColonized();
planet_empire_colonization_ship_ids[colonize_planet_id][owner_empire_id].insert(ship_id);
}
std::vector<int> colonized_planet_ids;
colonized_planet_ids.reserve(planet_empire_colonization_ship_ids.size());
std::vector<int> colonizing_ship_ids;
colonizing_ship_ids.reserve(planet_empire_colonization_ship_ids.size()); // possibly an underestimate
// execute colonization except when:
// 1) an enemy empire has armed aggressive ships in the system
// 2) multiple empires try to colonize a planet on the same turn
for (const auto& [planet_id, empires_ships_colonizing] : planet_empire_colonization_ship_ids) {
const auto* const planet = objects.getRaw<Planet>(planet_id);
if (!planet) {
ErrorLogger() << "HandleColonization couldn't get planet with id " << planet_id;
continue;
}
const int system_id = planet->SystemID();
const auto* const system = objects.getRaw<System>(system_id);
if (!system) {
ErrorLogger() << "HandleColonization couldn't get system with id " << system_id;
continue;
}
// can't colonize if multiple empires attempting to do so on same turn
if (empires_ships_colonizing.size() != 1) {
// generate sitreps for every empire that tried to colonize this planet
for (const auto& [empire_id, colonizing_ships] : empires_ships_colonizing) {
const auto empire = context.GetEmpire(empire_id);
if (!empire) {
ErrorLogger() << "HandleColonization couldn't get empire with id " << empire_id;
continue;
}
const auto is_visible =
[empire_it{context.empire_object_vis.find(empire_id)},
end_it{context.empire_object_vis.end()}](const int obj_id) -> bool
{
if (empire_it == end_it)
return false;
const auto obj_it = empire_it->second.find(obj_id);
if (obj_it == empire_it->second.end())
return false;
return obj_it->second >= Visibility::VIS_BASIC_VISIBILITY;
};
for (const int ship_id : colonizing_ships) {
bool created_empire_specific_message = false;
// check other ships colonizing here...
for (const auto& [other_empire_id, other_empire_colonizing_ships] : empires_ships_colonizing) {
for (const auto other_ship_id : other_empire_colonizing_ships) {
if (!is_visible(other_ship_id))
continue;
if (other_ship_id == ship_id)
continue;
empire->AddSitRepEntry(CreatePlanetEstablishFailedVisibleOtherSitRep(
planet_id, ship_id, other_empire_id, context.current_turn));
created_empire_specific_message = true;
break;
}
}
// can this empire see another ship that attempted to colonize the same planet?
// SITREP_PLANET_ESTABLISH_FAILED_VISIBLE
//Ship %ship% failed to establish a colony or outpost on %planet% because the %empire% also attempted to establish on the same planet.
//SITREP_PLANET_ESTABLISH_FAILED_VISIBLE_LABEL
// no, just issue generic message
if (!created_empire_specific_message)
empire->AddSitRepEntry(CreatePlanetEstablishFailedSitRep(planet_id, ship_id, context.current_turn));
}
}
continue;
}
const int colonizing_empire_id = empires_ships_colonizing.begin()->first;
auto empire = context.GetEmpire(colonizing_empire_id);
const auto& empire_ships_colonizing = empires_ships_colonizing.begin()->second;
if (empire_ships_colonizing.empty())
continue;
const int colonizing_ship_id = *empire_ships_colonizing.begin();
// find which empires have obstructive armed ships in system
std::set<int> empires_with_armed_ships_in_system;
for (auto* fleet : objects.findRaw<const Fleet>(system->FleetIDs())) {
if (fleet->Obstructive() && fleet->CanDamageShips(context))
empires_with_armed_ships_in_system.insert(fleet->Owner()); // may include ALL_EMPIRES, which is fine; this makes monsters prevent colonization
}
// are any of the empires with armed ships in the system enemies of the colonzing empire?
bool colonize_blocked = false;
for (int armed_ship_empire_id : empires_with_armed_ships_in_system) {
if (armed_ship_empire_id == colonizing_empire_id)
continue;
if (armed_ship_empire_id == ALL_EMPIRES ||
context.ContextDiploStatus(colonizing_empire_id, armed_ship_empire_id) == DiplomaticStatus::DIPLO_WAR)
{
if (empire)
empire->AddSitRepEntry(CreatePlanetEstablishFailedArmedSitRep(
planet_id, colonizing_ship_id, armed_ship_empire_id, context.current_turn));
colonize_blocked = true;
break;
}
}
if (colonize_blocked)
continue;
// before actual colonization, which deletes the colony ship, store ship info for later use with sitrep generation
auto* ship = objects.getRaw<Ship>(colonizing_ship_id);
if (!ship)
ErrorLogger() << "HandleColonization couldn't get ship with id " << colonizing_ship_id;
const auto& species_name = ship ? ship->SpeciesName() : "";
float colonist_capacity = ship ? ship->ColonyCapacity(universe) : 0.0f;
// do colonization
if (!ColonizePlanet(colonizing_ship_id, planet_id, context, empire_ids))
continue; // skip sitrep if colonization failed
// record successful colonization
colonized_planet_ids.push_back(planet_id);
colonizing_ship_ids.push_back(colonizing_ship_id);
// sitrep about colonization / outposting
if (!empire) {
ErrorLogger() << "HandleColonization couldn't get empire with id " << colonizing_empire_id;
} else {
if (species_name.empty() || colonist_capacity <= 0.0f)
empire->AddSitRepEntry(CreatePlanetOutpostedSitRep(planet_id, context.current_turn));
else
empire->AddSitRepEntry(CreatePlanetColonizedSitRep(planet_id, species_name, context.current_turn));
}
}
return {colonized_planet_ids, colonizing_ship_ids};
}
/** Determines which ships ordered to invade planets, does invasion and
* ground combat resolution. Returns IDs of planets that had ground combat
* occur on them and IDs of ships that invaded a planet. */
[[nodiscard]] std::pair<std::vector<int>, std::vector<int>> HandleInvasion(ScriptingContext& context) {
std::map<int, std::map<int, double>> planet_empire_troops; // map from planet ID to map from empire ID to pair consisting of set of ship IDs and amount of troops empires have at planet
std::vector<Ship*> invade_ships;
Universe& universe = context.ContextUniverse();
ObjectMap& objects = context.ContextObjects();
EmpireManager& empires = context.Empires();
#if (!defined(__clang_major__) || (__clang_major__ >= 16)) && (BOOST_VERSION >= 107700)
const std::span<const int> empire_ids(context.EmpireIDs());
#else
const auto& empire_ids_fs = context.EmpireIDs();
const std::vector<int> empire_ids_vec(empire_ids_fs.begin(), empire_ids_fs.end());
const std::span<const int> empire_ids(empire_ids_vec);
#endif
// collect ships that are invading and the troops they carry
for (auto* ship : objects.findRaw<Ship>([&universe](const Ship& s) {
return s.SystemID() != INVALID_OBJECT_ID &&
s.OrderedInvadePlanet() != INVALID_OBJECT_ID &&
s.HasTroops(universe);
}))
{
invade_ships.push_back(ship);
auto* planet = objects.getRaw<Planet>(ship->OrderedInvadePlanet());
if (!planet)
continue;
planet->ResetIsAboutToBeInvaded();
if (ship->SystemID() != planet->SystemID())
continue;
if (ship->SystemID() == INVALID_OBJECT_ID)
continue;
// how many troops are invading?
planet_empire_troops[ship->OrderedInvadePlanet()][ship->Owner()] += ship->TroopCapacity(universe);
DebugLogger() << "HandleInvasion has accounted for "<< ship->TroopCapacity(universe)
<< " troops to invade " << planet->Name()
<< " and is destroying ship " << ship->ID()
<< " named " << ship->Name();
}
std::vector<int> invading_ship_ids;
invading_ship_ids.reserve(invade_ships.size());
std::transform(invade_ships.begin(), invade_ships.end(), std::back_inserter(invading_ship_ids),
[](const auto* ship) { return ship->ID(); });
// delete ships that invaded something
for (auto* ship : invade_ships) {
auto* system = objects.getRaw<System>(ship->SystemID());
// destroy invading ships and their fleets if now empty
if (auto* fleet = objects.getRaw<Fleet>(ship->FleetID())) {
fleet->RemoveShips({ship->ID()});
if (fleet->Empty()) {
if (system)
system->Remove(fleet->ID());
universe.Destroy(fleet->ID(), empire_ids);
}
}
if (system)
system->Remove(ship->ID());
universe.RecursiveDestroy(ship->ID(), empire_ids); // does not count as ship loss for empire/species
}
// store invasion info in empires
UpdateEmpireInvasionInfo(planet_empire_troops, empires, objects);
// check each planet invading or other troops, such as due to empire troops,
// native troops, or rebel troops
for (const auto* planet : objects.allRaw<Planet>()) {
planet_empire_troops[planet->ID()].merge(planet->EmpireGroundCombatForces());
//auto empire_forces = planet->EmpireGroundCombatForces();
//if (!empire_forces.empty())
// planet_empire_troops[planet->ID()].insert(empire_forces.begin(), empire_forces.end());
}
std::vector<int> ground_combat_planet_ids;
ground_combat_planet_ids.reserve(planet_empire_troops.size());
// process each planet's ground combats
for (auto& [planet_id, empires_troops] : planet_empire_troops) {
if (empires_troops.empty())
continue;
std::set<int> all_involved_empires;
for (const auto empire_id : empires_troops | range_keys) {
if (empire_id != ALL_EMPIRES)
all_involved_empires.insert(empire_id);
}
auto planet = objects.get<Planet>(planet_id);
if (!planet) {
ErrorLogger() << "Ground combat couldn't get planet with id " << planet_id;
continue;
}
const int planet_initial_owner_id = planet->Owner();
if (planet_initial_owner_id != ALL_EMPIRES)
all_involved_empires.insert(planet_initial_owner_id);
{
// find which, if any, empire has invaded with the most troops, excluding
// the current owner, no empire / rebels, and any ally of the current owner
const int biggest_new_invader =
[&context, planet_initial_owner_id](const auto& empires_troops)
{
int biggest_troop_count = 0;
int biggest_empire_id = ALL_EMPIRES;
for (auto& [empire_id, troop_count] : empires_troops) {
if (empire_id == planet_initial_owner_id)
continue; // self defense of a planet with troops doesn't erase another empire having previous invaded
if (empire_id == ALL_EMPIRES)
continue; // rebels reclaiming a planet doesn't erase the last empire that invaded it
const auto ds = context.ContextDiploStatus(planet_initial_owner_id, empire_id);
if (ds == DiplomaticStatus::DIPLO_PEACE)
continue; // not sure how this would happen, but ignore...
if (ds == DiplomaticStatus::DIPLO_ALLIED)
continue; // if assisting in defense, is not an invasion
if (troop_count > biggest_troop_count) {
biggest_troop_count = troop_count;
biggest_empire_id = empire_id;
}
}
return biggest_empire_id;
}(empires_troops);
if (biggest_new_invader != ALL_EMPIRES)
planet->SetLastInvadedByEmpire(biggest_new_invader);
}
if (empires_troops.size() == 1) {
const int empire_with_troops_id = empires_troops.begin()->first;
if (planet->Unowned() && empire_with_troops_id == ALL_EMPIRES)
continue; // if troops are neutral and planet is unowned, not a combat.
if (planet->OwnedBy(empire_with_troops_id))
continue; // if troops all belong to planet owner, not a combat.
} else {
DebugLogger() << "Ground combat troops on " << planet->Name() << " :";
for (const auto& [empire_with_troops_id, empire_troop_level] : empires_troops)
DebugLogger() << " ... empire: " << empire_with_troops_id << " : " << empire_troop_level;
Planet::ResolveGroundCombat(empires_troops, empires.GetDiplomaticStatuses());
ground_combat_planet_ids.push_back(planet_id);
}
for (int empire_id : all_involved_empires) {
if (auto empire = empires.GetEmpire(empire_id))
empire->AddSitRepEntry(CreateGroundCombatSitRep(
planet_id, EnemyId(empire_id, all_involved_empires), context.current_turn));
}
// process post-combat cleanup...
if (empires_troops.size() == 1) {
int victor_id = empires_troops.begin()->first;
// single empire was victorious. conquer planet if appropriate...
// if planet is unowned and victor is an empire, or if planet is
// owned by an empire that is not the victor, conquer it
if ((victor_id != ALL_EMPIRES) && (planet->Unowned() || !planet->OwnedBy(victor_id))) {
planet->Conquer(victor_id, context);
// create planet conquered sitrep for all involved empires
for (int empire_id : all_involved_empires) {
if (auto empire = empires.GetEmpire(empire_id))
empire->AddSitRepEntry(CreatePlanetCapturedSitRep(planet_id, victor_id, context.current_turn));
}
DebugLogger() << "Empire conquers planet";
for (auto& [empire_with_post_battle_troops_id, troop_count] : empires_troops)
DebugLogger() << " empire: " << empire_with_post_battle_troops_id << ": " << troop_count;
} else if (!planet->Unowned() && victor_id == ALL_EMPIRES) {
int previous_owner_id = planet->Owner();
planet->Conquer(ALL_EMPIRES, context);
DebugLogger() << "Independents conquer planet";
for (const auto& empire_troops : empires_troops)
DebugLogger() << " empire: " << empire_troops.first << ": " << empire_troops.second;
for (int empire_id : all_involved_empires) {
if (auto empire = empires.GetEmpire(empire_id))
empire->AddSitRepEntry(CreatePlanetRebelledSitRep(planet_id, previous_owner_id, context.current_turn));
}
} else {
// defender held the planet
DebugLogger() << "Defender holds planet";
for (auto& [empire_with_post_battle_troops_id, troop_count] : empires_troops)
DebugLogger() << " empire: " << empire_with_post_battle_troops_id << ": " << troop_count;
}
// regardless of whether battle resulted in conquering, it did
// likely affect the troop numbers on the planet
if (Meter* meter = planet->GetMeter(MeterType::METER_TROOPS))
meter->SetCurrent(empires_troops.begin()->second); // new troops on planet is remainder after battle
} else {
// no troops left?
if (Meter* meter = planet->GetMeter(MeterType::METER_TROOPS))
meter->SetCurrent(0.0f);
if (Meter* meter = planet->GetMeter(MeterType::METER_REBEL_TROOPS))
meter->SetCurrent(0.0f);
}
planet->BackPropagateMeters();
// knowledge update to ensure previous owner of planet knows who owns it now?
if (planet_initial_owner_id != ALL_EMPIRES && planet_initial_owner_id != planet->Owner()) {
// get empire's knowledge of object
ObjectMap& empire_latest_known_objects = universe.EmpireKnownObjects(planet_initial_owner_id);
empire_latest_known_objects.CopyObject(std::move(planet), planet_initial_owner_id, universe);
}
}
return {ground_combat_planet_ids, invading_ship_ids};
}
/** Determines which fleets or planets ordered given to other empires,
* and sets their new ownership. Returns the IDs of anything gifted. */
std::vector<int> HandleGifting(EmpireManager& empires, ObjectMap& objects, int current_turn,
const std::span<const int> invaded_planet_ids,
const std::span<const int> invading_ship_ids,
const std::span<const int> colonizing_ship_ids,
const std::span<const int> annexed_planets_ids) // TODO: disallow annexed planets
{
// determine system IDs where empires can receive gifts
std::map<int, std::set<int>> empire_receiving_locations;
auto owned_planet = [](const Planet& p) { return !p.Unowned(); };
for (const auto* planet : objects.findRaw<const Planet>(owned_planet))
empire_receiving_locations[planet->SystemID()].insert(planet->Owner());
auto owned_ship_in_system = [](const Ship& s) { return !s.Unowned() && s.SystemID() != INVALID_OBJECT_ID; };
for (const auto* ship : objects.findRaw<const Ship>(owned_ship_in_system))
empire_receiving_locations[ship->SystemID()].insert(ship->Owner());
// collect fleets ordered to be given and their ships
std::map<int, std::vector<Fleet*>> empire_gifted_fleets; // indexed by recipient empire id
std::map<int, std::vector<Ship*>> empire_gifted_ships;
auto owned_given_stationary_fleet = [&empire_receiving_locations](const Fleet& f) {
if (f.Unowned() ||
f.OrderedGivenToEmpire() == ALL_EMPIRES ||
f.OwnedBy(f.OrderedGivenToEmpire()) ||
!f.TravelRoute().empty() ||
f.SystemID() == INVALID_OBJECT_ID)
{ return false; }
auto it = empire_receiving_locations.find(f.SystemID());
return it != empire_receiving_locations.end() &&
it->second.contains(f.OrderedGivenToEmpire());
};
auto not_invading_not_colonizing_ship = [invading_ship_ids, colonizing_ship_ids](const Ship& s) {
return std::none_of(invading_ship_ids.begin(), invading_ship_ids.end(),
[sid{s.ID()}] (const auto iid) { return iid == sid; }) &&
std::none_of(colonizing_ship_ids.begin(), colonizing_ship_ids.end(),
[sid{s.ID()}] (const auto cid) { return cid == sid; });
};
for (auto* fleet : objects.findRaw<Fleet>(owned_given_stationary_fleet)) {
const auto recipient_empire_id = fleet->OrderedGivenToEmpire();
empire_gifted_fleets[recipient_empire_id].push_back(fleet);
for (auto* ship : objects.findRaw<Ship>(fleet->ShipIDs())) {
if (ship && not_invading_not_colonizing_ship(*ship))
empire_gifted_ships[recipient_empire_id].push_back(ship);
}
}
for (auto* fleet : objects.allRaw<Fleet>())
fleet->ClearGiveToEmpire(); // in case things fail, to avoid potential inconsistent state
// collect planets ordered to be given but that aren't being invaded,
// and buildings on them
std::map<int, std::vector<Planet*>> empire_gifted_planets; // indexed by recipient empire id
std::map<int, std::vector<Building*>> empire_gifted_buildings;
auto owned_given_not_invaded_planet =
[invaded_planet_ids, &empire_receiving_locations](const Planet& p)
{
if (p.Unowned() ||
p.OrderedGivenToEmpire() == ALL_EMPIRES ||
p.OwnedBy(p.OrderedGivenToEmpire()) ||
p.SystemID() == INVALID_OBJECT_ID ||
std::any_of(invaded_planet_ids.begin(), invaded_planet_ids.end(),
[pid{p.ID()}](const auto iid) { return iid == pid; }))
{ return false; }
auto it = empire_receiving_locations.find(p.SystemID());
return it != empire_receiving_locations.end() &&
it->second.contains(p.OrderedGivenToEmpire());
};
for (auto* planet : objects.findRaw<Planet>(owned_given_not_invaded_planet)) {
const auto recipient_empire_id = planet->OrderedGivenToEmpire();
planet->ClearGiveToEmpire(); // in case things fail, to avoid potential inconsistent state
empire_gifted_planets[recipient_empire_id].push_back(planet);
for (auto* building : objects.findRaw<Building>(planet->BuildingIDs())) {
if (building)
empire_gifted_buildings[recipient_empire_id].push_back(building);
}
}
// storage for list of all gifted objects
std::vector<int> gifted_object_ids;
auto do_giving = [&gifted_object_ids, &empires, current_turn](auto& recipients_objs) {
for (auto& [recipient_empire_id, objs] : recipients_objs) {
for (auto* gifted_obj : objs) {
[[maybe_unused]] const auto initial_owner_empire_id = gifted_obj->Owner();
const auto gifted_obj_id = gifted_obj->ID();
gifted_object_ids.push_back(gifted_obj_id);
gifted_obj->SetOwner(recipient_empire_id);
using ObjsT = std::decay_t<decltype(*gifted_obj)>;
static_assert(!std::is_same_v<ObjsT, UniverseObject>);
static_assert(std::is_same_v<ObjsT, Planet> || std::is_same_v<ObjsT, Building> ||
std::is_same_v<ObjsT, Fleet> || std::is_same_v<ObjsT, Ship>);
if constexpr (std::is_same_v<ObjsT, Planet>) {
if (auto empire = empires.GetEmpire(recipient_empire_id))
empire->AddSitRepEntry(CreatePlanetGiftedSitRep(gifted_obj_id, initial_owner_empire_id,
current_turn));
} else if constexpr (std::is_same_v<ObjsT, Fleet>) {
if (auto empire = empires.GetEmpire(recipient_empire_id))
empire->AddSitRepEntry(CreateFleetGiftedSitRep(gifted_obj_id, initial_owner_empire_id,
current_turn));
} else if constexpr (std::is_same_v<ObjsT, Ship>) {
gifted_obj->SetOrderedScrapped(false);
gifted_obj->ClearColonizePlanet();
gifted_obj->ClearInvadePlanet();
gifted_obj->ClearBombardPlanet();
} else if constexpr (std::is_same_v<ObjsT, Building>) {
gifted_obj->SetOrderedScrapped(false);
}
Empire::ConquerProductionQueueItemsAtLocation(gifted_obj_id, recipient_empire_id, empires);
}
}
};
do_giving(empire_gifted_fleets);
do_giving(empire_gifted_ships);
do_giving(empire_gifted_planets);
do_giving(empire_gifted_buildings);
return gifted_object_ids;
}
/** Destroys suitable objects that have been ordered scrapped.*/
void HandleScrapping(Universe& universe, EmpireManager& empires,
const std::span<const int> invading_ship_ids,
const std::span<const int> invaded_planet_ids,
const std::span<const int> colonizing_ship_ids,
const std::span<const int> colonized_planet_ids,
const std::span<const int> gifted_ids,
const std::span<const int> annexed_planet_ids) // TODO: disallow scrapping during annexation
{
ObjectMap& objects{universe.Objects()};
#if (!defined(__clang_major__) || (__clang_major__ >= 16)) && (BOOST_VERSION >= 107700)
const std::span<const int> empire_ids(empires.EmpireIDs());
#else
const auto& empire_ids_fs = empires.EmpireIDs();
const std::vector<int> empire_ids_vec(empire_ids_fs.begin(), empire_ids_fs.end());
const std::span<const int> empire_ids(empire_ids_vec);
#endif
// only scap ships that aren't being gifted and that aren't invading or colonizing this turn
const auto scrapped_ships = objects.findRaw<Ship>(
[invading_ship_ids, colonizing_ship_ids, gifted_ids](const Ship* s) {
return s->OrderedScrapped() &&
std::none_of(gifted_ids.begin(), gifted_ids.end(),
[sid{s->ID()}](const auto gid) { return gid == sid; }) &&
std::none_of(invading_ship_ids.begin(), invading_ship_ids.end(),
[sid{s->ID()}](const auto iid) { return iid == sid; }) &&
std::none_of(colonizing_ship_ids.begin(), colonizing_ship_ids.end(),
[sid{s->ID()}](const auto cid) { return cid == sid; });
});
for (const auto* ship : scrapped_ships) {
DebugLogger() << "... ship: " << ship->ID() << " ordered scrapped";
const auto ship_id = ship->ID();
const auto fleet_id = ship->FleetID();
const auto sys_id = ship->SystemID();
auto* system = objects.getRaw<System>(sys_id);
if (system)
system->Remove(ship_id);
if (auto* fleet = objects.getRaw<Fleet>(fleet_id)) {
fleet->RemoveShips({ship_id});
if (fleet->Empty()) {
if (system)
system->Remove(fleet_id);
universe.Destroy(fleet_id, empire_ids);
}
}
// record scrapping in empire stats
auto scrapping_empire = empires.GetEmpire(ship->Owner());
if (scrapping_empire)
scrapping_empire->RecordShipScrapped(*ship);
//scrapped_object_ids.push_back(ship->ID());
universe.Destroy(ship_id, empire_ids);
}
auto scrapped_buildings = objects.findRaw<Building>(
[invaded_planet_ids, gifted_ids](const Building* b) {
return b->OrderedScrapped() &&
std::none_of(gifted_ids.begin(), gifted_ids.end(),
[bid{b->ID()}](const auto gid) { return gid == bid; }) &&
std::none_of(invaded_planet_ids.begin(), invaded_planet_ids.end(),
[pid{b->PlanetID()}](const auto iid) { return iid == pid; });
});
for (auto* building : scrapped_buildings) {
if (auto* planet = objects.getRaw<Planet>(building->PlanetID()))
planet->RemoveBuilding(building->ID());
if (auto* system = objects.getRaw<System>(building->SystemID()))
system->Remove(building->ID());
// record scrapping in empire stats
auto scrapping_empire = empires.GetEmpire(building->Owner());
if (scrapping_empire)
scrapping_empire->RecordBuildingScrapped(*building);
//scrapped_object_ids.push_back(building->ID());
universe.Destroy(building->ID(), empire_ids);
}
}
/** Determines which planets are ordered annexed by empires, and sets
* their new ownership, last turn annexed, and last annexer empire id.
* Returns the IDs of anything so marked as annexed.
* Note: Consumption of IP is done later, when updating the influence
* queue, which deducts IP for appropriately marked planets. */
std::vector<int> HandleAnnexation(ScriptingContext& context,
const std::span<int> invaded_planet_ids,
const auto& empire_planet_annexation_costs)
#if !defined(FREEORION_ANDROID)
requires requires {{*empire_planet_annexation_costs.begin()->second.begin()}
-> std::convertible_to<std::pair<int, double>>; }
#endif
{
std::vector<int> annexed_object_ids;
if (empire_planet_annexation_costs.empty())
return annexed_object_ids;
EmpireManager& empires = context.Empires();
ObjectMap& objects = context.ContextObjects();
const int current_turn = context.current_turn;
const auto annexed_not_invaded_planet = [invaded_planet_ids](const Planet& p) {
return p.IsAboutToBeAnnexed() &&
std::none_of(invaded_planet_ids.begin(), invaded_planet_ids.end(),
[pid{p.ID()}](const auto iid) { return iid == pid; });
};
// collect planets being annexed from passed-in IDs, that aren't also being invaded
boost::container::flat_map<int, std::vector<std::pair<Planet*, double>>> empire_annexing_planets_and_costs; // indexed by recipient / annexer empire id
empire_annexing_planets_and_costs.reserve(empire_planet_annexation_costs.size());
for (const auto& [by_empire_id, planet_id_costs] : empire_planet_annexation_costs) {
auto& planets_costs = empire_annexing_planets_and_costs[by_empire_id];
planets_costs.reserve(planet_id_costs.size());
for (const auto& [planet_id, cost] : planet_id_costs) {
Planet* planet = objects.getRaw<Planet>(planet_id);
if (planet && annexed_not_invaded_planet(*planet)) // skip if being invaded
planets_costs.emplace_back(planet, std::max(0.0, cost)); // prevent negative cost annexations
}
// sort by annexation cost, so most expensive annexations are resolved first.
static constexpr auto expensive_first = [](const auto& lhs, const auto& rhs) { return lhs.second > rhs.second; };
std::stable_sort(planets_costs.begin(), planets_costs.end(), expensive_first);
}
for (Planet* planet : objects.allRaw<Planet>())
planet->ResetBeingAnnxed(); // doing now, in case things fail, to avoid potential inconsistent state
// reserve space to store ids of everything that is annexed after considering IP limits
boost::container::flat_map<int, std::vector<int>> empire_annexed_object_ids;
empire_annexed_object_ids.reserve(empires.NumEmpires());
{
std::size_t total_annexed_count = 0u;
for (const auto& [empire_id, planets_costs] : empire_annexing_planets_and_costs) {
empire_annexed_object_ids[empire_id].reserve(planets_costs.size()); // planets and buildings, so this is probably an under-estimate
total_annexed_count += planets_costs.size();
}
annexed_object_ids.reserve(total_annexed_count);
}
// determine available IP for annexation per empire: stockpile minus costs for policy adoption
auto empire_available_ip = [&empires, &context]() {
boost::container::flat_map<int, double> empire_available_ip;
empire_available_ip.reserve(empires.NumEmpires());
for (const auto& [id, empire] : empires) {
const auto stockpile = empire ? empire->ResourceStockpile(ResourceType::RE_INFLUENCE) : 0.0;
const auto adoption_cost = empire ? empire->ThisTurnAdoptedPoliciesCost(context) : 0.0;
empire_available_ip.emplace(id, stockpile - adoption_cost);
}
return empire_available_ip;
}();
// check that there is enough IP for all annexations for each empire,
// reducing available for each that is kept.
// skipped annexations mark the planet pointer null
for (auto& [annexer_empire_id, planets_costs] : empire_annexing_planets_and_costs) {
auto available_ip = empire_available_ip[annexer_empire_id];
for (auto& [planet, cost] : planets_costs) {
const auto effective_cost = std::max(0.0, cost); // no refunds
if (available_ip >= effective_cost)
available_ip -= effective_cost;
else
planet = nullptr;
}
}
// Note: deduction of used IP done when updating the influence queue
// do annexations
for (auto& [annexer_empire_id, planets_costs] : empire_annexing_planets_and_costs) {
const auto annexer_empire = empires.GetEmpire(annexer_empire_id); // could be null?
const auto emit_annexation_sitrep =
[&annexer_empire, current_turn, annexer_empire_id{annexer_empire_id}, &empires]
(int initial_owner_id, int obj_id, UniverseObjectType obj_type)
{
if (obj_type == UniverseObjectType::OBJ_PLANET) {
if (annexer_empire)
annexer_empire->AddSitRepEntry(CreatePlanetAnnexedSitRep(
obj_id, initial_owner_id, annexer_empire_id, current_turn));
if (initial_owner_id != ALL_EMPIRES)
if (auto initial_owner_empire = empires.GetEmpire(initial_owner_id))
initial_owner_empire->AddSitRepEntry(CreatePlanetAnnexedSitRep(
obj_id, initial_owner_id, annexer_empire_id, current_turn));
}
};
for (auto& [planet, cost] : planets_costs) {
if (!planet)
continue;
const auto planet_id = planet->ID();
const auto initial_owner_id = planet->Owner();
planet->SetOwner(annexer_empire_id);
annexed_object_ids.push_back(planet_id);
planet->SetLastTurnAnnexed(current_turn);
planet->SetLastAnnexedByEmpire(annexer_empire_id);
Empire::ConquerProductionQueueItemsAtLocation(planet_id, annexer_empire_id, empires);
emit_annexation_sitrep(initial_owner_id, planet_id, UniverseObjectType::OBJ_PLANET);
auto buildings = objects.findRaw<Building>(planet->BuildingIDs());
for (auto* building : buildings) {
if (!building)
continue;
const auto building_id = building->ID();
const auto initial_owner_id = building->Owner();
building->SetOwner(annexer_empire_id);
annexed_object_ids.push_back(building_id);
emit_annexation_sitrep(initial_owner_id, building_id, UniverseObjectType::OBJ_BUILDING);
}
}
}
return annexed_object_ids;
}
/** Removes bombardment state info from objects. Actual effects of
* bombardment are handled during */
void CleanUpBombardmentStateInfo(ObjectMap& objects) {
for (auto* ship : objects.allRaw<Ship>())
ship->ClearBombardPlanet();
for (auto* planet : objects.allRaw<Planet>()) {
if (planet->IsAboutToBeBombarded()) {
//DebugLogger() << "CleanUpBombardmentStateInfo: " << planet->Name() << " was about to be bombarded";
planet->ResetIsAboutToBeBombarded();
}
}
}
/** Causes ResourceCenters (Planets) to update their focus records */
void UpdateResourceCenterFocusHistoryInfo(ObjectMap& objects) {
for (auto* planet : objects.allRaw<Planet>())
planet->UpdateFocusHistory();
}
/** Check validity of adopted policies, overwrite initial adopted
* policies with those currently adopted, update adopted turns counters. */
void UpdateEmpirePolicies(EmpireManager& empires, int current_turn,
bool update_cumulative_adoption_time = false)
{
for (auto& empire : empires | range_values)
empire->UpdatePolicies(update_cumulative_adoption_time, current_turn);
}
/** Deletes empty fleets. */
void CleanEmptyFleets(ScriptingContext& context) {
Universe& universe{context.ContextUniverse()};
ObjectMap& objects{context.ContextObjects()};
#if (!defined(__clang_major__) || (__clang_major__ >= 16)) && (BOOST_VERSION >= 107700)
const std::span<const int> empire_ids(context.EmpireIDs());
#else
const auto& empire_ids_fs = context.EmpireIDs();
const std::vector<int> empire_ids_vec(empire_ids_fs.begin(), empire_ids_fs.end());
const std::span<const int> empire_ids(empire_ids_vec);
#endif
// need to remove empty fleets from systems and call RecursiveDestroy for each fleet
static constexpr auto is_empty_fleet = [](const Fleet* f) { return f->Empty(); };
using fleet_system = std::pair<const Fleet*, System*>;
const auto to_fleet_and_system = [&objects](const Fleet* f) -> fleet_system
{ return fleet_system(f, objects.getRaw<System>(f->SystemID())); }; // system may be nullptr
static constexpr auto in_system = [](fleet_system fs) -> bool { return fs.second; };
static constexpr auto to_id = [](const auto* o) { return o->ID(); };
auto empty_fleets = objects.allRaw<const Fleet>() | range_filter(is_empty_fleet);
auto empty_fleet_ids = empty_fleets | range_transform(to_id);
auto empty_fleets_and_systems = empty_fleets | range_transform(to_fleet_and_system)
| range_filter(in_system);
for (auto [fleet, system] : empty_fleets_and_systems)
system->Remove(fleet->ID());
for (const auto fleet_id : empty_fleet_ids)
universe.RecursiveDestroy(fleet_id, empire_ids);
}
/** .first: Map from empire ID to IDs of fleets that are being revealed
* to that empire, due to those fleets blockading that empire's fleet(s)
* .second: Vector of ((fleet ID, owner empire ID), fleet IDs), where the fleet IDs are
* blockading the first fleet ID, and first fleet ID is owned by owner empire ID. */
auto GetBlockadingObjectsForEmpires(const auto& move_pathes, const ScriptingContext& context) {
// is there a blockade within (at end?) of this turn's movement?
using fleet_and_path = std::pair<const Fleet*, std::vector<MovePathNode>>;
using two_ids_and_ids = std::pair<std::pair<int, int>, std::vector<int>>;
static constexpr auto not_empty_path = [](const fleet_and_path& fleet_and_move_path) noexcept
{ return !fleet_and_move_path.second.empty(); };
const auto path_to_ids_and_blockading_fleets = [&context](fleet_and_path mp) -> two_ids_and_ids {
const auto& [fleet, nodes] = mp;
if (!fleet || nodes.empty())
return {std::pair{INVALID_OBJECT_ID, ALL_EMPIRES}, {}}; // no valid fleet??? (unexpected) or no path
std::pair<int, int> fleet_id_and_owner{fleet->ID(), fleet->Owner()};
static constexpr auto is_turn_end = [](const MovePathNode& n) { return n.turn_end || n.blockaded_here; };
// get first turn end node. blockades also count as a turn end for this purpose
auto turn_end_node_it = std::find_if(nodes.begin(), nodes.end(), is_turn_end);
if (turn_end_node_it == nodes.end())
return {fleet_id_and_owner, {}}; // didn't find a turn end node??? (unexpected)
// is it at a system?
const auto turn_end_sys_id = turn_end_node_it->object_id;
if (turn_end_sys_id == INVALID_OBJECT_ID)
return {fleet_id_and_owner, {}}; // turn end node is not at a system, so can't have a blockade there
// is there more path after the next turn end system?
const auto following_node_it = std::next(turn_end_node_it);
if (following_node_it == nodes.end())
return {fleet_id_and_owner, {}}; // end turn node is also end of path, so no blockade
// if the next node is not a system, then it should be a lane that starts at the turn end system
if (following_node_it->object_id == INVALID_OBJECT_ID) {
const auto should_be_turn_end_system_id = following_node_it->lane_start_id;
if (should_be_turn_end_system_id != turn_end_sys_id)
return {fleet_id_and_owner, {}}; // unexpected...
}
// what system is after the turn end system in the path?
const auto next_sys_id = following_node_it->object_id != INVALID_OBJECT_ID ?
following_node_it->object_id : following_node_it->lane_end_id;
if (next_sys_id == INVALID_OBJECT_ID)
return {fleet_id_and_owner, {}}; // following node exists but isn't headed anywhere??? (unexpected)
// is there a blockading fleet at the turn end system for this fleet going to the next system?
auto blockading_fleets = fleet->BlockadingFleetsAtSystem(turn_end_sys_id, next_sys_id, context);
return {fleet_id_and_owner, blockading_fleets};
};
std::vector<two_ids_and_ids> fleet_owner_and_blockading_fleets;
fleet_owner_and_blockading_fleets.reserve(context.ContextObjects().size<Fleet>());
range_copy(move_pathes | range_filter(not_empty_path)
| range_transform(path_to_ids_and_blockading_fleets),
std::back_inserter(fleet_owner_and_blockading_fleets));
DebugLogger(combat) << "total fleet count: " << context.ContextObjects().size<Fleet>();
DebugLogger(combat) << "count of not empty fleet move pathes: " << fleet_owner_and_blockading_fleets.size();
static constexpr auto not_null_and_has_blockaders = [](const two_ids_and_ids& fbids)
{ return fbids.first.first != INVALID_OBJECT_ID && !fbids.second.empty(); };
auto blockades_rng = fleet_owner_and_blockading_fleets | range_filter(not_null_and_has_blockaders);
// sort list of fleets are blockading into a per-empire list of
// fleets that are to be revealed to each empire
std::map<int, std::vector<int>> empires_blockaded_by_fleets;
for (const auto& [fleet_and_owner, blockader_ids] : blockades_rng) {
DebugLogger(combat) << "fleet: " << fleet_and_owner.first << " owner: " << fleet_and_owner.second
<< " blockaders: " << to_string(blockader_ids);
auto& revealed_ids = empires_blockaded_by_fleets[fleet_and_owner.second];
revealed_ids.insert(revealed_ids.end(), blockader_ids.begin(), blockader_ids.end());
}
// sort/unique, so that multiple fleets, owned by the same empire, and
// at the same system are only counted once per blockading fleet
for (auto& [empire_id, fleet_ids] : empires_blockaded_by_fleets) {
Uniquify(fleet_ids);
DebugLogger(combat) << "empire id " << empire_id
<< " is blockaded by fleets: " << to_string(fleet_ids);
}
return std::pair{empires_blockaded_by_fleets, fleet_owner_and_blockading_fleets};
}
// for each input fleet that is obstructive, pick one or no ship in that fleet to
// reveal to an empire that the fleet is blockading. pick no ships if there is already
// an armed visible ship in the fleet. otherwise, pick the lowest-stealth armed ship
// in the fleet. do not pick any ships in aggressive fleets, as these will reveal
// themselves in combat, rather than by this mechanism.
auto GetShipsToRevealForEmpiresBlockadedByFleets(
const std::map<int, std::vector<int>>& empires_blockaded_by_fleets, const ScriptingContext& context)
{
std::map<int, std::vector<int>> retval;
for (const auto& [empire_id, fleets] : empires_blockaded_by_fleets)
retval[empire_id].reserve(fleets.size()); // reveal at most one ship per fleet
// TODO: could consolidate fleets at the same system and only reveal one ship per system?
static constexpr auto not_null = [](const Fleet* f) noexcept -> bool { return !!f; };
static constexpr auto is_obstructive = [](const Fleet* f) noexcept -> bool
{ return f->Aggression() == FleetAggression::FLEET_OBSTRUCTIVE; };
const auto id_to_ship = [&context](int ship_id) -> const Ship*
{ return context.ContextObjects().getRaw<Ship>(ship_id); };
static constexpr auto ship_to_ship_and_stealth = [](const Ship* ship) noexcept
{ return std::pair{ship, ship ? ship->GetMeter(MeterType::METER_STEALTH)->Current() : 0.0f}; };
const auto is_potential_blockader = [&context](const Ship* ship) -> bool
{ return ship && ship->CanDamageShips(context); };
const auto& objs{context.ContextObjects()};
for (const auto& [empire_id, fleet_ids] : empires_blockaded_by_fleets) {
const auto fleets = objs.findRaw<Fleet>(fleet_ids);
const auto id_is_visible = [empire_id{empire_id}, &context](int id)
{ return context.ContextVis(id, empire_id) >= Visibility::VIS_BASIC_VISIBILITY; };
const auto id_not_visible = [id_is_visible](int id)
{ return !id_is_visible(id); };
// intentionally excluding aggressive fleets
for (const Fleet* fleet : fleets | range_filter(not_null) | range_filter(is_obstructive)) {
DebugLogger(combat) << " finding ship to reveal for obstructive fleet: "
<< fleet->Name() << " (" << fleet->ID()
<< ") ship ids: " << to_string(fleet->ShipIDs());
// if there is already a blockade-capable visible ship, don't need to reveal anything
auto vis_ships_rng = fleet->ShipIDs()
| range_filter(id_is_visible)
| range_transform(id_to_ship);
if (range_any_of(vis_ships_rng, is_potential_blockader))
continue; // don't need to reveal anything. blockaded empire can see a blockade-capable ship
// find blockade-capable not-visible ship that has the lowest stealth
auto not_vis_blockade_capable_ships_and_stealths_rng = fleet->ShipIDs()
| range_filter(id_not_visible)
| range_transform(id_to_ship)
| range_filter(is_potential_blockader)
| range_transform(ship_to_ship_and_stealth);
static constexpr auto second_less = [](const auto& l, const auto& r) noexcept -> bool
{ return l.second < r.second; };
const auto it = range_min_element(not_vis_blockade_capable_ships_and_stealths_rng, second_less);
static_assert(std::is_same_v<std::decay_t<decltype(*it)>, std::pair<const Ship*, float>>);
if (it != not_vis_blockade_capable_ships_and_stealths_rng.end()) {
const auto& [ship, stealth] = *it;
static_assert(std::is_same_v<std::decay_t<decltype(ship)>, const Ship*>);
retval[empire_id].push_back(ship->ID());
DebugLogger(combat) << " picked lowest stealth not-visible blockade capable ship in fleet: "
<< ship->Name() << " (id: " << ship->ID() << " stealth: " << stealth << ")";
} else {
DebugLogger(combat) << " no not-visible blockade capable ships in fleet!";
}
}
}
return retval;
}
void Resupply(const auto& fleets, ScriptingContext& context) {
static_assert(std::is_same_v<std::decay_t<decltype(*fleets.begin())>, Fleet*>);
const auto fleet_to_ships = [&context](const Fleet* fleet)
{ return context.ContextObjects().findRaw<Ship>(fleet->ShipIDs()); };
const auto owner_can_supply_at_location =
[&context](const auto* obj) {
static constexpr bool ALLOW_ALLIED_SUPPLY = true;
return obj && context.supply.SystemHasFleetSupply(obj->SystemID(), obj->Owner(),
ALLOW_ALLIED_SUPPLY, context.diplo_statuses);
};
// resupply ships at their initial locations
std::vector<std::vector<Ship*>> supplyable_ships;
supplyable_ships.reserve(context.ContextObjects().size<Fleet>());
range_copy(fleets | range_filter(owner_can_supply_at_location) | range_transform(fleet_to_ships),
std::back_inserter(supplyable_ships));
for (const auto& ships : supplyable_ships) {
for (auto* ship : ships)
ship->Resupply(context.current_turn);
}
}
void LogPathAndRoute(const Fleet* fleet, const auto& move_path, const ObjectMap& objects) {
if (move_path.empty())
return;
DebugLogger() << "Fleet " << fleet->Name() << " (" << fleet->ID()
<< ") route:" << [&]()
{
std::string ss;
ss.reserve(fleet->TravelRoute().size() * 32); // guesstimate
for (auto sys_id : fleet->TravelRoute()) {
if (auto sys = objects.getRaw<const System>(sys_id))
ss.append(" ").append(sys->Name()).append(" (")
.append(std::to_string(sys_id)).append(")");
else
ss.append(" (?) (").append(std::to_string(sys_id)).append(")");
}
return ss;
}()
<< " move path:" << [&]()
{
std::string ss;
ss.reserve(move_path.size() * 32); // guesstimate
for (const auto& node : move_path) {
if (auto sys = objects.getRaw<const System>(node.object_id))
ss.append(" ").append(sys->Name()).append(" (")
.append(std::to_string(node.object_id)).append(")");
else
ss.append(" (-)");
}
return ss;
}();
}
void LogTruncation(const auto* fleet, const auto& old_route,
int last_sys_id, const ScriptingContext& context)
{
const System* sys = context.ContextObjects().getRaw<const System>(last_sys_id);
DebugLogger() << "Truncated fleet " << fleet->Name() << " (" << fleet->ID()
<< ") route from: " << to_string(old_route)
<< " to end at last reachable system: "
<< (sys ? sys->Name() : "(Unknown system)") << " (" << last_sys_id << ")"
<< " :" << to_string(fleet->TravelRoute());
}
void GenerateSitrepsForBlockades(const auto& fleet_owner_and_blockading_fleets,
ScriptingContext& context)
{
static constexpr auto obj_to_id_and_owner = [](const auto* obj) noexcept -> std::pair<int, int>
{ return {obj->ID(), obj->Owner()}; };
const auto fleet_ids_to_also_owner_empires = [&context](const std::vector<int>& fleet_ids) {
std::vector<std::pair<int, int>> fleet_ids_owner_empire_ids;
fleet_ids_owner_empire_ids.reserve(fleet_ids.size());
const auto id_to_fleet = [&context](const int fleet_id) -> const Fleet*
{ return context.ContextObjects().getRaw<const Fleet>(fleet_id); };
auto empires_rng = fleet_ids | range_transform(id_to_fleet)
| range_filter(not_null) | range_transform(obj_to_id_and_owner);
range_copy(empires_rng, std::back_inserter(fleet_ids_owner_empire_ids));
return fleet_ids_owner_empire_ids;
};
for (const auto& [fleet_id_owner_id, blockading_fleet_ids] : fleet_owner_and_blockading_fleets) {
const auto [blockaded_fleet_id, blockaded_fleet_owner_id] = fleet_id_owner_id;
static_assert(std::is_same_v<std::decay_t<decltype(blockaded_fleet_id)>, int>);
static_assert(std::is_same_v<std::decay_t<decltype(blockading_fleet_ids)>, std::vector<int>>);
// which fleet and system is blockaded?
const Fleet* blockaded_fleet = context.ContextObjects().getRaw<const Fleet>(blockaded_fleet_id);
if (!blockaded_fleet || blockaded_fleet->SystemID() == INVALID_OBJECT_ID)
continue;
// which empire(s) are blockading the fleet?
const auto blockading_fleets_and_empire_ids = fleet_ids_to_also_owner_empires(blockading_fleet_ids);
for (auto& [recipient_empire_id, recipient_empire] : context.Empires()) {
// notify empires that can observe a blockaded fleet about the blockade
if (context.ContextVis(blockaded_fleet_id, recipient_empire_id) < Visibility::VIS_PARTIAL_VISIBILITY)
continue;
// every (blockading fleet) X (blockaded fleet) X (recipient empire) generates a separate sitrep
for (const auto &[blockading_fleet_id, blockading_empire_id] : blockading_fleets_and_empire_ids) {
if (context.ContextVis(blockading_fleet_id, recipient_empire_id) >= Visibility::VIS_PARTIAL_VISIBILITY) {
// if the blockading fleet is also visible, include that info
recipient_empire->AddSitRepEntry(
CreateFleetBlockadedSitRep(blockaded_fleet->SystemID(), blockaded_fleet_id,
blockaded_fleet_owner_id, blockading_empire_id, context));
} else {
// otherwise, just indicate that the blockade occurred
recipient_empire->AddSitRepEntry(
CreateFleetBlockadedSitRep(blockaded_fleet->SystemID(), blockaded_fleet_id,
blockaded_fleet_owner_id, context));
}
}
}
}
}
auto GetBlockadingFleetsForEmpires(auto&& fleet_owner_and_blockading_fleets) {
// map from empire ID to IDs of fleets that are blockading one of its fleets, and thus
// should be considered visible (as a fleet, not the contained ships) for combat initiation
std::map<int, std::vector<int>> empire_blockading_fleets;
for (const auto& [fleet_id_owner_id, blockading_fleet_ids] : fleet_owner_and_blockading_fleets) {
const auto [blockaded_fleet_id, blockaded_fleet_owner_id] = fleet_id_owner_id;
std::copy(blockading_fleet_ids.begin(), blockading_fleet_ids.end(),
std::back_inserter(empire_blockading_fleets[blockaded_fleet_owner_id]));
}
for (auto& [blockaded_fleet_owner_id, blockading_fleet_ids] : empire_blockading_fleets)
Uniquify(blockading_fleet_ids);
return empire_blockading_fleets;
}
/** Moves fleets, marks blockading fleets as visible to blockaded fleet owner empire.
* Returns fleet visibility overrides arising during movement, indexed by observer empire id. */
[[nodiscard]] auto HandleFleetMovement(ScriptingContext& context) {
const auto fleets = context.ContextObjects().allRaw<Fleet>();
for (auto* fleet : fleets | range_filter(not_null))
fleet->ClearArrivalFlag();
Resupply(fleets, context);
// get all fleets' move pathes (before anything moves).
// these move pathes may have been limited by blockades
const auto fleet_to_move_path = [&context](Fleet* fleet)
{ return std::pair{fleet, fleet->MovePath(true, context)}; };
std::vector<std::pair<Fleet*, std::vector<MovePathNode>>> fleets_move_pathes;
fleets_move_pathes.reserve(context.ContextObjects().size<Fleet>());
range_copy(fleets | range_filter(not_null) | range_transform(fleet_to_move_path),
std::back_inserter(fleets_move_pathes));
//// "correct" routes and pathes, in case a route is impossible to follow after some system...
//// TODO: need to rethink this, as the end of the valid part of the route (as reflected by
//// how far the path gets, may be in the middle, rather than at either end
//static constexpr auto not_empty_path = [](const auto& fleet_path) noexcept { return !fleet_path.second.empty(); };
//for (auto& [fleet, move_path] : fleets_move_pathes | range_filter(not_empty_path)) {
// const auto last_sys_id = move_path.back().object_id;
// if (last_sys_id == INVALID_OBJECT_ID) {
// ErrorLogger() << "Unexpected got fleet move path with last node not at a system...";
// continue;
// }
//
// const auto old_route{fleet->TravelRoute()};
// fleet->TruncateRouteToEndAtLastOf(last_sys_id);
//
// LogTruncation(fleet, old_route, last_sys_id, context);
//}
// log pathes
for (auto& [fleet, move_path] : fleets_move_pathes)
LogPathAndRoute(fleet, move_path, context.ContextObjects());
// if a blocking fleet has no armed and already-visible-to-the-blockaded-empire ships,
// mark lowest-stealth armed ship in blocking fleets as visible to moving fleet owner
auto [empires_blockaded_by_fleets, fleet_owner_and_blockading_fleets] =
GetBlockadingObjectsForEmpires(fleets_move_pathes, context);
context.ContextUniverse().SetObjectVisibilityOverrides(
GetShipsToRevealForEmpiresBlockadedByFleets(empires_blockaded_by_fleets, context));
// reset prev/next of not-moving fleets
static constexpr auto not_moving =
[](const std::pair<Fleet*, std::vector<MovePathNode>>& fleet_move_path) noexcept -> bool
{ return fleet_move_path.second.empty(); };
for (auto* fleet : fleets_move_pathes | range_filter(not_moving) | range_keys)
fleet->ResetPrevNextSystems();
// mark fleets that start turn in systems that are supply unobstructed as
// arriving from that system (which makes them able to depart on any lane)
const auto in_supply_unobstructed_system =
[&context](const Fleet* fleet) {
if (!fleet || fleet->SystemID() == INVALID_OBJECT_ID || fleet->Unowned())
return false;
const auto empire = context.GetEmpire(fleet->Owner());
if (!empire)
return false;
const auto& sus = empire->SupplyUnobstructedSystems();
return sus.contains(fleet->SystemID());
};
for (auto* fleet : fleets | range_filter(in_supply_unobstructed_system))
fleet->SetArrivalStarlane(fleet->SystemID());
static constexpr auto next_system_on_path = [](const std::vector<MovePathNode>& path)
noexcept(noexcept(path.front()))
{
if (path.empty())
return INVALID_OBJECT_ID;
const int first_node_obj_id = path.front().object_id;
for (const MovePathNode& node : path) {
if (node.object_id != first_node_obj_id && node.object_id != INVALID_OBJECT_ID)
return node.object_id; // next object on path
if (node.object_id == INVALID_OBJECT_ID && node.lane_end_id != INVALID_OBJECT_ID)
return node.lane_end_id; // end of lane that next node is on
}
return INVALID_OBJECT_ID;
};
static constexpr auto in_system_and_moving =
[](const std::pair<Fleet*, std::vector<MovePathNode>>& fleet_path)
noexcept(noexcept(fleet_path.second.front()))
{
const auto& [fleet, path] = fleet_path;
return fleet &&
fleet->SystemID() != INVALID_OBJECT_ID &&
!path.empty() &&
!path.front().blockaded_here;
};
// set fleets that are in systems and are unblockaded and
// are moving away from them as being from that system
for (auto& [fleet, path] : fleets_move_pathes | range_filter(in_system_and_moving)) {
const auto fleet_sys_id = fleet->SystemID();
if (!context.ContextObjects().getRaw<System>(fleet_sys_id)) {
ErrorLogger() << "Couldn't find system with id " << fleet_sys_id << " that fleet "
<< fleet->Name() << " (" << fleet->ID() << ") is supposedly in / departing";
continue;
}
fleet->SetArrivalStarlane(fleet_sys_id);
fleet->SetNextAndPreviousSystems(next_system_on_path(path), fleet_sys_id);
}
// move fleets to end of current turn's move path, consuming fuel or being resupplied
for (auto& [fleet, path] : fleets_move_pathes)
fleet->MoveAlongPath(context, path);
// post-movement visibility update
context.ContextUniverse().UpdateEmpireObjectVisibilities(context);
context.ContextUniverse().UpdateEmpireLatestKnownObjectsAndVisibilityTurns(context.current_turn);
context.ContextUniverse().UpdateEmpireStaleObjectKnowledge(context.Empires());
// SitReps for fleets having arrived at destinations
static constexpr auto arrived_this_turn = [](const Fleet* f) noexcept { return f && f->ArrivedThisTurn(); };
for (auto* fleet : fleets | range_filter(arrived_this_turn)) {
// sitreps for all empires that can see fleet at new location
for (auto& [empire_id, empire] : context.Empires()) {
if (context.ContextVis(fleet->ID(), empire_id) >= Visibility::VIS_BASIC_VISIBILITY) {
empire->AddSitRepEntry(
CreateFleetArrivedAtDestinationSitRep(fleet->SystemID(), fleet->ID(),
empire_id, context));
}
}
}
GenerateSitrepsForBlockades(fleet_owner_and_blockading_fleets, context);
auto empire_blockading_fleets = GetBlockadingFleetsForEmpires(fleet_owner_and_blockading_fleets);
for (const auto& [blockaded_empire_id, blockading_fleets] : empire_blockading_fleets) {
DebugLogger() << "FleetMovement blockading fleets for empire " << blockaded_empire_id
<< ": " << to_string(blockading_fleets);
}
return empire_blockading_fleets;
}
}
void ServerApp::CacheCostsTimes(const ScriptingContext& context) {
m_cached_empire_policy_adoption_costs = [this, &context]() {
std::map<int, std::vector<std::pair<std::string_view, double>>> retval;
std::transform(m_empires.begin(), m_empires.end(), std::inserter(retval, retval.end()),
[&context](const auto& e)
{ return std::pair{e.first, e.second->PolicyAdoptionCosts(context)}; });
return retval;
}();
m_cached_empire_research_costs_times = [this, &context]() {
std::map<int, std::vector<std::tuple<std::string_view, double, int>>> retval;
const auto& tm = GetTechManager();
// cache costs for each empire for techs on queue an that are researchable, which
// may be used later when updating the queue
for (const auto& [empire_id, empire] : m_empires) {
retval[empire_id].reserve(tm.size());
const auto should_cache = [empire{empire.get()}](const auto tech_name, const auto& tech) {
return (tech.Researchable() &&
empire->GetTechStatus(tech_name) == TechStatus::TS_RESEARCHABLE) ||
empire->GetResearchQueue().InQueue(tech_name);
};
for (const auto& [tech_name, tech] : tm) {
if (should_cache(tech_name, tech)) {
retval[empire_id].emplace_back(
tech_name, tech.ResearchCost(empire_id, context), tech.ResearchTime(empire_id, context));
}
}
}
return retval;
}();
m_cached_empire_production_costs_times = [this, &context]() {
std::map<int, std::vector<std::tuple<std::string_view, int, float, int>>> retval;
for (const auto& [empire_id, empire] : m_empires) {
retval[empire_id].reserve(empire->GetProductionQueue().size());
for (const auto& elem : empire->GetProductionQueue()) {
const auto [cost, time] = elem.ProductionCostAndTime(context);
retval[empire_id].emplace_back(elem.item.name, elem.item.design_id, cost, time);
}
}
return retval;
}();
m_cached_empire_annexation_costs = [&context]() {
std::map<int, std::vector<std::pair<int, double>>> retval;
// ensure every empire has an entry, even if empty
for (const int id : context.EmpireIDs())
retval.emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple());
// loop over planets, for each being annexed, store its annexation cost
const auto being_annexed = [](const Planet& p) { return p.IsAboutToBeAnnexed(); };
for (const auto* p : context.ContextObjects().findRaw<Planet>(being_annexed)) {
const auto empire_id = p->OrderedAnnexedByEmpire();
retval[empire_id].emplace_back(p->ID(), p->AnnexationCost(empire_id, context));
}
return retval;
}();
}
void ServerApp::PreCombatProcessTurns() {
ScopedTimer timer("ServerApp::PreCombatProcessTurns");
// revert current meter values to initial values prior to update after
// incrementing turn number during previous post-combat turn processing.
m_universe.ResetAllObjectMeters(false, true);
m_universe.UpdateEmpireVisibilityFilteredSystemGraphsWithOwnObjectMaps(m_empires);
DebugLogger() << "ServerApp::ProcessTurns executing orders";
// inform players of order execution
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::PROCESSING_ORDERS));
// clear bombardment state before executing orders, so result after is only
// determined by what orders set.
CleanUpBombardmentStateInfo(m_universe.Objects());
ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data, m_species_manager, m_supply_manager};
// execute orders
for (auto& [orders_empire_id, save_game_data] : m_turn_sequence) {
if (!save_game_data) {
DebugLogger() << "No SaveGameData for empire " << orders_empire_id;
continue;
}
if (!save_game_data->orders) {
DebugLogger() << "No OrderSet for empire " << orders_empire_id;
continue;
}
DebugLogger() << "<<= Executing Orders for empire " << orders_empire_id << " =>>";
save_game_data->orders->ApplyOrders(context);
}
// cache costs of stuff (policy adoption, research, production, annexation before anything more changes
// costs are determined after executing orders, adopting policies, queue manipulations, etc. but before
// any production, research, combat, annexation, gifting, etc. happens
CacheCostsTimes(context);
// clean up orders, which are no longer needed
ClearEmpireTurnOrders();
// TODO: CHECK THIS: was in ClearEmpireTurnOrders... needed?
for (auto& empire : m_empires | range_values)
empire->AutoTurnSetReady();
// update focus history info
UpdateResourceCenterFocusHistoryInfo(context.ContextObjects());
// validate adopted policies, and update Empire Policy history
// actual policy adoption occurs during order execution above
UpdateEmpirePolicies(m_empires, context.current_turn, false);
// clean up empty fleets that empires didn't order deleted
CleanEmptyFleets(context);
// update production queues after order execution
for (auto& [empire_id, empire] : m_empires) {
if (empire->Eliminated())
continue;
const auto pct_it = std::find_if(m_cached_empire_production_costs_times.begin(),
m_cached_empire_production_costs_times.end(),
[id{empire_id}](const auto& pct) { return pct.first == id; });
if (pct_it == m_cached_empire_production_costs_times.end()) {
ErrorLogger() << "Couldn't find cached production costs/times in PreCombatProcessTurns for empire " << empire_id;
continue;
}
empire->UpdateProductionQueue(context, pct_it->second);
}
// player notifications
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::COLONIZE_AND_SCRAP));
DebugLogger() << "ServerApp::ProcessTurns colonization";
auto [colonized_planet_ids, colonizing_ship_ids] = HandleColonization(context);
DebugLogger() << "ServerApp::ProcessTurns invasion";
auto [invaded_planet_ids, invading_ship_ids] = HandleInvasion(context);
DebugLogger() << "ServerApp::ProcessTurns annexation";
auto annexed_ids = HandleAnnexation(context, invaded_planet_ids, m_cached_empire_annexation_costs);
DebugLogger() << "ServerApp::ProcessTurns gifting";
auto gifted_ids = HandleGifting(m_empires, m_universe.Objects(), context.current_turn, invaded_planet_ids,
invading_ship_ids, colonizing_ship_ids, annexed_ids);
DebugLogger() << "ServerApp::ProcessTurns scrapping";
HandleScrapping(m_universe, m_empires, invading_ship_ids, invaded_planet_ids,
colonizing_ship_ids, colonized_planet_ids, gifted_ids, annexed_ids);
DebugLogger() << "ServerApp::ProcessTurns movement";
// player notifications
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::FLEET_MOVEMENT));
// Update system-obstruction after orders, colonization, invasion, gifting, scrapping
static constexpr auto not_eliminated = [](auto& id_e) { return !id_e.second->Eliminated(); };
for (auto& empire : m_empires | range_filter(not_eliminated) | range_values)
empire->UpdateSupplyUnobstructedSystems(context, true);
m_empire_fleet_combat_initiation_vis_overrides = HandleFleetMovement(context);
// indicate that the clients are waiting for their new Universes
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::DOWNLOADING));
for (auto& empire : m_empires | range_values) {
empire->UpdateOwnedObjectCounters(m_universe);
empire->PrepQueueAvailabilityInfoForSerialization(context);
empire->PrepPolicyInfoForSerialization(context);
}
// send partial turn updates to all players after orders and movement
// exclude those without empire and who are not Observer or Moderator
for (auto player_it = m_networking.established_begin();
player_it != m_networking.established_end(); ++player_it)
{
const auto& player = *player_it;
const int empire_id = PlayerEmpireID(player->PlayerID());
if (m_empires.GetEmpire(empire_id) ||
player->GetClientType() == Networking::ClientType::CLIENT_TYPE_HUMAN_MODERATOR ||
player->GetClientType() == Networking::ClientType::CLIENT_TYPE_HUMAN_OBSERVER)
{
bool use_binary_serialization = player->IsBinarySerializationUsed();
player->SendMessage(TurnPartialUpdateMessage(PlayerEmpireID(player->PlayerID()),
m_universe, use_binary_serialization,
!player->IsLocalConnection()));
}
}
}
void ServerApp::ProcessCombats() {
ScopedTimer timer("ServerApp::ProcessCombats");
DebugLogger() << "ServerApp::ProcessCombats";
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::COMBAT));
ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data, m_species_manager, m_supply_manager};
// collect data about locations where combat is to occur:
// map from system ID to CombatInfo for that system
auto combats = AssembleSystemCombatInfo(context, m_empire_fleet_combat_initiation_vis_overrides);
m_empire_fleet_combat_initiation_vis_overrides.clear();
// loop through assembled combat infos, handling each combat to update the
// various systems' CombatInfo structs
for (CombatInfo& combat_info : combats) {
const auto combat_system = combat_info.GetSystem();
if (combat_system)
combat_system->SetLastTurnBattleHere(context.current_turn);
DebugLogger(combat) << "Processing combat at " << (combat_system ? combat_system->Name() : "(No System id: " + std::to_string(combat_info.system_id) + ")");
TraceLogger(combat) << combat_info.objects.Dump();
AutoResolveCombat(combat_info);
}
BackProjectSystemCombatInfoObjectMeters(combats);
UpdateEmpireCombatDestructionInfo(combats, context);
DisseminateSystemCombatInfo(combats, m_universe, m_empires);
// update visibilities with any new info gleaned during combat
m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(context.current_turn);
// update stale object info based on any mid- combat glimpses
// before visibility is totally recalculated in the post combat processing
m_universe.UpdateEmpireStaleObjectKnowledge(m_empires);
CreateCombatSitReps(combats);
}
void ServerApp::UpdateMonsterTravelRestrictions() {
const ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data,
m_species_manager, m_supply_manager};
for (auto const* system : m_universe.Objects().allRaw<System>()) {
bool unrestricted_monsters_present = false;
bool empires_present = false;
bool unrestricted_empires_present = false;
std::vector<Fleet*> monsters;
for (auto* fleet : m_universe.Objects().findRaw<Fleet>(system->FleetIDs())) {
// will not require visibility for empires to block clearing of monster travel restrictions
// unrestricted lane access (i.e, (fleet->ArrivalStarlane() == system->ID()) ) is used as a proxy for
// order of arrival -- if an enemy has unrestricted lane access and you don't, they must have arrived
// before you, or be in cahoots with someone who did.
bool unrestricted = ((fleet->ArrivalStarlane() == system->ID())
&& fleet->Obstructive()
&& fleet->CanDamageShips(context));
if (fleet->Unowned()) {
monsters.push_back(fleet);
if (unrestricted)
unrestricted_monsters_present = true;
} else {
empires_present = true;
if (unrestricted)
unrestricted_empires_present = true;
}
}
// Prevent monsters from leaving any empire blockade.
if (unrestricted_empires_present) {
for (auto* monster_fleet : monsters)
monster_fleet->SetArrivalStarlane(INVALID_OBJECT_ID);
}
// Break monster blockade after combat.
if (empires_present && unrestricted_monsters_present) {
for (auto* monster_fleet : monsters)
monster_fleet->SetArrivalStarlane(INVALID_OBJECT_ID);
}
}
}
void ServerApp::PostCombatProcessTurns() {
ScopedTimer timer("ServerApp::PostCombatProcessTurns");
ScriptingContext context{m_universe, m_empires, m_galaxy_setup_data,
m_species_manager, m_supply_manager};
// post-combat visibility update
m_universe.UpdateEmpireObjectVisibilities(context);
m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(context.current_turn);
// check for loss of empire capitals
for (auto& [empire_id, empire] : m_empires) {
int capital_id = empire->CapitalID();
if (auto capital = m_universe.Objects().get(capital_id)) {
if (!capital->OwnedBy(empire_id))
empire->SetCapitalID(INVALID_OBJECT_ID, m_universe.Objects());
} else {
empire->SetCapitalID(INVALID_OBJECT_ID, m_universe.Objects());
}
}
m_empires.RefreshCapitalIDs();
// process production and growth phase
// notify players that production and growth is being processed
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::EMPIRE_PRODUCTION));
DebugLogger() << "ServerApp::PostCombatProcessTurns effects and meter updates";
TraceLogger(effects) << "!!!!!!! BEFORE TURN PROCESSING EFFECTS APPLICATION";
TraceLogger(effects) << m_universe.Objects().Dump();
// execute all effects and update meters prior to production, research, etc.
if (GetGameRules().Get<bool>("RULE_RESEED_PRNG_SERVER")) {
static boost::hash<std::string> pcpt_string_hash;
Seed(static_cast<unsigned int>(context.current_turn) + pcpt_string_hash(m_galaxy_setup_data.seed));
}
m_universe.ApplyAllEffectsAndUpdateMeters(context, false);
// regenerate system connectivity graph after executing effects, which may
// have added or removed starlanes.
m_universe.InitializeSystemGraph(m_empires, m_universe.Objects());
m_universe.UpdateEmpireVisibilityFilteredSystemGraphsWithOwnObjectMaps(m_empires);
TraceLogger(effects) << "!!!!!!! AFTER TURN PROCESSING EFFECTS APPLICATION";
TraceLogger(effects) << m_universe.Objects().Dump();
DebugLogger() << "ServerApp::PostCombatProcessTurns empire resources updates";
// now that we've had combat and applied Effects, update visibilities again, prior
// to updating system obstructions below.
m_universe.UpdateEmpireObjectVisibilities(context);
m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(context.current_turn);
UpdateEmpireSupply(context, m_supply_manager, false);
UpdateResourcePools(context, m_cached_empire_research_costs_times,
m_cached_empire_annexation_costs, m_cached_empire_policy_adoption_costs,
m_cached_empire_production_costs_times);
// Update fleet travel restrictions (monsters and empire fleets)
UpdateMonsterTravelRestrictions();
for (auto& [empire_id, empire] : m_empires) {
if (!empire->Eliminated()) {
empire->UpdatePreservedLanes();
empire->UpdateUnobstructedFleets(
m_universe.Objects(), m_universe.EmpireKnownDestroyedObjectIDs(empire_id)); // must be done after *all* noneliminated empires have updated their unobstructed systems
}
}
TraceLogger(effects) << "!!!!!!! AFTER UPDATING RESOURCE POOLS AND SUPPLY STUFF";
TraceLogger(effects) << m_universe.Objects().Dump();
DebugLogger() << "ServerApp::PostCombatProcessTurns queue progress checking";
// Consume distributed resources to planets and on queues, create new
// objects for completed production and give techs to empires that have
// researched them
for ([[maybe_unused]] auto& [empire_id, empire] : m_empires) {
if (empire->Eliminated())
continue; // skip eliminated empires
const auto cached_tech_cost_it = m_cached_empire_research_costs_times.find(empire_id);
if (cached_tech_cost_it == m_cached_empire_research_costs_times.end()) {
ErrorLogger() << "no cached research costs info for empire " << empire_id;
} else {
const auto& costs_times = cached_tech_cost_it->second;
const auto new_techs = empire->CheckResearchProgress(context, costs_times);
for (const auto& tech : new_techs)
empire->AddNewlyResearchedTechToGrantAtStartOfNextTurn(tech);
}
const auto cached_prod_cost_it = m_cached_empire_production_costs_times.find(empire_id);
if (cached_prod_cost_it == m_cached_empire_production_costs_times.end()) {
ErrorLogger() << "no cached production costs info for empire " << empire_id;
} else {
const auto& costs_times = cached_prod_cost_it->second;
empire->CheckProductionProgress(context, costs_times);
}
//const auto cached_policy_cost_it = m_cached_empire_policy_adoption_costs.find(empire_id);
//if (cached_policy_cost_it == m_cached_empire_policy_adoption_costs.end())
// ErrorLogger() << "no cached policy costs info for empire " << empire_id;
//static CONSTEXPR_VEC const decltype(cached_policy_cost_it->second) EMPTY_POLICY_COSTS;
//const auto& policy_costs = (cached_policy_cost_it == m_cached_empire_policy_adoption_costs.end()) ?
// EMPTY_POLICY_COSTS : cached_policy_cost_it->second;
//const auto cached_annex_cost_it = m_cached_empire_annexation_costs.find(empire_id);
//if (cached_annex_cost_it == m_cached_empire_annexation_costs.end())
// ErrorLogger() << "no cached annex costs info for empire " << empire_id;
//static CONSTEXPR_VEC const decltype(cached_annex_cost_it->second) EMPTY_ANNEX_COSTS;
//const auto& annex_costs = (cached_annex_cost_it == m_cached_empire_annexation_costs.end()) ?
// EMPTY_ANNEX_COSTS : cached_annex_cost_it->second;
empire->CheckInfluenceProgress(/*policy_costs, annex_costs*/);
}
TraceLogger(effects) << "!!!!!!! AFTER CHECKING QUEUE AND RESOURCE PROGRESS";
TraceLogger(effects) << m_universe.Objects().Dump();
// execute turn events implemented as Python scripts
ExecuteScriptedTurnEvents();
// Execute meter-related effects on objects created this turn, so that new
// UniverseObjects will have effects applied to them this turn, allowing
// (for example) ships to have max fuel meters greater than 0 on the turn
// they are created.
m_universe.ApplyMeterEffectsAndUpdateMeters(context, false);
TraceLogger(effects) << "!!!!!!! AFTER UPDATING METERS OF ALL OBJECTS";
TraceLogger(effects) << m_universe.Objects().Dump();
// Planet depopulation, some in-C++ meter modifications
for (const auto& obj : m_universe.Objects().all()) {
obj->PopGrowthProductionResearchPhase(context);
obj->ClampMeters(); // ensures no meters are over MAX. probably redundant with ClampMeters() in Universe::ApplyMeterEffectsAndUpdateMeters()
}
TraceLogger(effects) << "!!!!!!!!!!!!!!!!!!!!!!AFTER GROWTH AND CLAMPING";
TraceLogger(effects) << m_universe.Objects().Dump();
// store initial values of meters for this turn.
m_universe.BackPropagateObjectMeters();
m_empires.BackPropagateMeters();
m_species_manager.BackPropagateOpinions();
// check for loss of empire capitals
for (auto& [empire_id, empire] : m_empires) {
int capital_id = empire->CapitalID();
if (auto capital = m_universe.Objects().get(capital_id)) {
if (!capital->OwnedBy(empire_id))
empire->SetCapitalID(INVALID_OBJECT_ID, m_universe.Objects());
} else {
empire->SetCapitalID(INVALID_OBJECT_ID, m_universe.Objects());
}
}
m_empires.RefreshCapitalIDs();
// store any changes in objects from various progress functions
// before updating visibility again, so that if the
// visibility update removes an empires ability to detect an object, the
// empire will still know the latest state on the
// turn when the empire did have detection ability for the object
m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(context.current_turn);
// post-production and meter-effects visibility update
m_universe.UpdateEmpireObjectVisibilities(context);
m_universe.UpdateEmpireStaleObjectKnowledge(m_empires);
// update empire-visibility filtered graphs after visiblity update
m_universe.UpdateEmpireVisibilityFilteredSystemGraphsWithOwnObjectMaps(m_empires);
TraceLogger(effects) << "!!!!!!!!!!!!!!!!!!!!!!AFTER TURN PROCESSING POP GROWTH PRODCUTION RESEARCH";
TraceLogger(effects) << m_universe.Objects().Dump();
// this has to be here for the sitreps it creates to be in the right turn
CheckForEmpireElimination();
// update current turn number so that following visibility updates and info
// sent to players will have updated turn associated with them
context.current_turn = ++m_current_turn;
DebugLogger() << "ServerApp::PostCombatProcessTurns Turn number incremented to " << m_current_turn;
// new turn visibility update
m_universe.UpdateEmpireObjectVisibilities(context);
DebugLogger() << "ServerApp::PostCombatProcessTurns applying Newly Added Techs";
// apply new techs
for (auto& empire : m_empires | range_values
| range_filter([](const auto& e) { return e && !e->Eliminated(); }))
{
empire->ApplyNewTechs(m_universe, m_current_turn);
empire->ApplyPolicies(m_universe, m_current_turn);
}
// do another policy update before final meter update to be consistent with what clients calculate...
UpdateEmpirePolicies(m_empires, context.current_turn, true);
TraceLogger(effects) << "ServerApp::PostCombatProcessTurns Before Final Meter Estimate Update: ";
TraceLogger(effects) << m_universe.Objects().Dump();
// redo meter estimates to hopefully be consistent with what happens in clients
m_universe.UpdateMeterEstimates(context, false);
TraceLogger(effects) << "ServerApp::PostCombatProcessTurns After Final Meter Estimate Update: ";
TraceLogger(effects) << m_universe.Objects().Dump();
// Re-determine supply distribution and exchanging and resource pools for empires
UpdateEmpireSupply(context, m_supply_manager, true);
UpdateResourcePools(context, m_cached_empire_research_costs_times,
m_cached_empire_annexation_costs, m_cached_empire_policy_adoption_costs,
m_cached_empire_production_costs_times);
// copy latest visible gamestate to each empire's known object state
m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(context.current_turn);
// misc. other updates and records
m_universe.UpdateStatRecords(context);
for (auto& empire : m_empires | range_values) {
empire->UpdateOwnedObjectCounters(m_universe);
empire->PrepQueueAvailabilityInfoForSerialization(context);
empire->PrepPolicyInfoForSerialization(context);
}
// indicate that the clients are waiting for their new gamestate
m_networking.SendMessageAll(TurnProgressMessage(Message::TurnProgressPhase::DOWNLOADING));
// compile map of PlayerInfo, indexed by player ID
std::map<int, PlayerInfo> players;
for (auto player_it = m_networking.established_begin();
player_it != m_networking.established_end(); ++player_it)
{
PlayerConnectionPtr player = *player_it;
const int player_id = player->PlayerID();
players[player_id] = PlayerInfo{player->PlayerName(),
PlayerEmpireID(player_id),
player->GetClientType(),
m_networking.PlayerIsHost(player_id)};
}
{ // TEST
const auto server_players = this->GetPlayerInfoMap();
if (server_players.size() != players.size())
WarnLogger() << "PostCombatProcessTurns constructed players has different size than server players";
for (auto it1 = server_players.begin(), it2 = players.cbegin(); it1 != server_players.end(); ++it1, ++it2) {
if (it1->first != it2->first)
WarnLogger() << "PostCombatProcessTurns constructed player info id " << it1->first
<< " differs from server info id " << it2->first;
if (it1->second != it2->second) {
WarnLogger() << "PostCombatProcessTurns constructed player info differs from server player info:\n" <<
it1->second.name << " ? " << it2->second.name << "\n" <<
it1->second.empire_id << " ? " << it2->second.empire_id << "\n" <<
it1->second.client_type << " ? " << it2->second.client_type << "\n" <<
it1->second.host << " ? " << it2->second.host;
}
}
} // END TEST
m_universe.ObfuscateIDGenerator();
DebugLogger() << "ServerApp::PostCombatProcessTurns Sending turn updates to players";
// send new-turn updates to all players
// exclude those without empire and who are not Observer or Moderator
for (auto player_it = m_networking.established_begin();
player_it != m_networking.established_end(); ++player_it)
{
PlayerConnectionPtr player = *player_it;
const int empire_id = PlayerEmpireID(player->PlayerID());
const auto empire = m_empires.GetEmpire(empire_id);
if (empire ||
player->GetClientType() == Networking::ClientType::CLIENT_TYPE_HUMAN_MODERATOR ||
player->GetClientType() == Networking::ClientType::CLIENT_TYPE_HUMAN_OBSERVER)
{
const bool use_binary_serialization = player->IsBinarySerializationUsed();
player->SendMessage(TurnUpdateMessage(empire_id, m_current_turn,
m_empires, m_universe,
m_species_manager, GetCombatLogManager(),
m_supply_manager, players,
use_binary_serialization, !player->IsLocalConnection()),
empire_id, m_current_turn);
}
}
m_turn_expired = false;
DebugLogger() << "ServerApp::PostCombatProcessTurns done";
}
void ServerApp::CheckForEmpireElimination() {
std::set<std::shared_ptr<Empire>> surviving_empires;
std::set<std::shared_ptr<Empire>> non_eliminated_non_ai_controlled_empires;
for (auto& [empire_id, empire] : m_empires) {
if (empire->Eliminated()) {
continue; // don't double-eliminate an empire
} else if (EmpireEliminated(empire_id, m_universe.Objects())) {
empire->Eliminate(m_empires, m_current_turn);
RemoveEmpireTurn(empire_id);
const int player_id = EmpirePlayerID(empire_id);
DebugLogger() << "ServerApp::CheckForEmpireElimination empire #" << empire_id << " " << empire->Name()
<< " of player #" << player_id << " eliminated";
auto player_it = m_networking.GetPlayer(player_id);
if (player_it != m_networking.established_end() &&
(*player_it)->GetClientType() == Networking::ClientType::CLIENT_TYPE_AI_PLAYER)
{
auto it = m_ai_client_processes.find((*player_it)->PlayerName());
if (it != m_ai_client_processes.end()) {
it->second.Kill();
m_ai_client_processes.erase(it);
}
}
} else {
surviving_empires.insert(empire);
// empires could be controlled only by connected AI client, connected human client, or
// disconnected human client.
// Disconnected AI client controls non-eliminated empire is an error.
if (GetEmpireClientType(empire->EmpireID()) != Networking::ClientType::CLIENT_TYPE_AI_PLAYER)
non_eliminated_non_ai_controlled_empires.insert(empire);
}
}
if (surviving_empires.size() == 1) { // last man standing
auto& only_empire = *surviving_empires.begin();
only_empire->Win(UserStringNop("VICTORY_ALL_ENEMIES_ELIMINATED"), m_empires.GetEmpires(), m_current_turn);
} else if (!m_single_player_game &&
static_cast<int>(non_eliminated_non_ai_controlled_empires.size()) <= GetGameRules().Get<int>("RULE_THRESHOLD_HUMAN_PLAYER_WIN"))
{
// human victory threshold
if (GetGameRules().Get<bool>("RULE_ONLY_ALLIANCE_WIN")) {
for (auto emp1_it = non_eliminated_non_ai_controlled_empires.begin();
emp1_it != non_eliminated_non_ai_controlled_empires.end(); ++emp1_it)
{
auto emp2_it = emp1_it;
++emp2_it;
for (; emp2_it != non_eliminated_non_ai_controlled_empires.end(); ++emp2_it) {
const auto status = m_empires.GetDiplomaticStatus((*emp1_it)->EmpireID(), (*emp2_it)->EmpireID());
// if diplomacy forbidden then allow peace status
if (status == DiplomaticStatus::DIPLO_WAR || (GetGameRules().Get<std::string>("RULE_DIPLOMACY") != UserStringNop("RULE_DIPLOMACY_FORBIDDEN_FOR_ALL") && status == DiplomaticStatus::DIPLO_PEACE))
return;
}
}
}
for (auto& empire : non_eliminated_non_ai_controlled_empires)
empire->Win(UserStringNop("VICTORY_FEW_HUMANS_ALIVE"), m_empires.GetEmpires(), m_current_turn);
}
}
void ServerApp::HandleDiplomaticStatusChange(int empire1_id, int empire2_id) {
DiplomaticStatus status = m_empires.GetDiplomaticStatus(empire1_id, empire2_id);
DiplomaticStatusUpdateInfo update(empire1_id, empire2_id, status);
for (auto player_it = m_networking.established_begin();
player_it != m_networking.established_end(); ++player_it)
{
PlayerConnectionPtr player = *player_it;
player->SendMessage(DiplomaticStatusMessage(update));
}
}
void ServerApp::HandleDiplomaticMessageChange(int empire1_id, int empire2_id) {
const DiplomaticMessage& message = m_empires.GetDiplomaticMessage(empire1_id, empire2_id);
// get players corresponding to empires in message
int player1_id = EmpirePlayerID(empire1_id);
int player2_id = EmpirePlayerID(empire2_id);
if (player1_id == Networking::INVALID_PLAYER_ID || player2_id == Networking::INVALID_PLAYER_ID)
return;
auto player1_it = m_networking.GetPlayer(player1_id);
if (player1_it != m_networking.established_end())
(*player1_it)->SendMessage(DiplomacyMessage(message));
auto player2_it = m_networking.GetPlayer(player2_id);
if (player2_it != m_networking.established_end())
(*player2_it)->SendMessage(DiplomacyMessage(message));
}
|