Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Comment: | initial commit of v0.4.3 of AI Empire |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | trunk |
Files: | files | file ages | folders |
SHA1: | 28693ba5f2fef65a48df55bf51306291 |
User & Date: | pollen 2019-06-19 04:29:05 |
2019-06-19
| ||
04:29 | initial commit of v0.4.3 of AI Empire Leaf check-in: 28693ba5f2 user: pollen tags: trunk | |
04:07 | initial empty check-in check-in: bf6341cca7 user: pollen tags: trunk | |
Added data/skin definitions/default.txt.
1 +Font: Small = DroidSans_8 2 +Font: Normal = DroidSans_11 3 +Font: Bold = DroidSans_11_Bold 4 +Font: Italic = OpenSans_11_Italic 5 +Font: Medium = DroidSans_16 6 +Font: Big = DroidSans_20 7 + 8 +Font: Subsys = DroidSans_11 9 +Font: Name = GoodTimes_9 10 +Font: Detail = DroidSans_8 11 + 12 +Font: Title = GoodTimes_18 13 +Font: Subtitle = DroidSans_14 14 +Font: Button = DroidSans_11 15 + 16 +Color: Text = #fff 17 +Color: Selected = #a0000080 18 +Color: Disabled = #888888ff 19 + 20 +Color: ButtonText = #fff 21 + 22 +// {{{ Generic Styles 23 +Style: RoundedBox 24 + Element: Normal 25 + Rect: [3,3][182,39] 26 + Margin: 6 27 + Vertical: Scaled 28 + Horizontal: Scaled 29 + 30 + Element: Hovered 31 + Rect: [3,43][182,79] 32 + Margin: 6 33 + Vertical: Scaled 34 + Horizontal: Scaled 35 + 36 + Element: Active 37 + Rect: [3,83][182,119] 38 + Margin: 6 39 + Vertical: Scaled 40 + Horizontal: Scaled 41 + 42 + Element: Hovered, Active 43 + Inherit: RoundedBox, Active 44 + 45 + Element: Disabled 46 + Rect: [195,343][374,379] 47 + Margin: 6 48 + Vertical: Scaled 49 + Horizontal: Scaled 50 + 51 +Style: StraightBox 52 + Element: Normal 53 + Rect: [194,4][373,39] 54 + Margin: 6 55 + Vertical: Scaled 56 + Horizontal: Scaled 57 + 58 + Element: Active 59 + Rect: [194,84][373,119] 60 + Margin: 6 61 + Vertical: Scaled 62 + Horizontal: Scaled 63 + 64 + Element: Hovered 65 + Rect: [194,44][373,79] 66 + Margin: 6 67 + Vertical: Scaled 68 + Horizontal: Scaled 69 + 70 + Element: Active, Hovered 71 + Rect: [194,124][373,159] 72 + Margin: 6 73 + Vertical: Scaled 74 + Horizontal: Scaled 75 + 76 +Style: TinyBox 77 + Element: Normal 78 + Rect: [194,4][373,39] 79 + Margin: 2 80 + Vertical: Scaled 81 + Horizontal: Scaled 82 + 83 + Element: Hovered 84 + Rect: [194,84][373,119] 85 + Margin: 2 86 + Vertical: Scaled 87 + Horizontal: Scaled 88 + 89 +Style: ItemBox 90 + Element: Normal 91 + Rect: [3,124][182,159] 92 + Margin: 4 93 + Vertical: Scaled 94 + Horizontal: Scaled 95 + 96 + Element: Hovered 97 + Rect: [3,164][182,199] 98 + Margin: 4 99 + Vertical: Scaled 100 + Horizontal: Scaled 101 + 102 + Element: Active 103 + Rect: [3,204][182,239] 104 + Margin: 4 105 + Vertical: Scaled 106 + Horizontal: Scaled 107 + 108 + Element: Hovered, Active 109 + Rect: [3,699][182,734] 110 + Margin: 4 111 + Vertical: Scaled 112 + Horizontal: Scaled 113 + 114 + Element: Disabled 115 + Rect: [3,124][182,159] 116 + Margin: 4 117 + Vertical: Scaled 118 + Horizontal: Scaled 119 + 120 + Add Gradient: 121 + TopLeft : #00000020 122 + TopRight: #00000020 123 + BotLeft : #00000020 124 + BotRight: #00000020 125 + 126 + GX1: 0 127 + GY1: 0 128 + GX2: 100% 129 + GY2: 100% 130 + 131 +Style: InputBox 132 + Element: Normal 133 + Rect: [2,493][181,528] 134 + Margin: 6 135 + Vertical: Scaled 136 + Horizontal: Scaled 137 + 138 + Element: Active 139 + Rect: [2,533][181,568] 140 + Margin: 6 141 + Vertical: Scaled 142 + Horizontal: Scaled 143 + 144 + Element: Hovered 145 + Rect: [2,573][181,608] 146 + Margin: 6 147 + Vertical: Scaled 148 + Horizontal: Scaled 149 + 150 + Element: Active, Hovered 151 + Rect: [2,613][181,648] 152 + Margin: 6 153 + Vertical: Scaled 154 + Horizontal: Scaled 155 + 156 + Element: Disabled 157 + Rect: [2,653][181,692] 158 + Margin: 6 159 + Vertical: Scaled 160 + Horizontal: Scaled 161 + 162 +Style: PlainBox 163 + Element: Normal 164 + Rect: [400,40][426,61] 165 + Margin: 2 166 + 167 + Horizontal: Scaled 168 + Vertical: Scaled 169 + 170 +Style: PlainNameBox 171 + Element: Normal 172 + Rect: [429,42][459,56] 173 + Margin: 16,14 174 + Horizontal: Scaled 175 + 176 +Style: HexPattern 177 + Element: Normal 178 + Rect: [390,11][412,36] 179 + Margin: 1 180 + Vertical: Tiled 181 + Horizontal: Tiled 182 + 183 +Style: SmallHexPattern 184 + Element: Normal 185 + Rect: [390,40][395,56] 186 + Margin: 0 187 + Vertical: Tiled 188 + Horizontal: Tiled 189 + 190 +Style: PatternBox 191 + Element: Normal 192 + Layer: PlainBox 193 + Layer: SmallHexPattern 194 + Color Override: #ffffff40 195 + OX1: +3 196 + OY1: +3 197 + OX2: -3 198 + OY2: -3 199 + 200 + GradientMode: Overlay 201 + Add Gradient: 202 + TopLeft : #4443 203 + TopRight: #4443 204 + BotLeft : #2221 205 + BotRight: #2221 206 + 207 + GX1: +3 208 + GY1: +3 209 + GX2: -3 210 + GY2: -3 211 + 212 +Style: Panel 213 + Element: Normal 214 + Rect: [195,179][375,215] 215 + Margin: 6 216 + 217 + Vertical: Scaled 218 + Horizontal: Scaled 219 + 220 + Add Gradient: 221 + TopLeft : #1b1b1bff 222 + TopRight: #1b1b1bff 223 + BotLeft : #101010ff 224 + BotRight: #101010ff 225 + 226 + GX1: +4 227 + GY1: 50% 228 + GX2: -5 229 + GY2: -5 230 + 231 +Style: HorizBar 232 + Element: Normal 233 + Rect: [205,221][365,257] 234 + Margin: 6 235 + 236 + Vertical: Scaled 237 + Horizontal: Scaled 238 + 239 +Style: PlainOverlay 240 + Element: Normal 241 + Rect: [400,40][426,61] 242 + Margin: 2 243 + 244 + Horizontal: Scaled 245 + Vertical: Scaled 246 + 247 + Add Gradient: 248 + TopLeft : #1c1c1cff 249 + TopRight: #1c1c1cff 250 + BotLeft : #151515ff 251 + BotRight: #151515ff 252 + 253 + GX1: 1 254 + GY1: 1 255 + GX2: -1 256 + GY2: -1 257 + 258 + Element: Hovered 259 + Rect: [400,40][426,61] 260 + Margin: 2 261 + 262 + Horizontal: Scaled 263 + Vertical: Scaled 264 + 265 + Add Gradient: 266 + TopLeft : #2c2c2cff 267 + TopRight: #2c2c2cff 268 + BotLeft : #202020ff 269 + BotRight: #202020ff 270 + 271 + GX1: 1 272 + GY1: 1 273 + GX2: -1 274 + GY2: -1 275 + 276 + Element: Disabled 277 + Inherit: PlainOverlay, Normal 278 + 279 +Style: OverlayBox 280 + Element: Normal 281 + Inherit: Panel 282 + 283 +Style: LightPanel 284 + Element: Normal 285 + Rect: [195,221][375,257] 286 + Margin: 6 287 + 288 + Vertical: Scaled 289 + Horizontal: Scaled 290 + 291 +Style: Highlight 292 + Element: Normal 293 + Rect: [5,740][182,773] 294 + Margin: 6 295 + 296 + Vertical: Scaled 297 + Horizontal: Scaled 298 + 299 +Style: Glow 300 + Element: Normal 301 + Rect: [4,780][184,849] 302 + Margin: 10 303 + 304 + Vertical: Scaled 305 + Horizontal: Scaled 306 + 307 +Style: SubtleGlow 308 + Element: Normal 309 + Rect: [4,854][184,922] 310 + Margin: 10 311 + 312 + Vertical: Scaled 313 + Horizontal: Scaled 314 + 315 +Style: HighlightPanel 316 + Element: Normal 317 + Inherit: Panel, Normal 318 + 319 + Element: Hovered 320 + Inherit: Panel, Normal 321 + 322 + Add Gradient: 323 + TopLeft : #2c2c2c40 324 + TopRight: #2c2c2c40 325 + BotLeft : #20202040 326 + BotRight: #20202040 327 + 328 + GX1: 3 329 + GY1: 3 330 + GX2: -3 331 + GY2: -3 332 + 333 +Style: RoundedTitle 334 + Element: Normal 335 + Rect: [195,264][393,294] 336 + Margin: 6, 6, 120, 6 337 + 338 + Horizontal: Scaled 339 + Vertical: Scaled 340 + 341 +Style: CenterTitle 342 + Element: Normal 343 + Rect: [195,308][410,334] 344 + Margin: 90, 6 345 + 346 + Horizontal: Scaled 347 + Vertical: Scaled 348 + 349 +Style: FullTitle 350 + Element: Normal 351 + Rect: [198,450][332,480] 352 + Margin: 6 353 + 354 + Horizontal: Scaled 355 + Vertical: Scaled 356 + 357 +Style: PanelTitle 358 + Element: Normal 359 + Layer: FullTitle 360 + Color Override: #fff 361 + OX1: 0 362 + OY1: 0 363 + OX2: 100% 364 + OY2: 100% 365 + Layer: RoundedTitle 366 + OX1: 0 367 + OY1: 0 368 + OX2: 70% 369 + OY2: 100% 370 + 371 +Style: WindowTitle 372 + Inherit: FullTitle 373 + 374 +Style: SubTitle 375 + Element: Normal 376 + Rect: [201,487][327,517] 377 + Margin: 6 378 + 379 + Horizontal: Scaled 380 + Vertical: Scaled 381 + 382 +Style: HorizAccent 383 + Element: Normal 384 + Rect: [197,385][302,439] 385 + Margin: 8 386 + 387 + Horizontal: Scaled 388 + Vertical: Scaled 389 +// }}} 390 +// {{{ Generic Parts 391 +Style: DownArrow 392 + Element: Normal 393 + Rect: [443,6][455,19] 394 + 395 +Style: UpArrow 396 + Element: Normal 397 + Rect: [457,6][470,19] 398 + 399 +Style: RightArrow 400 + Element: Normal 401 + Rect: [429,6][441,19] 402 + 403 +Style: LeftArrow 404 + Element: Normal 405 + Rect: [416,6][428,19] 406 + 407 +Style: Field 408 + Inherit: PlainBox 409 + 410 +Style: FieldName 411 + Inherit: PlainNameBox 412 + 413 +Style: BG3D 414 + Element: Normal 415 + Rect: [202,526][446,636] 416 + 417 +Style: ProgressBarBG 418 + Element: Normal 419 + Rect: [382,70][488,80] 420 + Margin: 6, 4 421 + Horizontal: Scaled 422 + Vertical: Scaled 423 + 424 +Style: ProgressBar 425 + Element: Normal 426 + Rect: [383,83][487,91] 427 + Margin: 3, 4 428 + Horizontal: Scaled 429 + Vertical: Scaled 430 + 431 +Style: DragHandle 432 + Element: Normal 433 + Inherit: ItemBox, Active 434 + 435 +Style: ResizeHandle 436 + Element: Normal 437 + Inherit: ItemBox, Normal 438 + Element: Active 439 + Inherit: ItemBox, Active 440 + 441 +Style: Checkmark 442 + Element: Normal 443 + Rect: [419,23][430,34] 444 +// }}} 445 +// {{{ Basic GUI Elements 446 +Style: Button 447 + Inherit: RoundedBox 448 + 449 +Style: BaselineButton 450 + Element: Normal 451 + Rect: [3,3][182,35] 452 + Margin: 6 453 + Vertical: Scaled 454 + Horizontal: Scaled 455 + 456 + Element: Hovered 457 + Rect: [3,43][182,75] 458 + Margin: 6 459 + Vertical: Scaled 460 + Horizontal: Scaled 461 + 462 + Element: Active 463 + Rect: [3,83][182,115] 464 + Margin: 6 465 + Vertical: Scaled 466 + Horizontal: Scaled 467 + 468 + Element: Hovered, Active 469 + Inherit: BaselineButton, Active 470 + 471 + Element: Disabled 472 + Rect: [195,343][374,375] 473 + Margin: 6 474 + Vertical: Scaled 475 + Horizontal: Scaled 476 + 477 +Style: IconButton 478 + Element: Normal 479 + Element: Hovered 480 + Inherit: Highlight 481 + Element: Active 482 + Inherit: Highlight 483 + Element: Hovered,Active 484 + Inherit: Highlight 485 + 486 +Style: IconToggle 487 + Element: Normal 488 + Element: Hovered 489 + Inherit: Highlight 490 + Element: Active 491 + Inherit: Highlight 492 + Layer: Button 493 + Element: Hovered,Active 494 + Inherit: Highlight 495 + Layer: Button 496 + 497 +Style: Tab 498 + Inherit: Button 499 + 500 +Style: PageTab 501 + Element: Normal 502 + Layer: Button 503 + Element: Hovered 504 + Layer: Button 505 + Layer: SubtleGlow 506 + Color Override: #ffffff 507 + Element: Active 508 + Layer: Button 509 + Layer: SubtleGlow 510 + 511 +Style: Listbox 512 + 513 +Style: ListboxItem 514 + Inherit: ItemBox 515 + 516 +Style: StaticListboxItem 517 + Element: Normal 518 + Inherit: ItemBox, Normal 519 + 520 +Style: DropdownList 521 + 522 +Style: DropdownListItem 523 + Element: Normal 524 + Inherit: ItemBox 525 + 526 + Element: Hovered 527 + Inherit: ItemBox, Active 528 + 529 + Element: Active 530 + Inherit: ItemBox, Active 531 + 532 + Element: Active, Hovered 533 + Inherit: ItemBox, Active, Hovered 534 + 535 +Style: Dropdown 536 + Inherit: RoundedBox 537 + 538 +Style: DropdownArrow 539 + Element: Normal 540 + Layer: DownArrow 541 + OX1: +7 542 + OY1: +7 543 + OX2: -7 544 + OY2: -7 545 + 546 +Style: Dialog 547 + Element: Normal 548 + Inherit: Panel 549 + 550 +Style: Tooltip 551 + Element: Normal 552 + Rect: [2,454][181,489] 553 + Margin: 6 554 + 555 + Horizontal: Scaled 556 + Vertical: Scaled 557 + 558 +Style: Textbox 559 + Inherit: InputBox 560 + 561 + Element: Focused 562 + Inherit: InputBox, Active 563 + 564 +Style: HoverTextbox 565 + Element: Normal 566 + Element: Disabled 567 + Element: Disabled, Hovered 568 + Element: Hovered 569 + Inherit: InputBox 570 + Element: Focused 571 + Inherit: InputBox 572 + Element: Hovered, Focused 573 + Inherit: InputBox, Active 574 + 575 +Style: HoverButton 576 + Element: Normal 577 + Element: Disabled 578 + Element: Disabled, Hovered 579 + Element: Active 580 + Inherit: Button, Active 581 + Element: Hovered 582 + Inherit: Button 583 + Element: Hovered, Active 584 + Inherit: Button, Active 585 + 586 +Style: GlowButton 587 + Element: Normal 588 + Inherit: PlainBox 589 + Element: Hovered 590 + Layer: PlainBox 591 + Layer: Highlight 592 + Color Override: #00c0ff 593 + Element: Hovered, Disabled 594 + Inherit: PlainBox 595 + Element: Active 596 + GradientMode: Overlay 597 + Layer: PlainBox 598 + 599 + Add Gradient: 600 + TopLeft : #2228 601 + TopRight: #2228 602 + BotLeft : #00c0ff48 603 + BotRight: #00c0ff48 604 + 605 + GX1: 0 606 + GY1: 0 607 + GX2: 100% 608 + GY2: 100% 609 + Element: Active, Hovered 610 + Layer: GlowButton, Active 611 + Layer: Highlight 612 + Color Override: #00c0ff 613 + 614 +Style: TabButton 615 + Element: Normal 616 + GradientMode: Overlay 617 + Layer: PlainBox 618 + 619 + Add Gradient: 620 + TopLeft : #4448 621 + TopRight: #4448 622 + BotLeft : #2228 623 + BotRight: #2228 624 + 625 + GX1: 1 626 + GY1: 1 627 + GX2: -1 628 + GY2: -1 629 + Element: Hovered 630 + GradientMode: Overlay 631 + Layer: TabButton, Normal 632 + Add Gradient: 633 + TopLeft : #fff0 634 + TopRight: #fff0 635 + BotLeft : #fff3 636 + BotRight: #fff3 637 + 638 + GX1: 1 639 + GY1: 1 640 + GX2: -1 641 + GY2: -1 642 + Element: Active 643 + GradientMode: Overlay 644 + Layer: PlainBox 645 + Color Override: #00c0ff 646 + 647 + Add Gradient: 648 + TopLeft : #2228 649 + TopRight: #2228 650 + BotLeft : #00c0ff48 651 + BotRight: #00c0ff48 652 + 653 + GX1: 1 654 + GY1: 1 655 + GX2: -1 656 + GY2: -1 657 + Element: Active, Hovered 658 + GradientMode: Overlay 659 + Layer: TabButton, Active 660 + Add Gradient: 661 + TopLeft : #fff0 662 + TopRight: #fff0 663 + BotLeft : #fff2 664 + BotRight: #fff2 665 + 666 + GX1: 1 667 + GY1: 1 668 + GX2: -1 669 + GY2: -1 670 + 671 +Style: AccordionHeader 672 + Element: Normal 673 + Inherit: PatternBox 674 + 675 + Element: Hovered 676 + Inherit: PatternBox 677 + Layer: Highlight 678 + 679 + Element: Active 680 + Inherit: PatternBox 681 + 682 + Element: Hovered, Active 683 + Inherit: PatternBox 684 + Layer: Highlight 685 + 686 + Element: Disabled 687 + GradientMode: Overlay 688 + 689 + Add Gradient: 690 + TopLeft : #2221 691 + TopRight: #2221 692 + BotLeft : #4446 693 + BotRight: #4446 694 + 695 + GX1: 0 696 + GY1: 0 697 + GX2: 100% 698 + GY2: -2 699 + 700 + Add Gradient: 701 + TopLeft : #444f 702 + TopRight: #444f 703 + BotLeft : #444f 704 + BotRight: #444f 705 + 706 + GX1: 0 707 + GY1: -2 708 + GX2: 100% 709 + GY2: 100% 710 + 711 +Style: BuildElement 712 + Element: Normal 713 + GradientMode: Overlay 714 + 715 + Add Gradient: 716 + TopLeft : #0000 717 + TopRight: #0000 718 + BotLeft : #2224 719 + BotRight: #2224 720 + 721 + GX1: 0 722 + GY1: 0 723 + GX2: 100% 724 + GY2: -1 725 + 726 + Add Gradient: 727 + TopLeft : #222f 728 + TopRight: #222f 729 + BotLeft : #222f 730 + BotRight: #222f 731 + 732 + GX1: 0 733 + GY1: -1 734 + GX2: 100% 735 + GY2: 100% 736 + 737 + Element: Hovered 738 + GradientMode: Overlay 739 + 740 + Add Gradient: 741 + TopLeft : #222a 742 + TopRight: #222a 743 + BotLeft : #444a 744 + BotRight: #444a 745 + 746 + GX1: 0 747 + GY1: 0 748 + GX2: 100% 749 + GY2: -1 750 + 751 + Add Gradient: 752 + TopLeft : #222f 753 + TopRight: #222f 754 + BotLeft : #222f 755 + BotRight: #222f 756 + 757 + GX1: 0 758 + GY1: -1 759 + GX2: 100% 760 + GY2: 100% 761 + 762 +Style: SpinButton 763 + Inherit: TinyBox 764 + 765 +Style: ContextMenu 766 + 767 +Style: ContextMenuItem 768 + Element: Normal 769 + Inherit: ItemBox 770 + 771 + Element: Hovered 772 + Inherit: ItemBox, Active 773 + 774 + Element: Active 775 + Inherit: ItemBox, Active 776 + 777 + Element: Active, Hovered 778 + Inherit: ItemBox, Active 779 + 780 +Style: Checkbox 781 + Element: Normal 782 + Inherit: ItemBox 783 + 784 + Element: Hovered 785 + Inherit: ItemBox, Hovered 786 + 787 + Element: Active 788 + Layer: ItemBox, Active 789 + 790 + Layer: Checkmark 791 + OX1: 4 792 + OY1: 0 793 + OX2: 100% 794 + OY2: -4 795 + 796 + Element: Active, Hovered 797 + Layer: ItemBox, Active, Hovered 798 + 799 + Layer: Checkmark 800 + OX1: 4 801 + OY1: 0 802 + OX2: 100% 803 + OY2: -4 804 + 805 +Style: Radiobox 806 + Element: Normal 807 + Inherit: ItemBox 808 + 809 + Element: Hovered 810 + Inherit: ItemBox, Hovered 811 + 812 + Element: Active 813 + Layer: ItemBox 814 + 815 + Layer: ItemBox, Active 816 + OX1: 20% 817 + OY1: 20% 818 + OX2: -20% 819 + OY2: -20% 820 + 821 + Element: Active, Hovered 822 + Layer: ItemBox, Hovered 823 + 824 + Layer: ItemBox, Active 825 + OX1: 20% 826 + OY1: 20% 827 + OX2: -20% 828 + OY2: -20% 829 + 830 +Style: ScrollVert 831 + Inherit: Panel 832 + 833 +Style: ScrollVertHandle 834 + Element: Normal 835 + Inherit: StraightBox 836 + 837 + Element: Hovered 838 + Inherit: StraightBox, Hovered 839 + 840 + Element: Active 841 + Inherit: StraightBox, Active 842 + 843 +Style: ScrollHoriz 844 + Inherit: Panel 845 + 846 +Style: ScrollHorizHandle 847 + Element: Normal 848 + Inherit: StraightBox 849 + 850 + Element: Hovered 851 + Inherit: StraightBox, Hovered 852 + 853 + Element: Active 854 + Inherit: StraightBox, Active 855 + 856 +Style: ScrollButton 857 + Inherit: Button 858 + 859 +Style: ScrollUp 860 + Element: Normal 861 + Layer: ScrollButton 862 + Layer: UpArrow 863 + OX1: +4 864 + OY1: +4 865 + OX2: -4 866 + OY2: -4 867 + 868 + Element: Hovered 869 + Layer: ScrollButton, Hovered 870 + Layer: UpArrow 871 + OX1: +4 872 + OY1: +4 873 + OX2: -4 874 + OY2: -4 875 + 876 + Element: Active 877 + Layer: ScrollButton, Active 878 + Layer: UpArrow 879 + OX1: +4 880 + OY1: +4 881 + OX2: -4 882 + OY2: -4 883 + 884 +Style: ScrollDown 885 + Element: Normal 886 + Layer: ScrollButton 887 + Layer: DownArrow 888 + OX1: +4 889 + OY1: +4 890 + OX2: -4 891 + OY2: -4 892 + 893 + Element: Hovered 894 + Layer: ScrollButton, Hovered 895 + Layer: DownArrow 896 + OX1: +4 897 + OY1: +4 898 + OX2: -4 899 + OY2: -4 900 + 901 + Element: Active 902 + Layer: ScrollButton, Active 903 + Layer: DownArrow 904 + OX1: +4 905 + OY1: +4 906 + OX2: -4 907 + OY2: -4 908 + 909 +Style: ScrollLeft 910 + Element: Normal 911 + Layer: ScrollButton 912 + Layer: LeftArrow 913 + OX1: +4 914 + OY1: +4 915 + OX2: -4 916 + OY2: -4 917 + 918 + Element: Hovered 919 + Layer: ScrollButton, Hovered 920 + Layer: LeftArrow 921 + OX1: +4 922 + OY1: +4 923 + OX2: -4 924 + OY2: -4 925 + 926 + Element: Active 927 + Layer: ScrollButton, Active 928 + Layer: LeftArrow 929 + OX1: +4 930 + OY1: +4 931 + OX2: -4 932 + OY2: -4 933 + 934 +Style: ScrollRight 935 + Element: Normal 936 + Layer: ScrollButton 937 + Layer: RightArrow 938 + OX1: +4 939 + OY1: +4 940 + OX2: -4 941 + OY2: -4 942 + 943 + Element: Hovered 944 + Layer: ScrollButton, Hovered 945 + Layer: RightArrow 946 + OX1: +4 947 + OY1: +4 948 + OX2: -4 949 + OY2: -4 950 + 951 + Element: Active 952 + Layer: ScrollButton, Active 953 + Layer: RightArrow 954 + OX1: +4 955 + OY1: +4 956 + OX2: -4 957 + OY2: -4 958 + 959 +Style: DistributionBar 960 + Inherit: StraightBox 961 + 962 +Style: DistributionElement 963 + Element: Normal 964 + GradientMode: Overlay 965 + Add Gradient: 966 + TopLeft : #bbb9 967 + TopRight: #bbb9 968 + BotLeft : #4449 969 + BotRight: #4449 970 + 971 + GX1: 0 972 + GY1: 0 973 + GX2: 100% 974 + GY2: 100% 975 + 976 +Style: ChoiceBox 977 + Inherit: ItemBox 978 + 979 +// }}} 980 +// {{{ Tab Bar 981 +Style: GameTabBar 982 + Element: Normal 983 + Rect: [1,400] [100,424] 984 + Margin: 0 985 + 986 + Vertical: Scaled 987 + Horizontal: Scaled 988 + 989 +Style: GameTab 990 + Element: Normal 991 + Rect: [2,345] [86,371] 992 + Margin: 17, 0 993 + Horizontal: Scaled 994 + 995 + Element: Hovered 996 + Rect: [2,317] [86,343] 997 + Margin: 17, 0 998 + Horizontal: Scaled 999 + 1000 + Element: Pressed 1001 + Inherit: GameTab, Normal 1002 + 1003 + Element: Active 1004 + Rect: [2,372] [86,398] 1005 + AspectMargin: Horizontal 1006 + Margin: 17,0 1007 + Horizontal: Scaled 1008 + 1009 +Style: GameTabClose 1010 + Element: Normal 1011 + Rect: [3,429][17,443] 1012 + 1013 + Element: Hovered 1014 + Rect: [17,429][31,443] 1015 + 1016 + Element: Active 1017 + Rect: [32,429][46,443] 1018 + 1019 + 1020 +Style: GameTabNew 1021 + Element: Normal 1022 + Rect: [138,350][169,369] 1023 + Margin: 9 1024 + Horizontal: Scaled 1025 + 1026 + Element: Hovered 1027 + Rect: [106,350][136,369] 1028 + Margin: 9 1029 + Horizontal: Scaled 1030 + 1031 + Element: Active 1032 + Rect: [138,373][169,392] 1033 + Margin: 9 1034 + Horizontal: Scaled 1035 + 1036 +Style: HomeIcon 1037 + Element: Normal 1038 + Rect: [105, 300][136, 319] 1039 + 1040 + Element: Hovered 1041 + Rect: [138, 300][169, 319] 1042 + 1043 + Element: Active 1044 + Rect: [105, 324][136,343] 1045 + 1046 +Style: GoIcon 1047 + Element: Normal 1048 + Rect: [105,253][136,272] 1049 + 1050 + Element: Hovered 1051 + Rect: [138,253][169,272] 1052 + 1053 + Element: Active 1054 + Rect: [105,277][136,296] 1055 + 1056 +Style: GalaxyIcon 1057 + Element: Normal 1058 + Rect: [132,428] [157,451] 1059 + 1060 + Vertical: Uniform 1061 + Horizontal: Uniform 1062 + 1063 +Style: PlanetIcon 1064 + Inherit: GalaxyIcon 1065 + 1066 +Style: SupportIcon 1067 + Inherit: GalaxyIcon 1068 + 1069 +Style: SystemIcon 1070 + Inherit: GalaxyIcon 1071 + 1072 +Style: DesignsIcon 1073 + Element: Normal 1074 + Rect: [94,429] [119,451] 1075 + 1076 + Vertical: Uniform 1077 + Horizontal: Uniform 1078 + 1079 +Style: ResearchIcon 1080 + Element: Normal 1081 + Rect: [62,428] [87,451] 1082 + 1083 + Vertical: Uniform 1084 + Horizontal: Uniform 1085 + 1086 +Style: GlobalBar 1087 + Element: Normal 1088 + Rect: [107,374] [136,425] 1089 + Margin: 4 1090 + 1091 + Vertical: Scaled 1092 + Horizontal: Scaled 1093 + 1094 + Add Gradient: 1095 + TopLeft : #1b1b1b44 1096 + TopRight: #1b1b1b44 1097 + BotLeft : #00000044 1098 + BotRight: #00000044 1099 + 1100 + GX1: 1 1101 + GY1: 1 1102 + GX2: -1 1103 + GY2: -1 1104 + 1105 +Style: GoDialog 1106 + Element: Normal 1107 + Inherit: Panel 1108 + 1109 +Style: GoItem 1110 + Element: Normal 1111 + Inherit: ItemBox 1112 + 1113 + Element: Active 1114 + Inherit: ItemBox, Active 1115 + 1116 + Element: Hovered 1117 + Inherit: ItemBox, Hovered 1118 + 1119 + Element: Active, Hovered 1120 + Inherit: ItemBox, Active, Hovered 1121 +// }}} 1122 +// {{{ Global Bar 1123 +Style: BudgetProgress 1124 + Inherit: ProgressBarBG 1125 + 1126 +Style: BudgetProgressBar 1127 + Element: Normal 1128 + Inherit: ProgressBar 1129 + 1130 +Style: ResearchProgress 1131 + Inherit: ProgressBarBG 1132 + 1133 +Style: ResearchProgressBar 1134 + Element: Normal 1135 + Inherit: ProgressBar 1136 + 1137 +Style: Notification 1138 + Element: Normal 1139 + Inherit: PlainBox 1140 + 1141 +Style: TimeDisplay 1142 + Element: Normal 1143 + Inherit: PlainBox 1144 + 1145 + Layer: SmallHexPattern 1146 + OX1: +3 1147 + OY1: +3 1148 + OX2: -3 1149 + OY2: -3 1150 +// }}} 1151 +// {{{ AI Empire Tab 1152 +Style: AIEmpireBG 1153 + Element: Normal 1154 + Inherit: HexPattern 1155 + 1156 + Add Gradient: 1157 + TopLeft : #333f 1158 + TopRight: #333f 1159 + BotLeft : #000e 1160 + BotRight: #000e 1161 + 1162 + GX1: 0% 1163 + GY1: 0% 1164 + GX2: 100% 1165 + GY2: 100% 1166 + 1167 +Style: ResearchField 1168 + Inherit: HighlightPanel 1169 +// }}} 1170 +// {{{ Research Tab 1171 +Style: ResearchBG 1172 + Element: Normal 1173 + Inherit: HexPattern 1174 + 1175 + Add Gradient: 1176 + TopLeft : #212e 1177 + TopRight: #212e 1178 + BotLeft : #000e 1179 + BotRight: #000e 1180 + 1181 + GX1: 0% 1182 + GY1: 0% 1183 + GX2: 100% 1184 + GY2: 100% 1185 + 1186 +Style: ResearchField 1187 + Inherit: HighlightPanel 1188 +// }}} 1189 +// {{{ Design Tabs 1190 +Style: DesignOverviewBG 1191 + Element: Normal 1192 + Inherit: HexPattern 1193 + 1194 + Add Gradient: 1195 + TopLeft : #112e 1196 + TopRight: #112e 1197 + BotLeft : #000e 1198 + BotRight: #000e 1199 + 1200 + GX1: 0% 1201 + GY1: 0% 1202 + GX2: 100% 1203 + GY2: 100% 1204 + 1205 +Style: DesignClassHeader 1206 + Element: Normal 1207 + Inherit: PanelTitle 1208 + 1209 +Style: DesignClass 1210 + Element: Normal 1211 + Inherit: Panel 1212 + 1213 +Style: DesignBorder 1214 + Element: Normal 1215 + Inherit: Panel 1216 + Element: Hovered 1217 + Inherit: Panel 1218 + 1219 + Add Gradient: 1220 + TopLeft : #1112 1221 + TopRight: #1112 1222 + BotLeft : #aaa2 1223 + BotRight: #aaa2 1224 + 1225 + GX1: +4 1226 + GY1: +4 1227 + GX2: -4 1228 + GY2: -4 1229 + 1230 + Element: Active 1231 + Inherit: Panel 1232 + 1233 + Add Gradient: 1234 + TopLeft : #fff3 1235 + TopRight: #fff3 1236 + BotLeft : #fff3 1237 + BotRight: #fff3 1238 + 1239 + GX1: 0 1240 + GY1: 0 1241 + GX2: 100% 1242 + GY2: +4 1243 + 1244 + Add Gradient: 1245 + TopLeft : #fff3 1246 + TopRight: #fff3 1247 + BotLeft : #fff3 1248 + BotRight: #fff3 1249 + 1250 + GX1: 0 1251 + GY1: -4 1252 + GX2: 100% 1253 + GY2: 100% 1254 + 1255 + Add Gradient: 1256 + TopLeft : #fff3 1257 + TopRight: #fff3 1258 + BotLeft : #fff3 1259 + BotRight: #fff3 1260 + 1261 + GX1: 0 1262 + GY1: +4 1263 + GX2: +4 1264 + GY2: -4 1265 + 1266 + Add Gradient: 1267 + TopLeft : #fff3 1268 + TopRight: #fff3 1269 + BotLeft : #fff3 1270 + BotRight: #fff3 1271 + 1272 + GX1: -4 1273 + GY1: +4 1274 + GX2: 100% 1275 + GY2: -4 1276 + 1277 + Element: Hovered, Active 1278 + Inherit: DesignBorder, Active 1279 + 1280 + Add Gradient: 1281 + TopLeft : #1112 1282 + TopRight: #1112 1283 + BotLeft : #aaa2 1284 + BotRight: #aaa2 1285 + 1286 + GX1: +4 1287 + GY1: +4 1288 + GX2: -4 1289 + GY2: -4 1290 + 1291 +Style: DesignGradient 1292 + Element: Normal 1293 + GradientMode: Overlay 1294 + Add Gradient: 1295 + TopLeft : #aaa3 1296 + TopRight: #8883 1297 + BotLeft : #8883 1298 + BotRight: #2223 1299 + 1300 + GX1: 0 1301 + GY1: 0 1302 + GX2: 100% 1303 + GY2: 100% 1304 + 1305 +Style: DesignSummary 1306 + Element: Normal 1307 + Layer: DesignBorder, Normal 1308 + Color Override: #fff 1309 + Layer: DesignGradient 1310 + OX1: +4 1311 + OY1: +4 1312 + OX2: -4 1313 + OY2: -4 1314 + Layer: PlainBox 1315 + OX1: 2 1316 + OY1: -34 1317 + OX2: -2 1318 + OY2: -2 1319 + 1320 +Style: DesignEditorBG 1321 + Inherit: DesignOverviewBG 1322 + 1323 +Style: DesignNavigationClass 1324 + Inherit: LightPanel 1325 + 1326 +Style: DesignNavigationIcon 1327 + Inherit: RoundedBox 1328 + 1329 +Style: ModuleButton 1330 + Inherit: Button 1331 +// }}} 1332 +// {{{ Diplomacy Tabs 1333 +Style: DiplomacyBG 1334 + Element: Normal 1335 + Inherit: HexPattern 1336 + 1337 + Add Gradient: 1338 + TopLeft : #121e 1339 + TopRight: #121e 1340 + BotLeft : #000e 1341 + BotRight: #000e 1342 + 1343 + GX1: 0% 1344 + GY1: 0% 1345 + GX2: 100% 1346 + GY2: 100% 1347 + 1348 +Style: EmpireBox 1349 + Element: Normal 1350 + Layer: RoundedBox 1351 + 1352 + Layer: Panel 1353 + Color Override: #fff 1354 + OX1: +4 1355 + OY1: +4 1356 + OX2: -4 1357 + OY2: -4 1358 + 1359 +Style: PlayerEmpireBox 1360 + Inherit: EmpireBox 1361 + 1362 +Style: DelegationBox 1363 + Element: Normal 1364 + Layer: RoundedBox 1365 + Color Override: #fff 1366 + OX1: 0 1367 + OY1: 4 1368 + OX2: 100% 1369 + OY2: -4 1370 + 1371 + Layer: RoundedBox 1372 + OX1: 0 1373 + OY1: 0 1374 + OX2: -46 1375 + OY2: 100% 1376 + 1377 +Style: VotingBox 1378 + Element: Normal 1379 + Inherit: PlainBox 1380 + 1381 +Style: VoteTotal 1382 + Element: Normal 1383 + Layer: PlainBox 1384 + 1385 +Style: InfluenceVoteBox 1386 + Inherit: PatternBox 1387 + 1388 +Style: TreatyBox 1389 + Inherit: PatternBox 1390 + 1391 +Style: InfluenceEffectBox 1392 + Inherit: PatternBox 1393 +// }}} 1394 +// {{{ Planet Tab 1395 +Style: QueueBackground 1396 + Inherit: SmallHexPattern 1397 + 1398 +Style: ConstructionBox 1399 + Inherit: Panel 1400 + 1401 +Style: PlanetBox 1402 + Element: Normal 1403 + Layer: RoundedBox 1404 + Color Override: #ffffff80 1405 + 1406 +Style: PlanetElement 1407 + Element: Normal 1408 + Layer: Panel 1409 + OX1: 0 1410 + OY1: 0 1411 + OX2: 100% 1412 + OY2: 100% 1413 + 1414 + Layer: PlainBox 1415 + OX1: +4 1416 + OY1: +4 1417 + OX2: -4 1418 + OY2: -4 1419 +// }}} 1420 +// {{{ System Tab 1421 +Style: SystemListBG 1422 + Element: Normal 1423 + Inherit: HexPattern 1424 + 1425 + Add Gradient: 1426 + TopLeft : #210e 1427 + TopRight: #210e 1428 + BotLeft : #000e 1429 + BotRight: #000e 1430 + 1431 + GX1: 0% 1432 + GY1: 0% 1433 + GX2: 100% 1434 + GY2: 100% 1435 + 1436 +Style: SystemPanel 1437 + Element: Normal 1438 + Layer: Panel 1439 + Color Override: #fff 1440 + 1441 + Layer: PanelTitle 1442 + OX1: +1 1443 + OY1: +1 1444 + OX2: -2 1445 + OY2: +31 1446 + 1447 +Style: PlanetBar 1448 + Element: Normal 1449 + Inherit: PlainBox 1450 + 1451 +// }}} 1452 +// {{{ Support Tab 1453 +Style: GroupPanel 1454 + Inherit: Panel 1455 + 1456 +Style: GroupSupportClass 1457 + Inherit: PatternBox 1458 +// }}} 1459 +// {{{ Wiki Tab 1460 +Style: WikiBG 1461 + Element: Normal 1462 + Inherit: HexPattern 1463 + 1464 + Add Gradient: 1465 + TopLeft : #211e 1466 + TopRight: #211e 1467 + BotLeft : #000e 1468 + BotRight: #000e 1469 + 1470 + GX1: 0% 1471 + GY1: 0% 1472 + GX2: 100% 1473 + GY2: 100% 1474 +// }}} 1475 +// {{{ Popups 1476 +Style: PopupBG 1477 + Element: Normal 1478 + Layer: Panel 1479 + Color Override: #fff 1480 + 1481 + Layer: SubTitle 1482 + OX1: +2 1483 + OY1: +1 1484 + OX2: -3 1485 + OY2: 25 1486 + 1487 + Layer: BG3D 1488 + OX1: +3 1489 + OY1: +24 1490 + OX2: -4 1491 + OY2: -35 1492 + 1493 +Style: ShipPopupBG 1494 + Element: Normal 1495 + Layer: Panel 1496 + Color Override: #fff 1497 + 1498 + Layer: SubTitle 1499 + OX1: +2 1500 + OY1: +1 1501 + OX2: -3 1502 + OY2: 25 1503 + 1504 + Layer: BG3D 1505 + OX1: +3 1506 + OY1: +24 1507 + OX2: -4 1508 + OY2: -80 1509 + 1510 +Style: GenericPopupBG 1511 + Element: Normal 1512 + Layer: Panel 1513 + Color Override: #fff 1514 + 1515 + Layer: SubTitle 1516 + OX1: +2 1517 + OY1: +1 1518 + OX2: -3 1519 + OY2: 25 1520 + 1521 + Layer: BG3D 1522 + OX1: +3 1523 + OY1: +24 1524 + OX2: -4 1525 + OY2: -4 1526 + 1527 +Style: SelectablePopup 1528 + Inherit: PopupBG 1529 + 1530 +Style: ManageButton 1531 + Element: Normal 1532 + Rect: [203,644][280,664] 1533 +// }}} 1534 +// {{{ Info Bar 1535 +Style: InfoBar 1536 + Element: Normal 1537 + Inherit: PlainBox 1538 + 1539 +Style: InfoBarPlain 1540 + Element: Normal 1541 + Layer: Panel 1542 + Color Override: #fff 1543 + OX1: 0 1544 + OY1: 0 1545 + OX2: 100% 1546 + OY2: 130% 1547 +// }}} 1548 +// {{{ Main Menu 1549 +Style: MapSelectorItem 1550 + Element: Normal 1551 + Inherit: Panel 1552 + 1553 + Element: Hovered 1554 + Inherit: Panel, Active 1555 + 1556 +Style: EmpireSetupItem 1557 + Element: Normal 1558 + Layer: Panel 1559 + OX1: 0 1560 + OY1: 0 1561 + OX2: 100% 1562 + OY2: 100% 1563 + 1564 +Style: GalaxySetupItem 1565 + Element: Normal 1566 + Layer: PatternBox 1567 + OX1: 0 1568 + OY1: 0 1569 + OX2: 100% 1570 + OY2: 100% 1571 + Layer: Panel 1572 + OX1: 0 1573 + OY1: 0 1574 + OX2: 100% 1575 + OY2: 40 1576 + 1577 +Style: MainMenuPanel 1578 + Element: Normal 1579 + Layer: Panel 1580 + Color Override: #ffffffff 1581 + 1582 +Style: MainMenuDescPanel 1583 + Inherit: MainMenuPanel 1584 + 1585 +Style: MainMenuItem 1586 + Element: Normal 1587 + Element: Hovered 1588 + Inherit: ItemBox, Active 1589 + Element: Active 1590 + Inherit: PlainBox, Normal 1591 + 1592 + Add Gradient: 1593 + TopLeft : #aa444440 1594 + TopRight: #aa444440 1595 + BotLeft : #aa444440 1596 + BotRight: #aa444440 1597 + 1598 + GX1: 0 1599 + GY1: 0 1600 + GX2: 100% 1601 + GY2: 100% 1602 + 1603 + Element: Hovered, Active 1604 + Inherit: ItemBox, Active 1605 +// }}}
Added logo.png.
cannot compute difference between binary files
Added modinfo.txt.
1 + Name: AI Empire 2 + Compatibility: 200 3 + Description: << 4 + Forces the AI on for player Empires, allows configuration of player AI (difficulty, cheats) at the new game screen, and allows runtime configuration of what roles the AI manages. 5 + 6 + [h1]Current version: 0.4.3[/h1] 7 + 8 + [h1][b]Defects[/b][/h1] 9 + - I'm having a lot of trouble preventing all the construction that the AI wants to do. In particular it buys a factory right away and it'll fill up fleets for you. 10 + - The tab UI hasn't gotten a lot of love yet. 11 + - This probably doesn't save/load well. 12 + - This probably doesn't work with multiplayer well. 13 + 14 + [h1][b]Changelog[/b][/h1] 15 + - 0.4.3 - clean up the UI a bit. controllable: scuttling. 16 + - 0.4.2 - controllable: artifact use 17 + - 0.4.1 - controls are now also in AI's race and FTL components 18 + - 0.4.0 - controllable: construction. 'diplomacy' now includes treaties and war/peace declarations. 19 + - controllable: scouting and also (Anomaly Resolution) whether scouts will decide for themselves which option to pick after scanning an anomaly. 20 + - 0.3.3 - minor fixes, depend on Missing Expansion rather than directly on the community patch 21 + - 0.3.0 - added 'prevent achievements' button. Doesn't turn cheats on, but flags game as having ever had cheats on. 22 + - 0.2.0 - first version with AI controls 23 + - 'AI Empire' tab added 24 + - controllable: diplomacy, colonization, remnant hunting, research 25 + also, defense of systems and attacks on enemy systems when at war 26 + - all disabled initially 27 + - 0.1.0 - initial version, AI is on for player but you have no control over it 28 + >> 29 + Derives From: Missing Expansion 30 + Override: scripts/menu/new_game.as 31 + Override: scripts/server/empire_ai/EmpireAI.as 32 + Override: scripts/server/empire_ai/weasel 33 + Override: scripts/server/cheats.as 34 + Override: data/skin definitions/default.txt
Added scripts/gui/tabs/AIEmpireTab.as.
1 +import tabs.Tab; 2 +import elements.GuiButton; 3 +import elements.GuiPanel; 4 +import elements.GuiMarkupText; 5 +import icons; 6 +from tabs.tabbar import newTab, switchToTab; 7 + 8 +const Color colorForbidden = Color(0xaaaaaaff); 9 +const Color colorAllowed = colors::Energy; 10 + 11 +Tab@ createAIEmpireTab() { 12 + return AIEmpireTab(); 13 +} 14 + 15 +void init() { 16 + Tab@ tab = createAIEmpireTab(); 17 + newTab(tab); 18 + cheatCommandAI(playerEmpire, "forbid all"); 19 +} 20 + 21 +class AIEmpireTab : Tab { 22 + GuiPanel@ panel; 23 + GuiButton@ forbidDiplomacyButton; 24 + GuiMarkupText@ forbidDiplomacyText; 25 + bool forbidDiplomacy; 26 + GuiButton@ forbidColonizationButton; 27 + GuiMarkupText@ forbidColonizationText; 28 + bool forbidColonization; 29 + GuiButton@ forbidCreepingButton; 30 + GuiMarkupText@ forbidCreepingText; 31 + bool forbidCreeping; 32 + GuiButton@ forbidResearchButton; 33 + GuiMarkupText@ forbidResearchText; 34 + bool forbidResearch; 35 + GuiButton@ forbidDefenseButton; 36 + GuiMarkupText@ forbidDefenseText; 37 + bool forbidDefense; 38 + GuiButton@ forbidAttackButton; 39 + GuiMarkupText@ forbidAttackText; 40 + bool forbidAttack; 41 + GuiButton@ forbidConstructionButton; 42 + GuiMarkupText@ forbidConstructionText; 43 + bool forbidConstruction; 44 + GuiButton@ forbidScoutingButton; 45 + GuiMarkupText@ forbidScoutingText; 46 + bool forbidScouting; 47 + GuiButton@ forbidAnomalyChoiceButton; 48 + GuiMarkupText@ forbidAnomalyChoiceText; 49 + bool forbidAnomalyChoice; 50 + GuiButton@ forbidArtifactButton; 51 + GuiMarkupText@ forbidArtifactText; 52 + bool forbidArtifact; 53 + GuiButton@ forbidScuttleButton; 54 + GuiMarkupText@ forbidScuttleText; 55 + bool forbidScuttle; 56 + GuiButton@ preventAchievementsButton; 57 + GuiMarkupText@ preventAchievementsText; 58 + bool preventAchievements; 59 + 60 + GuiButton@ enableAIButton; 61 + GuiMarkupText@ enableAIText; 62 + GuiButton@ disableAIButton; 63 + GuiMarkupText@ disableAIText; 64 + 65 + AIEmpireTab() { 66 + super(); 67 + title = "AI Empire"; 68 + 69 + forbidDiplomacy = true; 70 + forbidColonization = true; 71 + forbidCreeping = true; 72 + forbidResearch = true; 73 + forbidDefense = true; 74 + forbidAttack = true; 75 + forbidConstruction = true; 76 + forbidScouting = true; 77 + forbidAnomalyChoice = true; 78 + forbidArtifact = true; 79 + forbidScuttle = true; 80 + preventAchievements = false; 81 + 82 + @panel = GuiPanel(this, Alignment()); 83 + 84 + @forbidDiplomacyButton = GuiButton(panel, recti_area(15,50, 120,25)); 85 + @forbidDiplomacyText = GuiMarkupText(forbidDiplomacyButton, Alignment(Left, Top, Right, Bottom)); 86 + forbidDiplomacyText.text = "[center]Diplomacy[/center]"; 87 + forbidDiplomacyButton.color = colorForbidden; 88 + 89 + @preventAchievementsButton = GuiButton(panel, recti_area(0,370, 200,25)); 90 + @preventAchievementsText = GuiMarkupText(preventAchievementsButton, Alignment(Left, Top, Right, Bottom)); 91 + preventAchievementsText.text = "[center]Prevent Achievements[/center]"; 92 + preventAchievementsButton.color = colorForbidden; 93 + 94 + @forbidColonizationButton = GuiButton(panel, recti_area(15,80, 120,25)); 95 + @forbidColonizationText = GuiMarkupText(forbidColonizationButton, Alignment(Left, Top, Right, Bottom)); 96 + forbidColonizationText.text = "[center]Colonization[/center]"; 97 + forbidColonizationButton.color = colorForbidden; 98 + 99 + @forbidCreepingButton = GuiButton(panel, recti_area(15,110, 120,25)); 100 + @forbidCreepingText = GuiMarkupText(forbidCreepingButton, Alignment(Left, Top, Right, Bottom)); 101 + forbidCreepingText.text = "[center]Remnant Hunting[/center]"; 102 + forbidCreepingButton.color = colorForbidden; 103 + 104 + @forbidResearchButton = GuiButton(panel, recti_area(15,140, 120,25)); 105 + @forbidResearchText = GuiMarkupText(forbidResearchButton, Alignment(Left, Top, Right, Bottom)); 106 + forbidResearchText.text = "[center]Research[/center]"; 107 + forbidResearchButton.color = colorForbidden; 108 + 109 + @forbidDefenseButton = GuiButton(panel, recti_area(15,230, 120,25)); 110 + @forbidDefenseText = GuiMarkupText(forbidDefenseButton, Alignment(Left, Top, Right, Bottom)); 111 + forbidDefenseText.text = "[center]Wartime Defense[/center]"; 112 + forbidDefenseButton.color = colorForbidden; 113 + 114 + @forbidAttackButton = GuiButton(panel, recti_area(15,200, 120,25)); 115 + @forbidAttackText = GuiMarkupText(forbidAttackButton, Alignment(Left, Top, Right, Bottom)); 116 + forbidAttackText.text = "[center]Wartime Offense[/center]"; 117 + forbidAttackButton.color = colorForbidden; 118 + 119 + @forbidConstructionButton = GuiButton(panel, recti_area(15+200,50, 120,25)); 120 + @forbidConstructionText = GuiMarkupText(forbidConstructionButton, Alignment(Left, Top, Right, Bottom)); 121 + forbidConstructionText.text = "[center]Construction[/center]"; 122 + forbidConstructionButton.color = colorForbidden; 123 + 124 + @forbidScoutingButton = GuiButton(panel, recti_area(15,170, 120,25)); 125 + @forbidScoutingText = GuiMarkupText(forbidScoutingButton, Alignment(Left, Top, Right, Bottom)); 126 + forbidScoutingText.text = "[center]Scouting[/center]"; 127 + forbidScoutingButton.color = colorForbidden; 128 + 129 + @forbidAnomalyChoiceButton = GuiButton(panel, recti_area(15,260, 200,25)); 130 + @forbidAnomalyChoiceText = GuiMarkupText(forbidAnomalyChoiceButton, Alignment(Left, Top, Right, Bottom)); 131 + forbidAnomalyChoiceText.text = "[center]Anomaly Resolution[/center]"; 132 + forbidAnomalyChoiceButton.color = colorForbidden; 133 + 134 + @forbidArtifactButton = GuiButton(panel, recti_area(15,290, 120,25)); 135 + @forbidArtifactText = GuiMarkupText(forbidArtifactButton, Alignment(Left, Top, Right, Bottom)); 136 + forbidArtifactText.text = "[center]Artifact Use[/center]"; 137 + forbidArtifactButton.color = colorForbidden; 138 + 139 + @forbidScuttleButton = GuiButton(panel, recti_area(15,320, 120,25)); 140 + @forbidScuttleText = GuiMarkupText(forbidScuttleButton, Alignment(Left, Top, Right, Bottom)); 141 + forbidScuttleText.text = "[center]Scuttling[/center]"; 142 + forbidScuttleButton.color = colorForbidden; 143 + 144 + @enableAIButton = GuiButton(panel, recti_area(0,0, 60,25)); 145 + @enableAIText = GuiMarkupText(enableAIButton, Alignment(Left, Top, Right, Bottom)); 146 + enableAIText.text = "[center]All on[/center]"; 147 + enableAIButton.color = colorAllowed; 148 + 149 + @disableAIButton = GuiButton(panel, recti_area(70,0, 60,25)); 150 + @disableAIText = GuiMarkupText(disableAIButton, Alignment(Left, Top, Right, Bottom)); 151 + disableAIText.text = "[center]All off[/center]"; 152 + disableAIButton.color = colorForbidden; 153 + } 154 + 155 + void tick(double time) override { 156 + } 157 + 158 + bool onGuiEvent(const GuiEvent& event) { 159 + if (event.type == GUI_Clicked) { 160 + if (event.caller is enableAIButton) { 161 + cheatCommandAI(playerEmpire, "allow all"); 162 + setAll(colorAllowed, false); 163 + return true; 164 + } 165 + else if (event.caller is disableAIButton) { 166 + cheatCommandAI(playerEmpire, "forbid all"); 167 + setAll(colorForbidden, true); 168 + return true; 169 + } 170 + else if (event.caller is forbidDiplomacyButton) { 171 + toggle(forbidDiplomacyButton, forbidDiplomacy, "Diplomacy"); 172 + return true; 173 + } 174 + else if (event.caller is forbidColonizationButton) { 175 + toggle(forbidColonizationButton, forbidColonization, "Colonization"); 176 + return true; 177 + } 178 + else if (event.caller is forbidCreepingButton) { 179 + toggle(forbidCreepingButton, forbidCreeping, "Creeping"); 180 + return true; 181 + } 182 + else if (event.caller is forbidResearchButton) { 183 + toggle(forbidResearchButton, forbidResearch, "Research"); 184 + return true; 185 + } 186 + else if (event.caller is forbidDefenseButton) { 187 + toggle(forbidDefenseButton, forbidDefense, "Defense"); 188 + return true; 189 + } 190 + else if (event.caller is forbidAttackButton) { 191 + toggle(forbidAttackButton, forbidAttack, "Attack"); 192 + return true; 193 + } 194 + else if (event.caller is forbidConstructionButton) { 195 + toggle(forbidConstructionButton, forbidConstruction, "Construction"); 196 + return true; 197 + } 198 + else if (event.caller is forbidScoutingButton) { 199 + toggle(forbidScoutingButton, forbidScouting, "Scouting"); 200 + return true; 201 + } 202 + else if (event.caller is forbidAnomalyChoiceButton) { 203 + toggle(forbidAnomalyChoiceButton, forbidAnomalyChoice, "AnomalyChoice"); 204 + return true; 205 + } 206 + else if (event.caller is forbidArtifactButton) { 207 + toggle(forbidArtifactButton, forbidArtifact, "Artifact"); 208 + return true; 209 + } 210 + else if (event.caller is forbidScuttleButton) { 211 + toggle(forbidScuttleButton, forbidScuttle, "Scuttle"); 212 + return true; 213 + } 214 + else if (event.caller is preventAchievementsButton) { 215 + cheatCommandAI(playerEmpire, "no achievements"); 216 + preventAchievementsButton.color = colorAllowed; 217 + preventAchievements = true; 218 + return true; 219 + } 220 + } 221 + return BaseGuiElement::onGuiEvent(event); 222 + } 223 + 224 + void toggle (GuiButton& btn, bool& flag, string cmd) { 225 + if (flag) { 226 + btn.color = colorAllowed; 227 + cheatCommandAI(playerEmpire, "allow " + cmd); 228 + flag = false; 229 + } else { 230 + btn.color = colorForbidden; 231 + cheatCommandAI(playerEmpire, "forbid " + cmd); 232 + flag = true; 233 + } 234 + } 235 + 236 + void setAll(Color color, bool b) { 237 + forbidDiplomacy = b; 238 + forbidColonization = b; 239 + forbidCreeping = b; 240 + forbidResearch = b; 241 + forbidDefense = b; 242 + forbidAttack = b; 243 + forbidConstruction = b; 244 + forbidScouting = b; 245 + forbidAnomalyChoice = b; 246 + forbidArtifact = b; 247 + forbidScuttle = b; 248 + forbidDiplomacyButton.color = color; 249 + forbidColonizationButton.color = color; 250 + forbidCreepingButton.color = color; 251 + forbidResearchButton.color = color; 252 + forbidDefenseButton.color = color; 253 + forbidAttackButton.color = color; 254 + forbidConstructionButton.color = color; 255 + forbidScoutingButton.color = color; 256 + forbidAnomalyChoiceButton.color = color; 257 + forbidArtifactButton.color = color; 258 + forbidScuttleButton.color = color; 259 + } 260 + 261 + void draw() { 262 + skin.draw(SS_AIEmpireBG, SF_Normal, AbsolutePosition); 263 + Tab::draw(); 264 + } 265 +} 266 +
Added scripts/menu/new_game.as.
1 +import menus; 2 +import elements.BaseGuiElement; 3 +import elements.GuiButton; 4 +import elements.GuiPanel; 5 +import elements.GuiOverlay; 6 +import elements.GuiSprite; 7 +import elements.GuiText; 8 +import elements.GuiTextbox; 9 +import elements.GuiSpinbox; 10 +import elements.GuiCheckbox; 11 +import elements.GuiDropdown; 12 +import elements.GuiContextMenu; 13 +import elements.GuiIconGrid; 14 +import elements.GuiEmpire; 15 +import elements.GuiMarkupText; 16 +import elements.MarkupTooltip; 17 +import elements.GuiBackgroundPanel; 18 +import dialogs.SaveDialog; 19 +import dialogs.LoadDialog; 20 +import dialogs.MessageDialog; 21 +import dialogs.QuestionDialog; 22 +import util.settings_page; 23 +import empire_data; 24 +import traits; 25 +import icons; 26 +from util.draw_model import drawLitModel; 27 + 28 +import void showMultiplayer() from "multiplayer_menu"; 29 + 30 +from maps import Map, maps, mapCount, getMap; 31 + 32 +import settings.game_settings; 33 +import util.game_options; 34 + 35 +const int EMPIRE_SETUP_HEIGHT = 96; 36 +const int GALAXY_SETUP_HEIGHT = 200; 37 + 38 +const int REC_MAX_PEREMP = 25; 39 +const int REC_MAX_OPTIMAL = 150; 40 +const int REC_MAX_BAD = 400; 41 +const int REC_MAX_OHGOD = 1000; 42 + 43 +const array<Color> QDIFF_COLORS = {Color(0x00ff00ff), Color(0x1197e0ff), Color(0xff0000ff)}; 44 +const array<string> QDIFF_NAMES = {locale::AI_DIFF_EASY, locale::AI_DIFF_NORMAL, locale::AI_DIFF_HARD}; 45 +const array<string> QDIFF_DESC = {locale::AI_DIFF_EASY_DESC, locale::AI_DIFF_NORMAL_DESC, locale::AI_DIFF_HARD_DESC}; 46 +const array<Sprite> QDIFF_ICONS = {Sprite(spritesheet::AIDifficulty, 0), Sprite(spritesheet::AIDifficulty, 1), Sprite(spritesheet::AIDifficulty, 2)}; 47 + 48 +NameGenerator empireNames; 49 +bool empireNamesInitialized = false; 50 + 51 +class ConfirmStart : QuestionDialogCallback { 52 + void questionCallback(QuestionDialog@ dialog, int answer) { 53 + if(answer == QA_Yes) { 54 + new_game.start(); 55 + hideNewGame(true); 56 + } 57 + } 58 +}; 59 + 60 +class NewGame : BaseGuiElement { 61 + GameSettings settings; 62 + 63 + GuiBackgroundPanel@ empireBG; 64 + GuiBackgroundPanel@ gameBG; 65 + GuiBackgroundPanel@ chatBG; 66 + 67 + GuiButton@ backButton; 68 + GuiButton@ inviteButton; 69 + GuiButton@ playButton; 70 + 71 + EmpirePortraitCreation portraits; 72 + 73 + int nextEmpNum = 1; 74 + GuiPanel@ empirePanel; 75 + EmpireSetup@[] empires; 76 + GuiButton@ addAIButton; 77 + 78 + GuiSkinElement@ gameHeader; 79 + GuiButton@ mapsButton; 80 + array<GuiButton@> settingsButtons; 81 + array<GuiPanel@> settingsPanels; 82 + GuiButton@ resetButton; 83 + 84 + GuiPanel@ galaxyPanel; 85 + GalaxySetup@[] galaxies; 86 + GuiButton@ addGalaxyButton; 87 + 88 + GuiPanel@ mapPanel; 89 + GuiText@ mapHeader; 90 + GuiListbox@ mapList; 91 + 92 + GuiPanel@ chatPanel; 93 + GuiMarkupText@ chatLog; 94 + GuiTextbox@ chatBox; 95 + 96 + bool animating = false; 97 + bool hide = false; 98 + bool fromMP = false; 99 + bool choosingMap = false; 100 + 101 + string chatMessages; 102 + 103 + NewGame() { 104 + super(null, recti()); 105 + 106 + @empireBG = GuiBackgroundPanel(this, Alignment( 107 + Left+0.05f, Top+0.1f, Left+0.5f-6, Bottom-0.1f)); 108 + empireBG.title = locale::MENU_EMPIRES; 109 + empireBG.titleColor = Color(0x00ffe9ff); 110 + 111 + @gameBG = GuiBackgroundPanel(this, Alignment( 112 + Left+0.5f+6, Top+0.1f, Left+0.95f, Bottom-0.1f)); 113 + 114 + @gameHeader = GuiSkinElement(gameBG, Alignment(Left+1, Top+1, Right-2, Top+41), SS_FullTitle); 115 + 116 + @mapsButton = GuiButton(gameHeader, Alignment(Left, Top+1, Width=200, Height=38)); 117 + mapsButton.text = locale::MENU_GALAXIES; 118 + mapsButton.buttonIcon = Sprite(material::SystemUnderAttack); 119 + mapsButton.toggleButton = true; 120 + mapsButton.font = FT_Medium; 121 + mapsButton.pressed = true; 122 + mapsButton.style = SS_TabButton; 123 + 124 + @chatBG = GuiBackgroundPanel(this, Alignment( 125 + Left+0.05f, Bottom-0.1f-250, Left+0.5f-6, Bottom-0.1f)); 126 + chatBG.title = locale::CHAT; 127 + chatBG.titleColor = Color(0xff8000ff); 128 + chatBG.visible = false; 129 + 130 + //Empire list 131 + @empirePanel = GuiPanel(empireBG, 132 + Alignment(Left, Top+34, Right, Bottom-4)); 133 + 134 + //Game settings 135 + for(uint i = 0, cnt = GAME_SETTINGS_PAGES.length; i < cnt; ++i) { 136 + auto@ panel = GuiPanel(gameBG, 137 + Alignment(Left, Top+46, Right, Bottom-40)); 138 + panel.visible = false; 139 + settingsPanels.insertLast(panel); 140 + 141 + auto@ page = GAME_SETTINGS_PAGES[i]; 142 + page.create(panel); 143 + 144 + auto@ button = GuiButton(gameHeader, Alignment(Left+200+(i*200), Top+1, Width=200, Height=38)); 145 + button.text = page.header; 146 + button.buttonIcon = page.icon; 147 + button.toggleButton = true; 148 + button.pressed = false; 149 + button.font = FT_Medium; 150 + button.style = SS_TabButton; 151 + settingsButtons.insertLast(button); 152 + } 153 + 154 + @resetButton = GuiButton(gameBG, Alignment(Left+0.5f-120, Bottom-40, Width=240, Height=35), locale::NG_RESET); 155 + resetButton.color = Color(0xff8080ff); 156 + resetButton.buttonIcon = icons::Reset; 157 + resetButton.visible = false; 158 + 159 + //Galaxy list 160 + @galaxyPanel = GuiPanel(gameBG, 161 + Alignment(Left, Top+46, Right, Bottom-4)); 162 + galaxyPanel.visible = false; 163 + 164 + @addGalaxyButton = GuiButton(galaxyPanel, 165 + recti_area(vec2i(), vec2i(260, 36)), 166 + locale::ADD_GALAXY); 167 + addGalaxyButton.buttonIcon = Sprite(spritesheet::CardCategoryIcons, 3); 168 + 169 + //Maps choice list 170 + @mapPanel = GuiPanel(gameBG, 171 + Alignment(Left, Top+46, Right, Bottom-4)); 172 + mapPanel.visible = true; 173 + choosingMap = true; 174 + 175 + @mapHeader = GuiText(mapPanel, Alignment(Left, Top, Right, Top+30)); 176 + mapHeader.font = FT_Medium; 177 + mapHeader.horizAlign = 0.5; 178 + mapHeader.stroke = colors::Black; 179 + mapHeader.text = locale::CHOOSE_MAP; 180 + 181 + @mapList = GuiListbox(mapPanel, 182 + Alignment(Left+4, Top+34, Right-4, Bottom-4)); 183 + mapList.itemStyle = SS_DropdownListItem; 184 + mapList.itemHeight = 100; 185 + 186 + updateMapList(); 187 + 188 + //Chat 189 + @chatPanel = GuiPanel(chatBG, Alignment(Left+8, Top+34, Right-8, Bottom-38)); 190 + @chatLog = GuiMarkupText(chatPanel, recti_area(0, 0, 100, 100)); 191 + @chatBox = GuiTextbox(chatBG, Alignment(Left+6, Bottom-36, Right-6, Bottom-6)); 192 + 193 + //Actions 194 + @playButton = GuiButton(this, Alignment( 195 + Right-0.05f-200, Bottom-0.1f+6, Width=200, Height=46), 196 + locale::START_GAME); 197 + playButton.buttonIcon = Sprite(spritesheet::MenuIcons, 9); 198 + 199 + @addAIButton = GuiButton(this, Alignment( 200 + Left+300, Bottom-0.1f+6, Width=200, Height=46), 201 + locale::ADD_AI); 202 + addAIButton.buttonIcon = icons::Add; 203 + 204 + @backButton = GuiButton(this, Alignment( 205 + Left+0.05f, Bottom-0.1f+6, Width=200, Height=46), 206 + locale::BACK); 207 + backButton.buttonIcon = Sprite(spritesheet::MenuIcons, 11); 208 + 209 + @inviteButton = GuiButton(this, Alignment( 210 + Left+0.05f+408, Bottom-0.1f+6, Width=200, Height=46), 211 + locale::INVITE_FRIEND); 212 + inviteButton.buttonIcon = Sprite(spritesheet::MenuIcons, 13); 213 + inviteButton.visible = cloud::inLobby; 214 + 215 + updateAbsolutePosition(); 216 + } 217 + 218 + void updateMapList() { 219 + mapList.clearItems(); 220 + for(uint i = 0, cnt = mapCount; i < cnt; ++i) { 221 + auto@ mp = getMap(i); 222 + if(mp.isUnique) { 223 + bool found = false; 224 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 225 + if(galaxies[i].mp.id == mp.id) { 226 + found = true; 227 + break; 228 + } 229 + } 230 + if(found) 231 + continue; 232 + } 233 + if(mp.isListed && !mp.isScenario && (mp.dlc.length == 0 || hasDLC(mp.dlc))) 234 + mapList.addItem(MapElement(mp)); 235 + } 236 + } 237 + 238 + void init() { 239 + if(!empireNamesInitialized) { 240 + empireNames.read("data/empire_names.txt"); 241 + empireNames.useGeneration = false; 242 + empireNamesInitialized = true; 243 + } 244 + 245 + portraits.reset(); 246 + clearEmpires(); 247 + addEmpire(true, getRacePreset(0)); 248 + if(!mpServer && !fromMP) { 249 + addEmpire(false); 250 + addEmpire(false); 251 + RaceChooser(empires[0], true); 252 + } 253 + updateAbsolutePosition(); 254 + 255 + switchPage(0); 256 + 257 + if(fromMP) { 258 + mapPanel.visible = false; 259 + galaxyPanel.visible = true; 260 + choosingMap = false; 261 + } 262 + else { 263 + mapPanel.visible = true; 264 + galaxyPanel.visible = false; 265 + choosingMap = true; 266 + updateMapList(); 267 + } 268 + 269 + addGalaxyButton.visible = !fromMP; 270 + addAIButton.visible = !fromMP; 271 + chatMessages = ""; 272 + 273 + if(fromMP) { 274 + playButton.text = locale::MP_NOT_READY; 275 + playButton.color = colors::Orange; 276 + } 277 + else { 278 + playButton.text = locale::START_GAME; 279 + playButton.color = colors::White; 280 + } 281 + } 282 + 283 + void addChat(const string& str) { 284 + chatMessages += str+"\n"; 285 + bool wasBottom = chatPanel.vert.pos >= (chatPanel.vert.end - chatPanel.vert.page); 286 + chatLog.text = chatMessages; 287 + chatPanel.updateAbsolutePosition(); 288 + if(wasBottom) { 289 + chatPanel.vert.pos = max(0.0, chatPanel.vert.end - chatPanel.vert.page); 290 + chatPanel.updateAbsolutePosition(); 291 + } 292 + } 293 + 294 + void resetAIColors() { 295 + for(uint i = 0, cnt = empires.length; i < cnt; ++i) { 296 + auto@ setup = empires[i]; 297 + if(setup.player) 298 + continue; 299 + setup.settings.color = colors::Invisible; 300 + } 301 + for(uint i = 0, cnt = empires.length; i < cnt; ++i) { 302 + auto@ setup = empires[i]; 303 + if(setup.player) 304 + continue; 305 + setUniqueColor(setup); 306 + } 307 + } 308 + 309 + void resetAIRaces() { 310 + for(uint i = 0, cnt = empires.length; i < cnt; ++i) { 311 + auto@ setup = empires[i]; 312 + if(setup.player) 313 + continue; 314 + setup.settings.raceName = ""; 315 + } 316 + for(uint i = 0, cnt = empires.length; i < cnt; ++i) { 317 + auto@ setup = empires[i]; 318 + if(setup.player) 319 + continue; 320 + setup.applyRace(getUniquePreset()); 321 + } 322 + } 323 + 324 + RacePreset@ getUniquePreset() { 325 + uint index = randomi(0, getRacePresetCount() - 1); 326 + for(uint i = 0, cnt = getRacePresetCount(); i < cnt; ++i) { 327 + auto@ preset = getRacePreset((index+i) % cnt); 328 + if(preset.dlc.length != 0 && !hasDLC(preset.dlc)) 329 + continue; 330 + bool has = false; 331 + for(uint n = 0, ncnt = empires.length; n < ncnt; ++n) { 332 + if(empires[n].settings.raceName == preset.name) { 333 + has = true; 334 + break; 335 + } 336 + } 337 + if(!has) { 338 + return preset; 339 + } 340 + } 341 + for(uint i = 0, cnt = getRacePresetCount(); i < cnt; ++i) { 342 + auto@ preset = getRacePreset((index+i) % cnt); 343 + if(preset.dlc.length != 0 && !hasDLC(preset.dlc)) 344 + continue; 345 + return preset; 346 + } 347 + return getRacePreset(index); 348 + } 349 + 350 + void setUniqueColor(EmpireSetup@ setup) { 351 + bool found = false; 352 + Color setColor; 353 + for(uint i = 0, cnt = getEmpireColorCount(); i < cnt; ++i) { 354 + Color col = getEmpireColor(i).color; 355 + bool has = false; 356 + for(uint n = 0, ncnt = empires.length; n < ncnt; ++n) { 357 + if(empires[n] !is setup && empires[n].settings.color.color == col.color) { 358 + has = true; 359 + break; 360 + } 361 + } 362 + if(!has) { 363 + found = true; 364 + setColor = col; 365 + break; 366 + } 367 + } 368 + if(!found) { 369 + Colorf rnd; 370 + rnd.fromHSV(randomd(0, 360.0), randomd(0.5, 1.0), 1.0); 371 + setColor = Color(rnd); 372 + } 373 + setup.settings.color = setColor; 374 + setup.update(); 375 + } 376 + 377 + void tick(double time) { 378 + if(mapIcons.length == 0) { 379 + mapIcons.length = mapCount; 380 + for(uint i = 0, cnt = mapCount; i < cnt; ++i) { 381 + auto@ mp = getMap(i); 382 + if(mp.isListed && !mp.isScenario && mp.icon.length != 0) 383 + mapIcons[i].load(mp.icon); 384 + } 385 + } 386 + inviteButton.visible = cloud::inLobby; 387 + addAIButton.disabled = empires.length >= 28; 388 + if(mpServer) { 389 + bool allReady = true; 390 + for(uint n = 0, ncnt = empires.length; n < ncnt; ++n) { 391 + auto@ emp = empires[n]; 392 + if(emp.playerId != -1 && emp.playerId != CURRENT_PLAYER.id) { 393 + emp.found = false; 394 + if(!emp.settings.ready) 395 + allReady = false; 396 + } 397 + } 398 + 399 + array<Player@>@ players = getPlayers(); 400 + for(uint i = 0, cnt = players.length; i < cnt; ++i) { 401 + Player@ pl = players[i]; 402 + if(pl == CURRENT_PLAYER) 403 + continue; 404 + 405 + //Find if we already have an empire 406 + bool found = false; 407 + for(uint n = 0, ncnt = empires.length; n < ncnt; ++n) { 408 + auto@ emp = empires[n]; 409 + if(emp.playerId == pl.id) { 410 + emp.found = true; 411 + found = true; 412 + if(pl.name.length != 0 && emp.name.text.length == 0) 413 + emp.name.text = pl.name; 414 + } 415 + } 416 + 417 + if(!found) { 418 + auto@ emp = addEmpire(false, getRacePreset(0)); 419 + emp.name.text = pl.name; 420 + emp.address = pl.address; 421 + emp.setPlayer(pl.id); 422 + } 423 + } 424 + 425 + //Prune disconnected players 426 + for(uint n = 0, ncnt = empires.length; n < ncnt; ++n) { 427 + auto@ emp = empires[n]; 428 + if(emp.playerId != -1 && !emp.found) { 429 + removeEmpire(emp); 430 + --n; --ncnt; 431 + } 432 + } 433 + 434 + //Update play button 435 + if(allReady) 436 + playButton.color = colors::Green; 437 + else 438 + playButton.color = colors::Orange; 439 + } 440 + else if(fromMP) { 441 + if(game_running) { 442 + hideNewGame(true); 443 + switchToMenu(main_menu, snap=true); 444 + return; 445 + } 446 + if(awaitingGalaxy) { 447 + hideNewGame(true); 448 + switchToMenu(main_menu, snap=true); 449 + showMultiplayer(); 450 + return; 451 + } 452 + 453 + auto@ pl = findPlayer(CURRENT_PLAYER.id); 454 + if(pl !is null && pl.settings.ready) { 455 + playButton.text = locale::MP_READY; 456 + playButton.color = colors::Green; 457 + } 458 + else { 459 + playButton.text = locale::MP_NOT_READY; 460 + playButton.color = colors::Orange; 461 + } 462 + 463 + if(!mpIsConnected()) { 464 + message("Lost connection to server:\n " 465 + +localize("DISCONNECT_"+uint(mpDisconnectReason))); 466 + 467 + hideNewGame(true); 468 + switchToMenu(main_menu, snap=true); 469 + showMultiplayer(); 470 + } 471 + } 472 + } 473 + 474 + EmpireSetup@ addEmpire(bool player = false, const RacePreset@ preset = null) { 475 + if(empires.length >= 28) 476 + return null; 477 + empireBG.title = locale::MENU_EMPIRES + " (" + (empires.length + 1) + ")"; 478 + uint y = empires.length * (EMPIRE_SETUP_HEIGHT + 8) + 8; 479 + EmpireSetup@ emp = EmpireSetup(this, 480 + Alignment(Left+4, Top+y, Right-4, Top+y + EMPIRE_SETUP_HEIGHT), 481 + player); 482 + portraits.randomize(emp.settings); 483 + if(player && settings::sNickname.length != 0) 484 + emp.name.text = settings::sNickname; 485 + else 486 + emp.name.text = "Empire "+(nextEmpNum++); 487 + if(preset is null) { 488 + if(player) 489 + @preset = getRacePreset(0); 490 + else 491 + @preset = getUniquePreset(); 492 + } 493 + emp.defaultName = emp.name.text; 494 + emp.update(); 495 + empires.insertLast(emp); 496 + empirePanel.updateAbsolutePosition(); 497 + if(preset !is null) 498 + emp.applyRace(preset); 499 + else if(!player) 500 + emp.resetName(); 501 + if(!player) 502 + setUniqueColor(emp); 503 + return emp; 504 + } 505 + 506 + EmpireSetup@ findPlayer(int id) { 507 + for(uint i = 0, cnt = empires.length; i < cnt; ++i) { 508 + if(empires[i].playerId == id) 509 + return empires[i]; 510 + } 511 + return null; 512 + } 513 + 514 + void clearEmpires() { 515 + for(uint i = 0, cnt = empires.length; i < cnt; ++i) 516 + empires[i].remove(); 517 + empires.length = 0; 518 + nextEmpNum = 2; 519 + updateEmpirePositions(); 520 + } 521 + 522 + void removeEmpire(EmpireSetup@ emp) { 523 + emp.remove(); 524 + empires.remove(emp); 525 + updateEmpirePositions(); 526 + } 527 + 528 + void updateEmpirePositions() { 529 + uint cnt = empires.length; 530 + for(uint i = 0; i < cnt; ++i) { 531 + EmpireSetup@ emp = empires[i]; 532 + emp.alignment.top.pixels = i * (EMPIRE_SETUP_HEIGHT + 8) + 8; 533 + emp.alignment.bottom.pixels = emp.alignment.top.pixels + EMPIRE_SETUP_HEIGHT; 534 + emp.updateAbsolutePosition(); 535 + } 536 + } 537 + 538 + GalaxySetup@ addGalaxy(Map@ mp) { 539 + uint y = galaxies.length * (GALAXY_SETUP_HEIGHT + 8) + 8; 540 + GalaxySetup@ glx = GalaxySetup(this, 541 + Alignment(Left+8, Top+y, Right-8, Top+y + GALAXY_SETUP_HEIGHT), 542 + mp); 543 + 544 + if(mp.eatsPlayers) { 545 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) 546 + galaxies[i].setHomeworlds(false); 547 + } 548 + else { 549 + bool haveEating = false; 550 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 551 + if(galaxies[i].mp.eatsPlayers) { 552 + haveEating = true; 553 + } 554 + } 555 + 556 + if(haveEating) 557 + glx.setHomeworlds(false); 558 + } 559 + 560 + addGalaxyButton.position = vec2i((galaxyPanel.size.width - addGalaxyButton.size.width)/2, y + GALAXY_SETUP_HEIGHT); 561 + galaxies.insertLast(glx); 562 + galaxyPanel.updateAbsolutePosition(); 563 + updateGalaxyPositions(); 564 + return glx; 565 + } 566 + 567 + void removeGalaxy(GalaxySetup@ glx) { 568 + glx.remove(); 569 + galaxies.remove(glx); 570 + updateGalaxyPositions(); 571 + 572 + if(glx.mp.eatsPlayers) { 573 + bool haveEating = false; 574 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 575 + if(galaxies[i].mp.eatsPlayers) { 576 + haveEating = true; 577 + } 578 + } 579 + 580 + if(!haveEating) { 581 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 582 + galaxies[i].setHomeworlds(true); 583 + } 584 + } 585 + } 586 + 587 + if(galaxies.length == 0) { 588 + mapHeader.text = locale::CHOOSE_MAP; 589 + mapPanel.visible = true; 590 + galaxyPanel.visible = false; 591 + choosingMap = true; 592 + updateMapList(); 593 + } 594 + } 595 + 596 + void updateGalaxyPositions() { 597 + uint cnt = galaxies.length; 598 + for(uint i = 0; i < cnt; ++i) { 599 + GalaxySetup@ glx = galaxies[i]; 600 + glx.alignment.top.pixels = i * (GALAXY_SETUP_HEIGHT + 8) + 8; 601 + glx.alignment.bottom.pixels = glx.alignment.top.pixels + GALAXY_SETUP_HEIGHT; 602 + glx.updateAbsolutePosition(); 603 + } 604 + addGalaxyButton.position = vec2i((galaxyPanel.size.width - addGalaxyButton.size.width)/2, cnt * (GALAXY_SETUP_HEIGHT + 8) + 6); 605 + galaxyPanel.updateAbsolutePosition(); 606 + } 607 + 608 + void apply() { 609 + apply(settings); 610 + } 611 + 612 + void reset() { 613 + uint newCnt = settings.empires.length; 614 + uint oldCnt = empires.length; 615 + for(uint i = newCnt; i < oldCnt; ++i) { 616 + removeEmpire(empires[i]); 617 + --i; --oldCnt; 618 + } 619 + for(uint i = 0; i < newCnt; ++i) { 620 + EmpireSetup@ setup; 621 + if(i >= oldCnt) 622 + @setup = addEmpire(); 623 + else 624 + @setup = empires[i]; 625 + auto@ sett = settings.empires[i]; 626 + if(setup.playerId == sett.playerId 627 + && setup.playerId == CURRENT_PLAYER.id) { 628 + if(setup.settings.delta > sett.delta) 629 + setup.apply(settings.empires[i]); 630 + else 631 + setup.load(settings.empires[i]); 632 + } 633 + else { 634 + setup.load(settings.empires[i]); 635 + } 636 + } 637 + updateEmpirePositions(); 638 + 639 + newCnt = settings.galaxies.length; 640 + oldCnt = galaxies.length; 641 + for(uint i = newCnt; i < oldCnt; ++i) { 642 + removeGalaxy(galaxies[i]); 643 + --i; --oldCnt; 644 + } 645 + for(uint i = 0; i < newCnt; ++i) { 646 + GalaxySetup@ setup; 647 + if(i >= oldCnt) 648 + @setup = addGalaxy(getMap(settings.galaxies[i].map_id)); 649 + else 650 + @setup = galaxies[i]; 651 + setup.load(settings.galaxies[i]); 652 + } 653 + updateGalaxyPositions(); 654 + 655 + for(uint i = 0, cnt = GAME_SETTINGS_PAGES.length; i < cnt; ++i) 656 + GAME_SETTINGS_PAGES[i].load(settings); 657 + 658 + addGalaxyButton.visible = !mpClient; 659 + addAIButton.visible = !mpClient; 660 + } 661 + 662 + void reset(GameSettings& settings) { 663 + this.settings = settings; 664 + reset(); 665 + } 666 + 667 + void apply(GameSettings& settings) { 668 + uint empCnt = empires.length; 669 + settings.empires.length = empCnt; 670 + for(uint i = 0; i < empCnt; ++i) { 671 + settings.empires[i].index = i; 672 + empires[i].apply(settings.empires[i]); 673 + } 674 + 675 + uint glxCnt = galaxies.length; 676 + settings.galaxies.length = glxCnt; 677 + for(uint i = 0; i < glxCnt; ++i) 678 + galaxies[i].apply(settings.galaxies[i]); 679 + 680 + for(uint i = 0, cnt = GAME_SETTINGS_PAGES.length; i < cnt; ++i) 681 + GAME_SETTINGS_PAGES[i].apply(settings); 682 + } 683 + 684 + void start(){ 685 + apply(); 686 + 687 + Message msg; 688 + settings.write(msg); 689 + 690 + startNewGame(msg); 691 + } 692 + 693 + void switchPage(uint page) { 694 + mapsButton.pressed = page == 0; 695 + galaxyPanel.visible = page == 0 && !choosingMap; 696 + mapPanel.visible = page == 0 && choosingMap; 697 + if(mapPanel.visible) 698 + updateMapList(); 699 + //if(page == 0) 700 + // gameHeader.color = Color(0xff003fff); 701 + resetButton.visible = page != 0 && !mpClient; 702 + 703 + for(uint i = 0, cnt = settingsButtons.length; i < cnt; ++i) { 704 + settingsButtons[i].pressed = page == i+1; 705 + settingsPanels[i].visible = page == i+1; 706 + //if(page == i+1) 707 + // gameHeader.color = GAME_SETTINGS_PAGES[i].color; 708 + } 709 + } 710 + 711 + bool onGuiEvent(const GuiEvent& event) { 712 + switch(event.type) { 713 + case GUI_Clicked: 714 + if(event.caller is playButton) { 715 + if(fromMP) { 716 + auto@ pl = findPlayer(CURRENT_PLAYER.id); 717 + if(pl !is null) { 718 + pl.settings.ready = !pl.settings.ready; 719 + pl.submit(); 720 + } 721 + } 722 + else { 723 + if(mpServer) { 724 + bool allReady = true; 725 + for(uint n = 0, ncnt = empires.length; n < ncnt; ++n) { 726 + auto@ emp = empires[n]; 727 + if(emp.playerId != -1 && emp.playerId != CURRENT_PLAYER.id) { 728 + if(!emp.settings.ready) 729 + allReady = false; 730 + } 731 + } 732 + 733 + if(!allReady) { 734 + question(locale::MP_CONFIRM_NOT_READY, ConfirmStart()); 735 + return true; 736 + } 737 + } 738 + else { 739 + uint sysCount = 0; 740 + apply(); 741 + for(uint i = 0, cnt = settings.galaxies.length; i < cnt; ++i) 742 + sysCount += settings.galaxies[i].systemCount * settings.galaxies[i].galaxyCount; 743 + uint empCount = empires.length; 744 + if(sysCount > REC_MAX_OHGOD) { 745 + question(locale::NG_WARN_OHGOD, ConfirmStart()); 746 + return true; 747 + } 748 + else if(sysCount > REC_MAX_BAD) { 749 + question(locale::NG_WARN_BAD, ConfirmStart()); 750 + return true; 751 + } 752 + else if(sysCount > REC_MAX_OPTIMAL) { 753 + question(locale::NG_WARN_OPTIMAL, ConfirmStart()); 754 + return true; 755 + } 756 + else if(sysCount > REC_MAX_PEREMP * empCount) { 757 + question(locale::NG_WARN_PEREMP, ConfirmStart()); 758 + return true; 759 + } 760 + } 761 + start(); 762 + hideNewGame(true); 763 + } 764 + return true; 765 + } 766 + else if(event.caller is backButton) { 767 + if(!game_running) 768 + mpDisconnect(); 769 + hideNewGame(); 770 + return true; 771 + } 772 + else if(event.caller is inviteButton) { 773 + cloud::inviteFriend(); 774 + return true; 775 + } 776 + else if(event.caller is addAIButton) { 777 + addEmpire(); 778 + return true; 779 + } 780 + else if(event.caller is addGalaxyButton) { 781 + mapHeader.text = locale::ADD_GALAXY; 782 + mapPanel.visible = true; 783 + galaxyPanel.visible = false; 784 + updateMapList(); 785 + choosingMap = true; 786 + return true; 787 + } 788 + else if(event.caller is resetButton) { 789 + for(uint i = 0, cnt = GAME_SETTINGS_PAGES.length; i < cnt; ++i) { 790 + if(settingsPanels[i].visible) 791 + GAME_SETTINGS_PAGES[i].reset(); 792 + } 793 + return true; 794 + } 795 + else if(event.caller is mapsButton) { 796 + switchPage(0); 797 + return true; 798 + } 799 + else { 800 + for(uint i = 0, cnt = settingsButtons.length; i < cnt; ++i) { 801 + if(event.caller is settingsButtons[i]) { 802 + switchPage(i+1); 803 + return true; 804 + } 805 + } 806 + } 807 + break; 808 + case GUI_Confirmed: 809 + if(event.caller is chatBox) { 810 + string message = chatBox.text; 811 + if(message.length != 0) 812 + menuChat(message); 813 + chatBox.text = ""; 814 + } 815 + break; 816 + case GUI_Changed: 817 + if(event.caller is mapList) { 818 + if(mapList.selected != -1) 819 + addGalaxy(cast<MapElement>(mapList.selectedItem).mp); 820 + if(galaxies.length != 0) { 821 + mapList.clearSelection(); 822 + mapPanel.visible = false; 823 + galaxyPanel.visible = true; 824 + choosingMap = false; 825 + } 826 + return true; 827 + } 828 + break; 829 + case GUI_Animation_Complete: 830 + animating = false; 831 + return true; 832 + } 833 + 834 + return BaseGuiElement::onGuiEvent(event); 835 + } 836 + 837 + void updateAbsolutePosition() { 838 + if(!animating) { 839 + if(!hide) { 840 + size = parent.size; 841 + position = vec2i(0, 0); 842 + } 843 + else { 844 + size = parent.size; 845 + position = vec2i(size.x, 0); 846 + } 847 + } 848 + if(fromMP || mpServer) { 849 + chatBG.visible = true; 850 + chatLog.size = vec2i(chatPanel.size.width-20, chatLog.size.height); 851 + empireBG.alignment.bottom.pixels = 262; 852 + } 853 + else { 854 + chatBG.visible = false; 855 + empireBG.alignment.bottom.pixels = 0; 856 + } 857 + addGalaxyButton.position = vec2i((galaxyPanel.size.width - addGalaxyButton.size.width)/2, addGalaxyButton.position.y); 858 + BaseGuiElement::updateAbsolutePosition(); 859 + } 860 + 861 + void animateIn() { 862 + animating = true; 863 + hide = false; 864 + 865 + rect = recti_area(vec2i(parent.size.x, 0), parent.size); 866 + animate_time(this, recti_area(vec2i(), parent.size), MSLIDE_TIME); 867 + } 868 + 869 + void animateOut() { 870 + animating = true; 871 + hide = true; 872 + 873 + rect = recti_area(vec2i(), parent.size); 874 + animate_time(this, recti_area(vec2i(parent.size.x, 0), parent.size), MSLIDE_TIME); 875 + } 876 +}; 877 + 878 +void drawRace(const Skin@ skin, const recti& absPos, const string& name, 879 + const string& portrait, const array<const Trait@>@ traits = null, bool showTraits = true) { 880 + const Font@ normal = skin.getFont(FT_Normal); 881 + const Font@ bold = skin.getFont(FT_Bold); 882 + recti namePos = recti_area(absPos.topLeft + vec2i(8, 0), vec2i(absPos.width * 0.35, absPos.height)); 883 + 884 + //Portrait 885 + auto@ prt = getEmpirePortrait(portrait); 886 + if(prt !is null) { 887 + prt.portrait.draw(recti_area(absPos.topLeft + vec2i(8, 0), vec2i(absPos.height, absPos.height))); 888 + namePos.topLeft.x += absPos.height+8; 889 + } 890 + 891 + //Race name 892 + bold.draw(pos=namePos, text=name); 893 + 894 + //FTL Method 895 + recti ftlPos = recti_area(absPos.topLeft + vec2i(absPos.width*0.35 + 16, 0), 896 + vec2i(absPos.width * 0.35, absPos.height)); 897 + 898 + //Traits 899 + if(traits !is null) { 900 + recti pos = recti_area(vec2i(absPos.botRight.x - 32, absPos.topLeft.y + 3), vec2i(24, 24)); 901 + for(uint i = 0, cnt = traits.length; i < cnt; ++i) { 902 + auto@ trait = traits[i]; 903 + if(trait.unique == "FTL") { 904 + trait.icon.draw(recti_area(ftlPos.topLeft, vec2i(absPos.height, absPos.height)).aspectAligned(trait.icon.aspect)); 905 + ftlPos.topLeft.x += absPos.height+8; 906 + normal.draw(text=trait.name, pos=ftlPos); 907 + } 908 + else if(showTraits) { 909 + traits[i].icon.draw(pos.aspectAligned(traits[i].icon.aspect)); 910 + pos -= vec2i(24, 0); 911 + } 912 + } 913 + } 914 +} 915 + 916 +Color colorFromNumber(int num) { 917 + float hue = (num*26534371)%360; 918 + Colorf col; 919 + col.fromHSV(hue, 1.f, 1.f); 920 + return Color(col); 921 +} 922 + 923 +class RaceElement : GuiListElement { 924 + const RacePreset@ preset; 925 + 926 + RaceElement(const RacePreset@ preset) { 927 + @this.preset = preset; 928 + } 929 + 930 + void draw(GuiListbox@ ele, uint flags, const recti& absPos) { 931 + drawRace(ele.skin, absPos, preset.name, preset.portrait, preset.traits); 932 + } 933 +}; 934 + 935 +class CustomRaceElement : GuiListElement { 936 + const EmpireSettings@ settings; 937 + 938 + CustomRaceElement(const EmpireSettings@ settings) { 939 + @this.settings = settings; 940 + } 941 + 942 + void draw(GuiListbox@ ele, uint flags, const recti& absPos) { 943 + drawRace(ele.skin, absPos, settings.raceName, settings.portrait, settings.traits); 944 + } 945 +}; 946 + 947 +class CurrentRaceElement : GuiListElement { 948 + EmpireSettings@ settings; 949 + bool valid = true; 950 + 951 + CurrentRaceElement(EmpireSettings@ settings) { 952 + @this.settings = settings; 953 + } 954 + 955 + void update() { 956 + valid = settings.getTraitPoints() >= 0 && !settings.hasTraitConflicts(); 957 + } 958 + 959 + void draw(GuiListbox@ ele, uint flags, const recti& absPos) { 960 + if(!valid) { 961 + Color color(0xff0000ff); 962 + color.a = abs((frameTime % 1.0) - 0.5) * 2.0 * 255.0; 963 + ele.skin.draw(SS_Button, SF_Normal, absPos.padded(-5, -3), color); 964 + } 965 + drawRace(ele.skin, absPos, settings.raceName, settings.portrait, traits=settings.traits, showTraits=false); 966 + } 967 +}; 968 + 969 +class CustomizeOption : GuiListElement { 970 + void draw(GuiListbox@ ele, uint flags, const recti& absPos) { 971 + const Font@ bold = ele.skin.getFont(FT_Bold); 972 + 973 + recti namePos = recti_area(absPos.topLeft + vec2i(8, 0), vec2i(absPos.width * 0.95, absPos.height)); 974 + icons::Customize.draw(recti_area(absPos.topLeft + vec2i(8, 0), vec2i(absPos.height, absPos.height))); 975 + namePos.topLeft.x += absPos.height+8; 976 + 977 + bold.draw(pos=namePos, text=locale::CUSTOMIZE_RACE, color=Color(0xff8000ff)); 978 + } 979 +}; 980 + 981 +class TraitList : GuiIconGrid { 982 + array<const Trait@> traits; 983 + 984 + TraitList(IGuiElement@ parent, Alignment@ align) { 985 + super(parent, align); 986 + 987 + MarkupTooltip tt(350, 0.f, true, true); 988 + tt.Lazy = true; 989 + tt.LazyUpdate = false; 990 + tt.Padding = 4; 991 + @tooltipObject = tt; 992 + } 993 + 994 + uint get_length() override { 995 + return traits.length; 996 + } 997 + 998 + string get_tooltip() override { 999 + if(hovered < 0 || hovered >= int(length)) 1000 + return ""; 1001 + 1002 + auto@ trait = traits[hovered]; 1003 + return format("[color=$1][b]$2[/b][/color]\n$3", 1004 + toString(trait.color), trait.name, trait.description); 1005 + } 1006 + 1007 + void drawElement(uint i, const recti& pos) override { 1008 + traits[i].icon.draw(pos.aspectAligned(traits[i].icon.aspect)); 1009 + } 1010 +}; 1011 + 1012 +class ChangeWelfare : GuiContextOption { 1013 + ChangeWelfare(const string& text, uint index) { 1014 + value = int(index); 1015 + this.text = text; 1016 + icon = Sprite(spritesheet::ConvertIcon, index); 1017 + } 1018 + 1019 + void call(GuiContextMenu@ menu) override { 1020 + playerEmpire.WelfareMode = uint(value); 1021 + } 1022 +}; 1023 + 1024 +const Sprite[] DIFF_SPRITES = { 1025 + Sprite(material::HappyFace), 1026 + Sprite(material::StatusPeace), 1027 + Sprite(material::StatusWar), 1028 + Sprite(material::StatusCeaseFire), 1029 + Sprite(spritesheet::AttributeIcons, 3), 1030 + Sprite(spritesheet::AttributeIcons, 0), 1031 + Sprite(spritesheet::VoteIcons, 3), 1032 + Sprite(spritesheet::VoteIcons, 3, colors::Red) 1033 +}; 1034 + 1035 +const Color[] DIFF_COLORS = { 1036 + colors::Green, 1037 + colors::White, 1038 + colors::White, 1039 + colors::White, 1040 + colors::Orange, 1041 + colors::Red, 1042 + colors::Red, 1043 + colors::Red 1044 +}; 1045 + 1046 +const string[] DIFF_TOOLTIPS = { 1047 + locale::DIFF_PASSIVE, 1048 + locale::DIFF_EASY, 1049 + locale::DIFF_NORMAL, 1050 + locale::DIFF_HARD, 1051 + locale::DIFF_MURDEROUS, 1052 + locale::DIFF_CHEATING, 1053 + locale::DIFF_SAVAGE, 1054 + locale::DIFF_BARBARIC, 1055 +}; 1056 + 1057 +class ChangeDifficulty : GuiMarkupContextOption { 1058 + int level; 1059 + EmpireSetup@ setup; 1060 + 1061 + ChangeDifficulty(EmpireSetup@ setup, int value, const string& text) { 1062 + level = value; 1063 + set(text); 1064 + @this.setup = setup; 1065 + } 1066 + 1067 + void call(GuiContextMenu@ menu) override { 1068 + setup.settings.difficulty = level; 1069 + setup.update(); 1070 + } 1071 +}; 1072 + 1073 +class ChangeTeam : GuiMarkupContextOption { 1074 + int team; 1075 + EmpireSetup@ setup; 1076 + 1077 + ChangeTeam(EmpireSetup@ setup, int value) { 1078 + team = value; 1079 + if(value >= 0) 1080 + set(format("[b][color=$2]$1[/color][/b]", format(locale::TEAM_TEXT, toString(value)), 1081 + toString(colorFromNumber(value)))); 1082 + else 1083 + set(format("[b][color=#aaa]$1...[/color][/b]", locale::NO_TEAM)); 1084 + @this.setup = setup; 1085 + } 1086 + 1087 + void call(GuiContextMenu@ menu) override { 1088 + setup.settings.team = team; 1089 + setup.submit(); 1090 + } 1091 +}; 1092 + 1093 +class Chooser : GuiIconGrid { 1094 + Color spriteColor; 1095 + array<Color> colors; 1096 + array<Sprite> sprites; 1097 + 1098 + uint selected = 0; 1099 + 1100 + Chooser(IGuiElement@ parent, Alignment@ align, const vec2i& itemSize) { 1101 + super(parent, align); 1102 + horizAlign = 0.5; 1103 + vertAlign = 0.0; 1104 + iconSize = itemSize; 1105 + updateAbsolutePosition(); 1106 + } 1107 + 1108 + void add(const Color& col) { 1109 + colors.insertLast(col); 1110 + } 1111 + 1112 + void add(const Sprite& sprt) { 1113 + sprites.insertLast(sprt); 1114 + } 1115 + 1116 + uint get_length() override { 1117 + return max(colors.length, sprites.length); 1118 + } 1119 + 1120 + void drawElement(uint index, const recti& pos) override { 1121 + if(uint(selected) == index) 1122 + drawRectangle(pos, Color(0xffffff30)); 1123 + if(uint(hovered) == index) 1124 + drawRectangle(pos, Color(0xffffff30)); 1125 + if(index < colors.length) 1126 + drawRectangle(pos.padded(5), colors[index]); 1127 + if(index < sprites.length) 1128 + sprites[index].draw(pos, spriteColor); 1129 + } 1130 +}; 1131 + 1132 +class RaceChooser : GuiOverlay { 1133 + EmpireSetup@ setup; 1134 + GuiSkinElement@ panel; 1135 + 1136 + GuiText@ header; 1137 + GuiPanel@ list; 1138 + 1139 + const RacePreset@ selectedRace; 1140 + array<GuiButton@> presetButtons; 1141 + array<const RacePreset@> racePresets; 1142 + 1143 + GuiSprite@ portrait; 1144 + GuiSprite@ flag; 1145 + GuiSprite@ bgDisplay; 1146 + 1147 + GuiPanel@ descScroll; 1148 + GuiMarkupText@ description; 1149 + 1150 + GuiPanel@ loreScroll; 1151 + GuiMarkupText@ lore; 1152 + 1153 + GuiButton@ playButton; 1154 + GuiButton@ customizeButton; 1155 + GuiButton@ randomizeButton; 1156 + GuiButton@ loadButton; 1157 + GuiButton@ backButton; 1158 + bool isInitial; 1159 + bool hasChosenRace = false; 1160 + bool chosenShipset = false; 1161 + 1162 + Chooser@ flags; 1163 + Chooser@ colors; 1164 + ShipsetChooser@ shipsets; 1165 + 1166 + RaceChooser(EmpireSetup@ setup, bool isInitial = false) { 1167 + @this.setup = setup; 1168 + this.isInitial = isInitial; 1169 + super(null); 1170 + closeSelf = false; 1171 + 1172 + @panel = GuiSkinElement(this, Alignment(Left-4, Top+0.05f, Right+4, Bottom-0.05f), SS_Panel); 1173 + 1174 + @customizeButton = GuiButton(panel, Alignment(Right-232, Bottom-78, Width=220, Height=33)); 1175 + customizeButton.text = locale::CUSTOMIZE_RACE; 1176 + customizeButton.setIcon(icons::Edit); 1177 + 1178 + @randomizeButton = GuiButton(panel, Alignment(Right-452, Bottom-78, Width=220, Height=33)); 1179 + randomizeButton.text = "Randomize Race"; 1180 + 1181 + @loadButton = GuiButton(panel, Alignment(Right-232, Bottom-78+33, Width=220, Height=33)); 1182 + loadButton.text = locale::LOAD_CUSTOM_RACE; 1183 + loadButton.setIcon(icons::Load); 1184 + 1185 + int w = 250, h = 140; 1186 + int off = max((size.width - (getRacePresetCount() * w)) / 2 - 20, 0); 1187 + 1188 + GuiSkinElement listBG(panel, Alignment(Left-4, Top+12, Right+4, Top+154), SS_PlainBox); 1189 + 1190 + @list = GuiPanel(panel, Alignment(Left+off, Top+12, Right-off, Top+174)); 1191 + updateAbsolutePosition(); 1192 + 1193 + vec2i pos; 1194 + uint curSelection = 0; 1195 + for(uint i = 0, cnt = getRacePresetCount(); i < cnt; ++i) { 1196 + auto@ preset = getRacePreset(i); 1197 + if(preset.dlc.length != 0 && !hasDLC(preset.dlc)) 1198 + continue; 1199 + 1200 + racePresets.insertLast(preset); 1201 + 1202 + GuiButton btn(list, recti_area(pos.x, pos.y, w, h)); 1203 + btn.toggleButton = true; 1204 + btn.style = SS_GlowButton; 1205 + btn.pressed = i == 0; 1206 + 1207 + GuiSprite icon(btn, recti_area(2, 2, w*0.75, h-4)); 1208 + icon.horizAlign = 0.0; 1209 + icon.vertAlign = 1.0; 1210 + icon.desc = getSprite(preset.portrait); 1211 + 1212 + GuiText name(btn, recti_area(0, 0, w-4, h)); 1213 + name.font = FT_Big; 1214 + name.stroke = colors::Black; 1215 + name.text = preset.name; 1216 + name.vertAlign = 0.4; 1217 + name.horizAlign = 0.9; 1218 + 1219 + GuiSkinElement tagbar(btn, recti_area(1, h-28, w-3, 24), SS_PlainBox); 1220 + tagbar.color = Color(0xffffff80); 1221 + 1222 + GuiText tagline(btn, recti_area(0, h-30, w-4, 24)); 1223 + tagline.font = FT_Italic; 1224 + tagline.stroke = colors::Black; 1225 + tagline.color = Color(0xaaaaaaff); 1226 + tagline.text = preset.tagline; 1227 + tagline.horizAlign = 1.0; 1228 + 1229 + TraitList traits(btn, Alignment(Left, Bottom-56, Right, Bottom-28)); 1230 + traits.iconSize = vec2i(24, 24); 1231 + traits.horizAlign = 1.0; 1232 + traits.fallThrough = true; 1233 + traits.traits = preset.traits; 1234 + 1235 + if(preset.equals(setup.settings)) { 1236 + curSelection = i; 1237 + hasChosenRace = true; 1238 + } 1239 + 1240 + if(!setup.player && !preset.aiSupport) { 1241 + icon.saturation = 0.f; 1242 + traits.visible = false; 1243 + btn.disabled = true; 1244 + btn.color = Color(0xffffffaa); 1245 + name.color = Color(0xaa3030ff); 1246 + 1247 + setMarkupTooltip(btn, locale::AI_CANNOT_PLAY); 1248 + } 1249 + 1250 + presetButtons.insertLast(btn); 1251 + pos.x += w; 1252 + } 1253 + 1254 + 1255 + BaseGuiElement leftBG(panel, Alignment(Left+12, Top+174, Left+0.33f-6, Bottom-90)); 1256 + int y = 0; 1257 + 1258 + GuiSkinElement portBG(leftBG, Alignment(Left, Top+y, Right, Bottom), SS_PlainBox); 1259 + 1260 + @bgDisplay = GuiSprite(portBG, Alignment().padded(2), Sprite(getEmpireColor(setup.settings.color).background)); 1261 + bgDisplay.color = Color(0xffffff80); 1262 + bgDisplay.stretchOutside = true; 1263 + 1264 + @portrait = GuiSprite(portBG, Alignment(Left+2, Top, Right-2, Height=232)); 1265 + portrait.horizAlign = 0.0; 1266 + portrait.vertAlign = 1.0; 1267 + 1268 + @flag = GuiSprite(portBG, Alignment(Right-164, Top+4, Width=160, Height=160)); 1269 + flag.horizAlign = 1.0; 1270 + flag.vertAlign = 0.0; 1271 + flag.color = setup.settings.color; 1272 + flag.color.a = 0xc0; 1273 + flag.desc = getSprite(setup.settings.flag); 1274 + 1275 + y += 220 + 12; 1276 + GuiSkinElement colBG(leftBG, Alignment(Left, Top+y, Right, Height=34), SS_PlainBox); 1277 + @colors = Chooser(colBG, Alignment().padded(8, 0), vec2i(48, 32)); 1278 + for(uint i = 0, cnt = getEmpireColorCount(); i < cnt; ++i) { 1279 + Color color = getEmpireColor(i).color; 1280 + colors.add(color); 1281 + if(color.color == setup.settings.color.color) 1282 + colors.selected = i; 1283 + } 1284 + updateAbsolutePosition(); 1285 + 1286 + y += 34 + 12; 1287 + GuiSkinElement flagBG(leftBG, Alignment(Left, Top+y, Right, Height=110), SS_PlainBox); 1288 + @flags = Chooser(flagBG, Alignment().padded(8, 0), vec2i(48, 48)); 1289 + flags.spriteColor = setup.settings.color; 1290 + for(uint i = 0, cnt = getEmpireFlagCount(); i < cnt; ++i) { 1291 + string flag = getSpriteDesc(Sprite(getEmpireFlag(i).flag)); 1292 + flags.add(getSprite(flag)); 1293 + if(flag == setup.settings.flag) 1294 + flags.selected = i; 1295 + } 1296 + 1297 + y += 110 + 12; 1298 + // DOF - Adjust shipset selection box size. 1299 + // Trying dynamic size based on number of shipsets. Target 6 per row, max 4 rows (don't want to get too tall for lower resolutions). 1300 + uint SSBoxHeight = min(int(ceil(getShipsetCount()/8.0)),4) * 75; 1301 + GuiSkinElement shipsetBG(leftBG, Alignment(Left, Top+y, Right, Height=SSBoxHeight), SS_PlainBox); 1302 + @shipsets = ShipsetChooser(shipsetBG, Alignment().padded(8, 0), vec2i(160, 70)); 1303 + shipsets.selectedColor = setup.settings.color; 1304 + shipsets.selected = 0; 1305 + shipsets.horizAlign = 0.0; 1306 + for(uint i = 0, cnt = getShipsetCount(); i < cnt; ++i) { 1307 + auto@ ss = getShipset(i); 1308 + if(ss.available && (ss.dlc.length == 0 || hasDLC(ss.dlc))) 1309 + shipsets.add(ss); 1310 + if(ss.ident == setup.settings.shipset) 1311 + shipsets.selected = shipsets.length-1; 1312 + } 1313 + 1314 + GuiSkinElement loreBox(panel, Alignment(Left+0.33f+6, Top+174, Left+0.66f-6, Bottom-90), SS_PlainBox); 1315 + @loreScroll = GuiPanel(loreBox, Alignment().fill()); 1316 + @lore = GuiMarkupText(loreScroll, recti_area(12, 12, 376, 100)); 1317 + lore.fitWidth = true; 1318 + 1319 + GuiSkinElement descBox(panel, Alignment(Left+0.66f+6, Top+174, Right-12, Bottom-90), SS_PlainBox); 1320 + @descScroll = GuiPanel(descBox, Alignment().fill()); 1321 + @description = GuiMarkupText(descScroll, recti_area(12, 12, 376, 100)); 1322 + description.fitWidth = true; 1323 + 1324 + @playButton = GuiButton(panel, Alignment(Left+0.5f-150, Bottom-78, Left+0.5f+150, Bottom-12)); 1325 + playButton.font = FT_Medium; 1326 + playButton.color = Color(0x00c0ffff); 1327 + 1328 + @backButton = GuiButton(panel, Alignment(Left+12, Bottom-78, Left+220, Bottom-12), locale::BACK); 1329 + backButton.font = FT_Medium; 1330 + backButton.buttonIcon = icons::Back; 1331 + 1332 + selectRace(curSelection); 1333 + updateAbsolutePosition(); 1334 + updateAbsolutePosition(); 1335 + } 1336 + 1337 + void updateAbsolutePosition() { 1338 + if(shipsets !is null && shipsets.parent !is null) 1339 + shipsets.parent.visible = screenSize.height >= 900; 1340 + BaseGuiElement::updateAbsolutePosition(); 1341 + } 1342 + 1343 + void close() override { 1344 + if(isInitial) 1345 + return; 1346 + GuiOverlay::close(); 1347 + } 1348 + 1349 + void selectRace(uint select) { 1350 + for(uint i = 0, cnt = presetButtons.length; i < cnt; ++i) 1351 + presetButtons[i].pressed = i == select; 1352 + 1353 + auto@ preset = racePresets[select]; 1354 + 1355 + string desc; 1356 + if(preset.isHard) 1357 + desc += format("[font=Subtitle][color=#ffc000]$1[/color][/font]", locale::RACE_IS_HARD); 1358 + for(uint i = 0, cnt = preset.traits.length; i < cnt; ++i) { 1359 + if(desc.length != 0) 1360 + desc += "\n\n"; 1361 + desc += format("[font=Medium]$1[/font][vspace=4/]\n[offset=20]", preset.traits[i].name); 1362 + desc += preset.traits[i].description; 1363 + desc += "[/offset]"; 1364 + } 1365 + description.text = desc; 1366 + 1367 + string txt = format("[font=Big]$1[/font]\n", preset.name); 1368 + txt += format("[right][font=Medium][color=#aaa]$1[/color][/font][/right]\n\n", preset.tagline); 1369 + txt += preset.lore; 1370 + lore.text = txt; 1371 + 1372 + if(isInitial) 1373 + playButton.text = format(locale::PLAY_AS_RACE, preset.name); 1374 + else 1375 + playButton.text = format(locale::CHOOSE_A_RACE, preset.name); 1376 + playButton.buttonIcon = getSprite(preset.portrait); 1377 + 1378 + portrait.desc = getSprite(preset.portrait); 1379 + 1380 + loreScroll.updateAbsolutePosition(); 1381 + descScroll.updateAbsolutePosition(); 1382 + @selectedRace = preset; 1383 + 1384 + if(!chosenShipset) { 1385 + setup.settings.shipset = preset.shipset; 1386 + for(uint i = 0, cnt = shipsets.length; i < cnt; ++i) { 1387 + if(shipsets.items[i].ident == preset.shipset) { 1388 + shipsets.selected = i; 1389 + break; 1390 + } 1391 + } 1392 + } 1393 + } 1394 + 1395 + bool onGuiEvent(const GuiEvent& evt) { 1396 + if(evt.type == GUI_Clicked) { 1397 + if(evt.caller is flags) { 1398 + uint sel = flags.hovered; 1399 + if(sel != uint(-1)) { 1400 + string sprt = getSpriteDesc(Sprite(getEmpireFlag(sel).flag)); 1401 + setup.settings.flag = sprt; 1402 + flag.desc = getSprite(sprt); 1403 + flags.selected = sel; 1404 + } 1405 + return true; 1406 + } 1407 + else if(evt.caller is colors) { 1408 + uint sel = colors.hovered; 1409 + if(sel != uint(-1)) { 1410 + auto empCol = getEmpireColor(sel); 1411 + Color col = empCol.color; 1412 + setup.settings.color = col; 1413 + bgDisplay.desc = Sprite(empCol.background); 1414 + flag.color = col; 1415 + flag.color.a = 0xc0; 1416 + flags.spriteColor = col; 1417 + shipsets.selectedColor = col; 1418 + colors.selected = sel; 1419 + } 1420 + return true; 1421 + } 1422 + else if(evt.caller is shipsets) { 1423 + uint sel = shipsets.hovered; 1424 + if(sel != uint(-1)) { 1425 + chosenShipset = true; 1426 + setup.settings.shipset = shipsets.items[sel].ident; 1427 + shipsets.selected = sel; 1428 + } 1429 + return true; 1430 + } 1431 + else if(evt.caller is customizeButton) { 1432 + isInitial = false; 1433 + if(hasChosenRace) { 1434 + setup.applyRace(selectedRace); 1435 + setup.submit(); 1436 + } 1437 + setup.openRaceWindow(); 1438 + close(); 1439 + return true; 1440 + } 1441 + else if(evt.caller is backButton) { 1442 + if(isInitial) { 1443 + isInitial = false; 1444 + new_game.backButton.emitClicked(); 1445 + close(); 1446 + return true; 1447 + } 1448 + close(); 1449 + return true; 1450 + } 1451 + else if(evt.caller is randomizeButton) { 1452 + hasChosenRace = true; 1453 + selectRace(randomi(0, presetButtons.length - 1)); 1454 + } 1455 + else if(evt.caller is loadButton) { 1456 + isInitial = false; 1457 + LoadRaceDialog(null, setup.settings, setup); 1458 + close(); 1459 + return true; 1460 + } 1461 + else if(evt.caller is playButton) { 1462 + setup.applyRace(selectedRace); 1463 + setup.submit(); 1464 + if(isInitial) { 1465 + isInitial = false; 1466 + setup.resetName(); 1467 + setup.ng.resetAIColors(); 1468 + setup.ng.resetAIRaces(); 1469 + } 1470 + close(); 1471 + return true; 1472 + } 1473 + else { 1474 + for(uint i = 0, cnt = presetButtons.length; i < cnt; ++i) { 1475 + if(evt.caller.isChildOf(presetButtons[i])) { 1476 + hasChosenRace = true; 1477 + selectRace(i); 1478 + return true; 1479 + } 1480 + } 1481 + } 1482 + } 1483 + return GuiOverlay::onGuiEvent(evt); 1484 + } 1485 +}; 1486 + 1487 +class EmpireSetup : BaseGuiElement, IGuiCallback { 1488 + GuiButton@ portraitButton; 1489 + GuiEmpire@ portrait; 1490 + EmpireSettings settings; 1491 + 1492 + NewGame@ ng; 1493 + GuiTextbox@ name; 1494 + GuiButton@ removeButton; 1495 + GuiText@ handicapLabel; 1496 + GuiSpinbox@ handicap; 1497 + GuiButton@ raceBox; 1498 + GameAddress address; 1499 + GuiButton@ colorButton; 1500 + GuiButton@ flagButton; 1501 + GuiButton@ difficulty; 1502 + GuiButton@ aiSettings; 1503 + GuiSprite@ aiIcon; 1504 + GuiText@ aiText; 1505 + GuiButton@ teamButton; 1506 + GuiText@ raceName; 1507 + GuiSprite@ raceFTLIcon; 1508 + GuiText@ raceFTL; 1509 + TraitList@ traitList; 1510 + bool player; 1511 + bool found = true; 1512 + int playerId = -1; 1513 + ChoosePopup@ popup; 1514 + GuiSprite@ readyness; 1515 + string defaultName; 1516 + 1517 + EmpireSetup(NewGame@ menu, Alignment@ align, bool Player = false) { 1518 + super(menu.empirePanel, align); 1519 + 1520 + @portraitButton = GuiButton(this, Alignment(Left+8, Top+4, Left+EMPIRE_SETUP_HEIGHT, Bottom-4)); 1521 + portraitButton.style = SS_NULL; 1522 + @portrait = GuiEmpire(portraitButton, Alignment().fill()); 1523 + @portrait.settings = settings; 1524 + 1525 + @ng = menu; 1526 + @name = GuiTextbox(this, Alignment(Left+EMPIRE_SETUP_HEIGHT+8, Top+14, Right-310, Top+0.5f-4)); 1527 + name.font = FT_Subtitle; 1528 + name.style = SS_HoverTextbox; 1529 + name.selectionColor = Color(0xffffff40); 1530 + 1531 + @colorButton = GuiButton(this, Alignment(Right-302, Top+14, Width=50, Height=30)); 1532 + colorButton.style = SS_HoverButton; 1533 + 1534 + @flagButton = GuiButton(this, Alignment(Right-244, Top+14, Width=50, Height=30)); 1535 + flagButton.style = SS_HoverButton; 1536 + 1537 + @teamButton = GuiButton(this, Alignment(Right-186, Top+14, Width=50, Height=30)); 1538 + teamButton.style = SS_HoverButton; 1539 + 1540 + @difficulty = GuiButton(this, Alignment(Right-128, Top+14, Width=50, Height=30)); 1541 + difficulty.style = SS_HoverButton; 1542 + difficulty.visible = false; 1543 + 1544 + @aiSettings = GuiButton(this, Alignment(Right-128, Top+10, Width=66, Height=38)); 1545 + aiSettings.style = SS_HoverButton; 1546 + aiSettings.visible = false; 1547 + 1548 + @aiIcon = GuiSprite(aiSettings, Alignment().padded(1, 1, 1, 5)); 1549 + @aiText = GuiText(aiSettings, Alignment()); 1550 + aiText.horizAlign = 0.5; 1551 + aiText.vertAlign = 0.2; 1552 + aiText.font = FT_Small; 1553 + aiText.stroke = colors::Black; 1554 + 1555 + @raceBox = GuiButton(this, Alignment(Left+EMPIRE_SETUP_HEIGHT+8, Top+0.5f+4, Right-8, Bottom-14)); 1556 + raceBox.style = SS_HoverButton; 1557 + 1558 + @raceName = GuiText(raceBox, Alignment(Left+8, Top, Left+0.35f, Bottom)); 1559 + raceName.font = FT_Bold; 1560 + 1561 + @raceFTLIcon = GuiSprite(raceBox, Alignment(Left+0.4f, Top, Left+0.4f+22, Bottom)); 1562 + @raceFTL = GuiText(raceBox, Alignment(Left+0.4f+26, Top, Right-0.3f, Bottom)); 1563 + 1564 + @traitList = TraitList(raceBox, Alignment(Right-0.3f, Top+2, Right-30, Bottom)); 1565 + traitList.iconSize = vec2i(24, 24); 1566 + traitList.horizAlign = 1.0; 1567 + traitList.fallThrough = true; 1568 + 1569 + player = Player; 1570 + @removeButton = GuiButton(this, 1571 + Alignment(Right-50, Top, Right, Top+30)); 1572 + removeButton.color = colors::Red; 1573 + removeButton.setIcon(icons::Remove); 1574 +// if(!player) { 1575 + removeButton.visible = true; 1576 + aiSettings.visible = true; 1577 +// } 1578 +// else { 1579 +// removeButton.visible = false; 1580 +// playerId = 1; 1581 +// } 1582 + 1583 + @readyness = GuiSprite(portrait, Alignment(Right-40, Bottom-40, Right, Bottom)); 1584 + readyness.visible = false; 1585 + 1586 + applyRace(getRacePreset(randomi(0, getRacePresetCount()-1))); 1587 + updateAbsolutePosition(); 1588 + } 1589 + 1590 + void showDifficulties() { 1591 + GuiContextMenu menu(mousePos); 1592 + menu.itemHeight = 54; 1593 + menu.addOption(ChangeDifficulty(this, 0, locale::DIFF_PASSIVE)); 1594 + menu.addOption(ChangeDifficulty(this, 1, locale::DIFF_EASY)); 1595 + menu.addOption(ChangeDifficulty(this, 2, locale::DIFF_NORMAL)); 1596 + menu.addOption(ChangeDifficulty(this, 3, locale::DIFF_HARD)); 1597 + menu.addOption(ChangeDifficulty(this, 4, locale::DIFF_MURDEROUS)); 1598 + menu.addOption(ChangeDifficulty(this, 5, locale::DIFF_CHEATING)); 1599 + menu.addOption(ChangeDifficulty(this, 6, locale::DIFF_SAVAGE)); 1600 + menu.addOption(ChangeDifficulty(this, 7, locale::DIFF_BARBARIC)); 1601 + 1602 + menu.updateAbsolutePosition(); 1603 + } 1604 + 1605 + void showAISettings() { 1606 + AIPopup popup(aiSettings, this); 1607 + aiSettings.Hovered = false; 1608 + aiSettings.Pressed = false; 1609 + } 1610 + 1611 + void showTeams() { 1612 + GuiContextMenu menu(mousePos); 1613 + menu.itemHeight = 30; 1614 + 1615 + //Figure out how many distinct teams we have 1616 + uint distinctTeams = 0; 1617 + uint teamMask = 0; 1618 + int maxTeam = 0; 1619 + for(uint i = 0, cnt = ng.empires.length; i < cnt; ++i) { 1620 + int team = ng.empires[i].settings.team; 1621 + if(team < 0) 1622 + continue; 1623 + 1624 + maxTeam = max(maxTeam, team); 1625 + uint mask = 1<<(team-1); 1626 + if(mask & teamMask == 0) { 1627 + teamMask |= mask; 1628 + ++distinctTeams; 1629 + } 1630 + } 1631 + 1632 + //Add more teams than we currently have 1633 + menu.addOption(ChangeTeam(this, -1)); 1634 + for(uint i = 1; i <= min(max(distinctTeams+5, maxTeam+1), 30); ++i) 1635 + menu.addOption(ChangeTeam(this, i)); 1636 + 1637 + menu.updateAbsolutePosition(); 1638 + } 1639 + 1640 + void forceAITraits(EmpireSettings& settings) { 1641 + for(uint i = 0, cnt = settings.traits.length; i < cnt; ++i) { 1642 + auto@ trait = settings.traits[i]; 1643 + if(!trait.aiSupport) { 1644 + if(trait.unique.length == 0) { 1645 + settings.traits.removeAt(i); 1646 + --cnt; --i; 1647 + } 1648 + else { 1649 + const Trait@ repl; 1650 + uint replCount = 0; 1651 + for(uint n = 0, ncnt = getTraitCount(); n < ncnt; ++n) { 1652 + auto@ other = getTrait(n); 1653 + if(other.unique == trait.unique && other.aiSupport && other.hasDLC) { 1654 + replCount += 1; 1655 + if(randomd() < 1.0 / double(replCount)) 1656 + @repl = other; 1657 + } 1658 + } 1659 + 1660 + if(repl !is null) { 1661 + @settings.traits[i] = repl; 1662 + } 1663 + else { 1664 + settings.traits.removeAt(i); 1665 + --cnt; --i; 1666 + } 1667 + } 1668 + } 1669 + } 1670 + } 1671 + 1672 + void applyRace(const RacePreset@ preset) { 1673 + preset.apply(settings); 1674 + forceAITraits(settings); 1675 + if(!player) { 1676 + //forceAITraits(settings); 1677 + if(defaultName == name.text) 1678 + resetName(); 1679 + } 1680 + update(); 1681 + } 1682 + 1683 + void applyRace(const EmpireSettings@ custom) { 1684 + settings.copyRaceFrom(custom); 1685 + forceAITraits(settings); 1686 + if(!player) { 1687 + //settings.copyRaceFrom(custom); 1688 + if(defaultName == name.text) 1689 + resetName(); 1690 + } 1691 + } 1692 + 1693 + void resetName() { 1694 + string race = settings.raceName; 1695 + if(race.startswith_nocase("the ")) 1696 + race = race.substr(4); 1697 + name.text = format(localize(empireNames.generate()), race); 1698 + defaultName = name.text; 1699 + } 1700 + 1701 + void setPlayer(int id) { 1702 + player = id != -1; 1703 + playerId = id; 1704 + name.disabled = player || !mpClient; 1705 + removeButton.visible = !mpClient && (!player || id != CURRENT_PLAYER.id); 1706 + aiSettings.visible = !player; 1707 + readyness.visible = player && id != 1; 1708 + 1709 + bool editable = id == CURRENT_PLAYER.id || (!mpClient && id == -1); 1710 + raceBox.disabled = !editable; 1711 + colorButton.disabled = !editable; 1712 + flagButton.disabled = !editable; 1713 + teamButton.disabled = !editable; 1714 + aiSettings.disabled = !editable; 1715 + } 1716 + 1717 + void openRaceWindow() { 1718 + TraitsWindow win(this); 1719 + } 1720 + 1721 + void update() { 1722 + updateTraits(); 1723 + 1724 + if(difficulty.visible) { 1725 + difficulty.color = DIFF_COLORS[settings.difficulty]; 1726 + setMarkupTooltip(difficulty, locale::TT_DIFF+"\n"+DIFF_TOOLTIPS[settings.difficulty], width=300); 1727 + if(difficulty.color.color != colors::White.color) 1728 + difficulty.style = SS_Button; 1729 + else 1730 + difficulty.style = SS_HoverButton; 1731 + } 1732 + 1733 + if(aiSettings.visible) { 1734 + aiIcon.desc = QDIFF_ICONS[clamp(settings.difficulty, 0, 2)]; 1735 + aiText.color = QDIFF_COLORS[clamp(settings.difficulty, 0, 2)]; 1736 + aiText.text = getAIName(settings); 1737 + } 1738 + 1739 + name.textColor = settings.color; 1740 + raceName.text = settings.raceName; 1741 + for(uint i = 0, cnt = settings.traits.length; i < cnt; ++i) { 1742 + auto@ trait = settings.traits[i]; 1743 + if(trait.unique == "FTL") { 1744 + raceFTLIcon.desc = trait.icon; 1745 + raceFTL.text = trait.name; 1746 + } 1747 + } 1748 + } 1749 + 1750 + void updateTraits() { 1751 + traitList.traits.length = 0; 1752 + for(uint i = 0, cnt = getTraitCount(); i < cnt; ++i) { 1753 + auto@ trait = getTrait(i); 1754 + if(settings.hasTrait(trait) && trait.unique != "FTL") 1755 + traitList.traits.insertLast(trait); 1756 + } 1757 + if(settings.ready) { 1758 + readyness.tooltip = locale::MP_PLAYER_READY; 1759 + readyness.desc = icons::Ready; 1760 + } 1761 + else { 1762 + readyness.tooltip = locale::MP_PLAYER_NOT_READY; 1763 + readyness.desc = icons::NotReady; 1764 + } 1765 + } 1766 + 1767 + void submit() { 1768 + if(mpClient) 1769 + changeEmpireSettings(settings); 1770 + //if(!player) 1771 + forceAITraits(settings); 1772 + settings.delta += 1; 1773 + update(); 1774 + } 1775 + 1776 + bool onGuiEvent(const GuiEvent& evt) { 1777 + switch(evt.type) { 1778 + case GUI_Clicked: 1779 + if(evt.caller is removeButton) { 1780 + if(player) 1781 + mpKick(playerId); 1782 + else 1783 + ng.removeEmpire(this); 1784 + return true; 1785 + } 1786 + else if(evt.caller is difficulty) { 1787 + showDifficulties(); 1788 + return true; 1789 + } 1790 + else if(evt.caller is aiSettings) { 1791 + showAISettings(); 1792 + return true; 1793 + } 1794 + else if(evt.caller is teamButton) { 1795 + showTeams(); 1796 + return true; 1797 + } 1798 + else if(evt.caller is raceBox || evt.caller is portraitButton) { 1799 + RaceChooser(this); 1800 + raceBox.Hovered = false; 1801 + return true; 1802 + } 1803 + else if(evt.caller is colorButton) { 1804 + vec2i pos(evt.caller.absolutePosition.topLeft.x, 1805 + evt.caller.absolutePosition.botRight.y); 1806 + uint cnt = getEmpireColorCount(); 1807 + vec2i size(220, ceil(double(cnt)/4.0) * 38.0); 1808 + @popup = ChoosePopup(pos, size, vec2i(48, 32)); 1809 + @popup.callback = this; 1810 + popup.extraHeight = 60; 1811 + ColorPicker picker(popup.overlay, recti_area(pos+vec2i(20,size.y+2), vec2i(size.x-40, 50))); 1812 + @picker.callback = this; 1813 + for(uint i = 0; i < cnt; ++i) 1814 + popup.add(getEmpireColor(i).color); 1815 + return true; 1816 + } 1817 + else if(evt.caller is flagButton) { 1818 + vec2i pos(evt.caller.absolutePosition.topLeft.x, 1819 + evt.caller.absolutePosition.botRight.y); 1820 + uint cnt = getEmpireFlagCount(); 1821 + vec2i size(220, ceil(double(cnt)/4.0) * 52.0); 1822 + @popup = ChoosePopup(pos, size, vec2i(48, 48)); 1823 + @popup.callback = this; 1824 + popup.spriteColor = settings.color; 1825 + for(uint i = 0; i < cnt; ++i) 1826 + popup.add(Sprite(getEmpireFlag(i).flag)); 1827 + return true; 1828 + } 1829 + else if(evt.caller is popup) { 1830 + 1831 + } 1832 + break; 1833 + case GUI_Confirmed: 1834 + if(evt.caller is popup) { 1835 + if(popup.colors.length > 0) 1836 + settings.color = getEmpireColor(evt.value).color; 1837 + else if(popup.sprites.length > 0) 1838 + settings.flag = getEmpireFlag(evt.value).flagDef; 1839 + @popup = null; 1840 + submit(); 1841 + return true; 1842 + } 1843 + if(cast<ColorPicker>(evt.caller) !is null) { 1844 + settings.color = cast<ColorPicker>(evt.caller).picked; 1845 + popup.remove(); 1846 + @popup = null; 1847 + submit(); 1848 + } 1849 + break; 1850 + } 1851 + return BaseGuiElement::onGuiEvent(evt); 1852 + } 1853 + 1854 + void apply(EmpireSettings& es) { 1855 + es = settings; 1856 + es.name = name.text; 1857 + es.playerId = playerId; 1858 + 1859 + //if(player || playerId != -1) 1860 + // es.type = ET_Player; 1861 + //else if(es.type == ET_Player) 1862 + es.type = ET_WeaselAI; 1863 + } 1864 + 1865 + void load(EmpireSettings& es) { 1866 + name.text = es.name; 1867 + player = es.type == uint(ET_Player); 1868 + setPlayer(es.playerId); 1869 + settings = es; 1870 + update(); 1871 + } 1872 + 1873 + void draw() { 1874 + Color color = settings.color; 1875 + 1876 + skin.draw(SS_EmpireSetupItem, SF_Normal, AbsolutePosition.padded(-10, 0), color); 1877 + BaseGuiElement::draw(); 1878 + 1879 + if(colorButton.visible) { 1880 + setClip(colorButton.absoluteClipRect); 1881 + drawRectangle(colorButton.absolutePosition.padded(6), color); 1882 + } 1883 + 1884 + auto@ flag = getEmpireFlag(settings.flag); 1885 + if(flag !is null && flagButton.visible) { 1886 + setClip(flagButton.absoluteClipRect); 1887 + flag.flag.draw(recti_centered(flagButton.absolutePosition, 1888 + vec2i(flagButton.size.height, flagButton.size.height)), 1889 + color); 1890 + } 1891 + 1892 + if(difficulty.visible) { 1893 + setClip(difficulty.absoluteClipRect); 1894 + DIFF_SPRITES[settings.difficulty].draw(recti_centered(difficulty.absolutePosition, 1895 + vec2i(difficulty.size.height, difficulty.size.height))); 1896 + } 1897 + 1898 + if(teamButton.visible) { 1899 + setClip(teamButton.absoluteClipRect); 1900 + if(settings.team >= 0) { 1901 + material::TabDiplomacy.draw(recti_centered(teamButton.absolutePosition, 1902 + vec2i(teamButton.size.height, teamButton.size.height))); 1903 + skin.getFont(FT_Small).draw( 1904 + pos=teamButton.absolutePosition, 1905 + text=locale::TEAM, 1906 + horizAlign=0.5, vertAlign=0.0, 1907 + stroke=colors::Black, 1908 + color=colors::White); 1909 + skin.getFont(FT_Medium).draw( 1910 + pos=teamButton.absolutePosition, 1911 + text=toString(settings.team), 1912 + horizAlign=0.5, vertAlign=1.0, 1913 + stroke=colors::Black, 1914 + color=colorFromNumber(settings.team)); 1915 + } 1916 + else { 1917 + shader::SATURATION_LEVEL = 0.f; 1918 + material::TabDiplomacy.draw(recti_centered(teamButton.absolutePosition, 1919 + vec2i(teamButton.size.height, teamButton.size.height)), 1920 + Color(0xffffff80), shader::Desaturate); 1921 + } 1922 + } 1923 + } 1924 +}; 1925 + 1926 +string getAIName(EmpireSettings& settings) { 1927 + string text; 1928 + text = QDIFF_NAMES[clamp(settings.difficulty, 0, 2)]; 1929 + 1930 + if(settings.aiFlags & AIF_Passive != 0) 1931 + text += "|"; 1932 + if(settings.aiFlags & AIF_Aggressive != 0) 1933 + text += "@"; 1934 + if(settings.aiFlags & AIF_Biased != 0) 1935 + text += "^"; 1936 + if(settings.aiFlags & AIF_CheatPrivileged != 0) 1937 + text += "$"; 1938 + if(settings.type == ET_BumAI) 1939 + text += "?"; 1940 + 1941 + int cheatLevel = 0; 1942 + if(settings.cheatWealth > 0) 1943 + cheatLevel += ceil(double(settings.cheatWealth) / 10.0); 1944 + if(settings.cheatStrength > 0) 1945 + cheatLevel += settings.cheatStrength; 1946 + if(settings.cheatAbundance > 0) 1947 + cheatLevel += settings.cheatAbundance; 1948 + 1949 + if(cheatLevel > 0) { 1950 + if(cheatLevel > 3) 1951 + cheatLevel = 3; 1952 + while(cheatLevel > 0) { 1953 + text += "+"; 1954 + cheatLevel -= 1; 1955 + } 1956 + } 1957 + 1958 + return text; 1959 +} 1960 + 1961 +class AIPopup : BaseGuiElement { 1962 + GuiOverlay@ overlay; 1963 + 1964 + GuiListbox@ difficulties; 1965 + 1966 + GuiText@ behaveHeading; 1967 + GuiText@ cheatHeading; 1968 + 1969 + EmpireSetup@ setup; 1970 + 1971 + GuiCheckbox@ aggressive; 1972 + GuiCheckbox@ passive; 1973 + GuiCheckbox@ biased; 1974 + GuiCheckbox@ legacy; 1975 + 1976 + GuiCheckbox@ wealth; 1977 + GuiSpinbox@ wealthAmt; 1978 + GuiCheckbox@ strength; 1979 + GuiSpinbox@ strengthAmt; 1980 + GuiCheckbox@ abundance; 1981 + GuiSpinbox@ abundanceAmt; 1982 + GuiCheckbox@ privileged; 1983 + 1984 + GuiButton@ okButton; 1985 + GuiButton@ applyToAllButton; 1986 + 1987 + AIPopup(IGuiElement@ around, EmpireSetup@ setup) { 1988 + @overlay = GuiOverlay(null); 1989 + overlay.closeSelf = false; 1990 + overlay.fade.a = 0; 1991 + @this.setup = setup; 1992 + 1993 + recti pos = recti_area( 1994 + vec2i(around.absolutePosition.botRight.x, around.absolutePosition.topLeft.y), 1995 + vec2i(600, 200)); 1996 + if(pos.botRight.y > screenSize.y) 1997 + pos += vec2i(0, screenSize.y - pos.botRight.y); 1998 + if(pos.botRight.x > screenSize.x) 1999 + pos += vec2i(screenSize.x - pos.botRight.x, 0); 2000 + 2001 + super(overlay, pos); 2002 + updateAbsolutePosition(); 2003 + setGuiFocus(this); 2004 + 2005 + @difficulties = GuiListbox(this, Alignment(Left+4, Top+4, Left+250, Bottom-4)); 2006 + difficulties.required = true; 2007 + difficulties.itemHeight = 64; 2008 + 2009 + for(uint i = 0; i < 3; ++i) { 2010 + difficulties.addItem(GuiMarkupListText( 2011 + format("[color=$1][font=Medium][stroke=#000]$2[/stroke][/font][/color]\n[color=#aaa][i]$3[/i][/color]", 2012 + toString(QDIFF_COLORS[i]), QDIFF_NAMES[i], QDIFF_DESC[i]))); 2013 + } 2014 + 2015 + @behaveHeading = GuiText(this, Alignment(Left+260, Top+6, Left+260+170, Top+36)); 2016 + behaveHeading.font = FT_Medium; 2017 + behaveHeading.stroke = colors::Black; 2018 + behaveHeading.text = locale::AI_BEHAVIOR; 2019 + 2020 + pos = recti_area(vec2i(260, 36), vec2i(170, 30)); 2021 + 2022 + @aggressive = GuiCheckbox(this, pos, locale::AI_AGGRESSIVE); 2023 + setMarkupTooltip(aggressive, locale::AI_AGGRESSIVE_DESC); 2024 + pos += vec2i(0, 30); 2025 + 2026 + @passive = GuiCheckbox(this, pos, locale::AI_PASSIVE); 2027 + setMarkupTooltip(passive, locale::AI_PASSIVE_DESC); 2028 + pos += vec2i(0, 30); 2029 + 2030 + @biased = GuiCheckbox(this, pos, locale::AI_BIASED); 2031 + setMarkupTooltip(biased, locale::AI_BIASED_DESC); 2032 + pos += vec2i(0, 30); 2033 + 2034 + @legacy = GuiCheckbox(this, pos, locale::AI_LEGACY); 2035 + legacy.textColor = Color(0xaaaaaaff); 2036 + legacy.visible = !hasDLC("Heralds"); 2037 + setMarkupTooltip(legacy, locale::AI_LEGACY_DESC); 2038 + pos += vec2i(0, 30); 2039 + 2040 + @cheatHeading = GuiText(this, Alignment(Left+260+165, Top+6, Right-12, Top+36)); 2041 + cheatHeading.font = FT_Medium; 2042 + cheatHeading.stroke = colors::Black; 2043 + cheatHeading.text = locale::AI_CHEATS; 2044 + 2045 + pos = recti_area(vec2i(260+165, 36), vec2i(170, 30)); 2046 + 2047 + @wealth = GuiCheckbox(this, recti_area(pos.topLeft, vec2i(110, 30)), locale::AI_WEALTH); 2048 + setMarkupTooltip(wealth, locale::AI_WEALTH_DESC); 2049 + @wealthAmt = GuiSpinbox(this, recti_area(pos.topLeft+vec2i(115, 0), vec2i(50, 30)), 10, 0, 1000, 1, 0); 2050 + pos += vec2i(0, 30); 2051 + 2052 + @strength = GuiCheckbox(this, recti_area(pos.topLeft, vec2i(110, 30)), locale::AI_STRENGTH); 2053 + setMarkupTooltip(strength, locale::AI_STRENGTH_DESC); 2054 + @strengthAmt = GuiSpinbox(this, recti_area(pos.topLeft+vec2i(115, 0), vec2i(50, 30)), 1, 0, 100, 1, 0); 2055 + pos += vec2i(0, 30); 2056 + 2057 + @abundance = GuiCheckbox(this, recti_area(pos.topLeft, vec2i(110, 30)), locale::AI_ABUNDANCE); 2058 + setMarkupTooltip(abundance, locale::AI_ABUNDANCE_DESC); 2059 + @abundanceAmt = GuiSpinbox(this, recti_area(pos.topLeft+vec2i(115, 0), vec2i(50, 30)), 1, 0, 100, 1, 0); 2060 + pos += vec2i(0, 30); 2061 + 2062 + @privileged = GuiCheckbox(this, pos, locale::AI_PRIVILEGED); 2063 + setMarkupTooltip(privileged, locale::AI_PRIVILEGED_DESC); 2064 + pos += vec2i(0, 30); 2065 + 2066 + @okButton = GuiButton(this, Alignment(Left+260+165, Bottom-34, Width=70, Height=30), locale::OK); 2067 + @applyToAllButton = GuiButton(this, Alignment(Left+260, Bottom-34, Width=120, Height=30), "Apply to all"); 2068 + 2069 + reset(); 2070 + } 2071 + 2072 + void reset() { 2073 + difficulties.selected = clamp(setup.settings.difficulty, 0, 2); 2074 + aggressive.checked = setup.settings.aiFlags & AIF_Aggressive != 0; 2075 + passive.checked = setup.settings.aiFlags & AIF_Passive != 0; 2076 + biased.checked = setup.settings.aiFlags & AIF_Biased != 0; 2077 + privileged.checked = setup.settings.aiFlags & AIF_CheatPrivileged != 0; 2078 + legacy.checked = setup.settings.type == ET_BumAI; 2079 + if(legacy.checked) 2080 + legacy.visible = true; 2081 + 2082 + wealth.checked = setup.settings.cheatWealth > 0; 2083 + wealthAmt.visible = wealth.checked; 2084 + if(wealth.checked) 2085 + wealthAmt.value = setup.settings.cheatWealth; 2086 + 2087 + strength.checked = setup.settings.cheatStrength > 0; 2088 + strengthAmt.visible = strength.checked; 2089 + if(strength.checked) 2090 + strengthAmt.value = setup.settings.cheatStrength; 2091 + 2092 + abundance.checked = setup.settings.cheatAbundance > 0; 2093 + abundanceAmt.visible = abundance.checked; 2094 + if(abundance.checked) 2095 + abundanceAmt.value = setup.settings.cheatAbundance; 2096 + } 2097 + 2098 + void apply(bool toAll = false) { 2099 + uint flags = 0; 2100 + if(aggressive.checked) 2101 + flags |= AIF_Aggressive; 2102 + if(passive.checked) 2103 + flags |= AIF_Passive; 2104 + if(biased.checked) 2105 + flags |= AIF_Biased; 2106 + if(privileged.checked) 2107 + flags |= AIF_CheatPrivileged; 2108 + 2109 + if(legacy.checked) 2110 + setup.settings.type = ET_BumAI; 2111 + else 2112 + setup.settings.type = ET_WeaselAI; 2113 + 2114 + wealthAmt.visible = wealth.checked; 2115 + if(wealthAmt.visible) 2116 + setup.settings.cheatWealth = wealthAmt.value; 2117 + else 2118 + setup.settings.cheatWealth = 0; 2119 + 2120 + strengthAmt.visible = strength.checked; 2121 + if(strengthAmt.visible) 2122 + setup.settings.cheatStrength = strengthAmt.value; 2123 + else 2124 + setup.settings.cheatStrength = 0; 2125 + 2126 + abundanceAmt.visible = abundance.checked; 2127 + if(abundanceAmt.visible) 2128 + setup.settings.cheatAbundance = abundanceAmt.value; 2129 + else 2130 + setup.settings.cheatAbundance = 0; 2131 + 2132 + if (toAll) { 2133 + for (uint i = 0; i < setup.ng.empires.length; i++) { 2134 + setup.ng.empires[i].settings.difficulty = difficulties.selected; 2135 + setup.ng.empires[i].settings.aiFlags = flags; 2136 + setup.ng.empires[i].submit(); 2137 + } 2138 + } else { 2139 + setup.settings.difficulty = difficulties.selected; 2140 + setup.settings.aiFlags = flags; 2141 + setup.submit(); 2142 + } 2143 + } 2144 + 2145 + bool onGuiEvent(const GuiEvent& evt) override { 2146 + if(evt.type == GUI_Changed) { 2147 + if(evt.caller is passive) { 2148 + if(passive.checked) 2149 + aggressive.checked = false; 2150 + apply(); 2151 + return true; 2152 + } 2153 + if(evt.caller is aggressive) { 2154 + if(aggressive.checked) 2155 + passive.checked = false; 2156 + apply(); 2157 + return true; 2158 + } 2159 + apply(); 2160 + } 2161 + if(evt.type == GUI_Clicked) { 2162 + if(evt.caller is okButton) { 2163 + apply(); 2164 + remove(); 2165 + return true; 2166 + } else if(evt.caller is applyToAllButton) { 2167 + apply(true); 2168 + remove(); 2169 + return true; 2170 + } 2171 + } 2172 + return BaseGuiElement::onGuiEvent(evt); 2173 + } 2174 + 2175 + void remove() { 2176 + overlay.remove(); 2177 + @overlay = null; 2178 + BaseGuiElement::remove(); 2179 + } 2180 + 2181 + void draw() override { 2182 + clearClip(); 2183 + skin.draw(SS_Panel, SF_Normal, AbsolutePosition); 2184 + BaseGuiElement::draw(); 2185 + } 2186 +}; 2187 + 2188 +class ChoosePopup : GuiIconGrid { 2189 + GuiOverlay@ overlay; 2190 + int extraHeight = 0; 2191 + 2192 + Color spriteColor; 2193 + array<Color> colors; 2194 + array<Sprite> sprites; 2195 + 2196 + ChoosePopup(const vec2i& pos, const vec2i& size, const vec2i& itemSize) { 2197 + @overlay = GuiOverlay(null); 2198 + overlay.closeSelf = false; 2199 + overlay.fade.a = 0; 2200 + super(overlay, recti_area(pos, size)); 2201 + horizAlign = 0.5; 2202 + vertAlign = 0.0; 2203 + iconSize = itemSize; 2204 + updateAbsolutePosition(); 2205 + } 2206 + 2207 + bool onGuiEvent(const GuiEvent& evt) override { 2208 + if(evt.caller is this && evt.type == GUI_Clicked) { 2209 + if(hovered != -1) 2210 + emitConfirmed(uint(hovered)); 2211 + overlay.close(); 2212 + return true; 2213 + } 2214 + return GuiIconGrid::onGuiEvent(evt); 2215 + } 2216 + 2217 + void remove() { 2218 + overlay.remove(); 2219 + @overlay = null; 2220 + GuiIconGrid::remove(); 2221 + } 2222 + 2223 + void add(const Color& col) { 2224 + colors.insertLast(col); 2225 + } 2226 + 2227 + void add(const Sprite& sprt) { 2228 + sprites.insertLast(sprt); 2229 + } 2230 + 2231 + uint get_length() override { 2232 + return max(colors.length, sprites.length); 2233 + } 2234 + 2235 + void drawElement(uint index, const recti& pos) override { 2236 + if(uint(hovered) == index) 2237 + drawRectangle(pos, Color(0xffffff30)); 2238 + if(index < colors.length) 2239 + drawRectangle(pos.padded(5), colors[index]); 2240 + if(index < sprites.length) 2241 + sprites[index].draw(pos, spriteColor); 2242 + } 2243 + 2244 + void draw() override { 2245 + clearClip(); 2246 + skin.draw(SS_Panel, SF_Normal, AbsolutePosition.padded(0,0,0,-extraHeight)); 2247 + GuiIconGrid::draw(); 2248 + } 2249 +}; 2250 + 2251 +class ColorPicker : BaseGuiElement { 2252 + Color picked; 2253 + bool pressed = false; 2254 + 2255 + ColorPicker(IGuiElement@ parent, const recti& pos) { 2256 + super(parent, pos); 2257 + updateAbsolutePosition(); 2258 + } 2259 + 2260 + void draw() { 2261 + shader::HSV_VALUE = 1.f; 2262 + shader::HSV_SAT_START = 0.5f; 2263 + shader::HSV_SAT_END = 1.f; 2264 + drawRectangle(AbsolutePosition, material::HSVPalette, Color()); 2265 + if(AbsolutePosition.isWithin(mousePos)) { 2266 + clearClip(); 2267 + recti area = recti_area(mousePos-vec2i(10), vec2i(20)); 2268 + drawRectangle(area.padded(-1), colors::Black); 2269 + drawRectangle(area, getColor(mousePos-AbsolutePosition.topLeft)); 2270 + } 2271 + BaseGuiElement::draw(); 2272 + } 2273 + 2274 + Color getColor(vec2i offset) { 2275 + Colorf col; 2276 + float hue = float(offset.x) / float(AbsolutePosition.width) * 360.f; 2277 + float sat = (1.f - float(offset.y) / float(AbsolutePosition.height)) * 0.5f + 0.5f; 2278 + col.fromHSV(hue, sat, 1.f); 2279 + col.a = 1.f; 2280 + return Color(col); 2281 + } 2282 + 2283 + bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) { 2284 + if(event.type == MET_Button_Down || (event.type == MET_Moved && pressed)) { 2285 + pressed = true; 2286 + picked = getColor(mousePos - AbsolutePosition.topLeft); 2287 + 2288 + GuiEvent evt; 2289 + @evt.caller = this; 2290 + evt.type = GUI_Changed; 2291 + onGuiEvent(evt); 2292 + return true; 2293 + } 2294 + else if(pressed && event.type == MET_Button_Up) { 2295 + pressed = false; 2296 + emitConfirmed(); 2297 + return true; 2298 + } 2299 + return BaseGuiElement::onMouseEvent(event, source); 2300 + } 2301 +}; 2302 + 2303 +class PortraitChooser : GuiIconGrid { 2304 + array<Sprite> sprites; 2305 + uint selected = 0; 2306 + Color selectedColor; 2307 + 2308 + PortraitChooser(IGuiElement@ parent, Alignment@ align, const vec2i& itemSize) { 2309 + super(parent, align); 2310 + horizAlign = 0.5; 2311 + vertAlign = 0.0; 2312 + iconSize = itemSize; 2313 + updateAbsolutePosition(); 2314 + } 2315 + 2316 + void add(const Sprite& sprt) { 2317 + sprites.insertLast(sprt); 2318 + } 2319 + 2320 + uint get_length() override { 2321 + return sprites.length; 2322 + } 2323 + 2324 + void drawElement(uint index, const recti& pos) override { 2325 + if(selected == index) 2326 + drawRectangle(pos, selectedColor); 2327 + if(uint(hovered) == index) 2328 + drawRectangle(pos, Color(0xffffff30)); 2329 + if(index < sprites.length) 2330 + sprites[index].draw(pos); 2331 + } 2332 +}; 2333 + 2334 +class ShipsetChooser : GuiIconGrid { 2335 + array<const Shipset@> items; 2336 + uint selected = 0; 2337 + Color selectedColor; 2338 + 2339 + ShipsetChooser(IGuiElement@ parent, Alignment@ align, const vec2i& itemSize) { 2340 + super(parent, align); 2341 + horizAlign = 0.5; 2342 + vertAlign = 0.0; 2343 + iconSize = itemSize; 2344 + updateAbsolutePosition(); 2345 + } 2346 + 2347 + void add(const Shipset@ shipset) { 2348 + items.insertLast(shipset); 2349 + } 2350 + 2351 + uint get_length() override { 2352 + return items.length; 2353 + } 2354 + 2355 + void drawElement(uint index, const recti& pos) override { 2356 + if(selected == index) { 2357 + Color col = selectedColor; 2358 + col.a = 0x15; 2359 + drawRectangle(pos, col); 2360 + } 2361 + if(uint(hovered) == index) 2362 + drawRectangle(pos, Color(0xffffff15)); 2363 + if(index < items.length) { 2364 + const Shipset@ shipset = items[index]; 2365 + const Hull@ hull = shipset.hulls[0]; 2366 + if(hull !is null) { 2367 + quaterniond rot; 2368 + rot = quaterniond_fromAxisAngle(vec3d_front(), -0.9); 2369 + rot *= quaterniond_fromAxisAngle(vec3d_up(), 0.6); 2370 + rot *= quaterniond_fromAxisAngle(vec3d_right(), -0.5); 2371 + setClip(pos); 2372 + Color lightColor = colors::White; 2373 + if(selected == index) { 2374 + NODE_COLOR = Colorf(selectedColor); 2375 + lightColor = selectedColor; 2376 + } 2377 + else 2378 + NODE_COLOR = Colorf(1.f, 1.f, 1.f, 1.f); 2379 + drawLitModel(hull.model, hull.material, pos+vec2i(-4,0), rot, 1.9, lightColor=lightColor); 2380 + clearClip(); 2381 + } 2382 + 2383 + const Font@ ft = skin.getFont(FT_Bold); 2384 + if(selected == index || uint(hovered) == index) 2385 + ft.draw(text=shipset.name, pos=pos.padded(0,4), 2386 + horizAlign=0.5, vertAlign=0.0, stroke=colors::Black, 2387 + color=(selected == index ? selectedColor : colors::White)); 2388 + } 2389 + } 2390 +}; 2391 + 2392 +class WeaponSkinChooser : GuiIconGrid { 2393 + array<const EmpireWeaponSkin@> items; 2394 + uint selected = 0; 2395 + Color selectedColor; 2396 + 2397 + WeaponSkinChooser(IGuiElement@ parent, Alignment@ align, const vec2i& itemSize) { 2398 + super(parent, align); 2399 + horizAlign = 0.5; 2400 + vertAlign = 0.0; 2401 + iconSize = itemSize; 2402 + updateAbsolutePosition(); 2403 + } 2404 + 2405 + void add(const EmpireWeaponSkin@ it) { 2406 + items.insertLast(it); 2407 + } 2408 + 2409 + uint get_length() override { 2410 + return items.length; 2411 + } 2412 + 2413 + void drawElement(uint index, const recti& pos) override { 2414 + if(selected == index) { 2415 + Color col = selectedColor; 2416 + col.a = 0x15; 2417 + drawRectangle(pos, col); 2418 + } 2419 + if(uint(hovered) == index) 2420 + drawRectangle(pos, Color(0xffffff15)); 2421 + if(index < items.length) 2422 + items[index].icon.draw(pos); 2423 + } 2424 +}; 2425 + 2426 +class TraitDisplay : BaseGuiElement { 2427 + const Trait@ trait; 2428 + GuiSprite@ icon; 2429 + GuiMarkupText@ name; 2430 + GuiMarkupText@ description; 2431 + GuiText@ points; 2432 + GuiText@ conflicts; 2433 + GuiCheckbox@ check; 2434 + bool hovered = false; 2435 + bool conflict = false; 2436 + 2437 + TraitDisplay(IGuiElement@ parent) { 2438 + super(parent, recti()); 2439 + 2440 + @icon = GuiSprite(this, Alignment(Left+20, Top+12, Left+52, Bottom-12)); 2441 + 2442 + @name = GuiMarkupText(this, Alignment(Left+65, Top+8, Right-168, Top+38)); 2443 + name.defaultFont = FT_Medium; 2444 + name.defaultStroke = colors::Black; 2445 + 2446 + @description = GuiMarkupText(this, Alignment(Left+124, Top+34, Right-168, Bottom-8)); 2447 + 2448 + @conflicts = GuiText(this, Alignment(Right-360, Top+8, Right-56, Bottom-8)); 2449 + conflicts.vertAlign = 0.1; 2450 + conflicts.horizAlign = 1.0; 2451 + 2452 + @points = GuiText(this, Alignment(Right-160, Top+8, Right-56, Bottom-8)); 2453 + points.horizAlign = 1.0; 2454 + points.font = FT_Subtitle; 2455 + 2456 + @check = GuiCheckbox(this, Alignment(Right-48, Top+0.5f-20, Right-8, Top+0.5f+20), ""); 2457 + } 2458 + 2459 + bool onGuiEvent(const GuiEvent& evt) override { 2460 + switch(evt.type) { 2461 + case GUI_Mouse_Entered: 2462 + hovered = true; 2463 + break; 2464 + case GUI_Mouse_Left: 2465 + hovered = false; 2466 + break; 2467 + case GUI_Changed: 2468 + if(evt.caller is check) { 2469 + check.checked = !check.checked; 2470 + emitClicked(); 2471 + return true; 2472 + } 2473 + break; 2474 + } 2475 + return BaseGuiElement::onGuiEvent(evt); 2476 + } 2477 + 2478 + bool onMouseEvent(const MouseEvent& evt, IGuiElement@ caller) override { 2479 + switch(evt.type) { 2480 + case MET_Button_Down: 2481 + if(evt.button == 0) 2482 + return true; 2483 + break; 2484 + case MET_Button_Up: 2485 + if(evt.button == 0) { 2486 + emitClicked(); 2487 + return true; 2488 + } 2489 + break; 2490 + } 2491 + return BaseGuiElement::onMouseEvent(evt, caller); 2492 + } 2493 + 2494 + void set(const Trait@ trait, bool selected, bool conflict) { 2495 + @this.trait = trait; 2496 + this.conflict = conflict; 2497 + description.text = trait.description; 2498 + icon.desc = trait.icon; 2499 + name.defaultColor = trait.color; 2500 + 2501 + if(trait.gives > 0) { 2502 + points.text = format(locale::RACE_POINTS_POS, toString(trait.gives)); 2503 + points.color = colors::Green; 2504 + points.visible = true; 2505 + } 2506 + else if(trait.cost > 0) { 2507 + points.text = format(locale::RACE_POINTS_NEG, toString(trait.cost)); 2508 + points.color = colors::Red; 2509 + points.visible = true; 2510 + } 2511 + else { 2512 + points.text = locale::RACE_POINTS_NEU; 2513 + points.color = Color(0xaaaaaaff); 2514 + points.visible = false; 2515 + } 2516 + 2517 + bool displayConflicts = false; 2518 + if(trait.conflicts.length > 0) { 2519 + if(conflict) { 2520 + conflicts.color = colors::Red; 2521 + conflicts.font = FT_Bold; 2522 + conflicts.vertAlign = 0.2; 2523 + } 2524 + else { 2525 + conflicts.color = Color(0xaaaaaaff); 2526 + conflicts.font = FT_Italic; 2527 + conflicts.vertAlign = 0.1; 2528 + } 2529 + string str = locale::CONFLICTS+" "; 2530 + for(uint i = 0, cnt = trait.conflicts.length; i < cnt; ++i) { 2531 + if(!trait.conflicts[i].available) 2532 + continue; 2533 + if(i != 0) 2534 + str += ", "; 2535 + str += trait.conflicts[i].name; 2536 + displayConflicts = true; 2537 + } 2538 + 2539 + conflicts.text = str; 2540 + } 2541 + if(displayConflicts) { 2542 + conflicts.visible = true; 2543 + points.vertAlign = 0.7; 2544 + } 2545 + else { 2546 + conflicts.visible = false; 2547 + points.vertAlign = 0.5; 2548 + } 2549 + 2550 + if(trait.unique.length != 0) { 2551 + check.style = SS_Radiobox; 2552 + if(description.alignment.right.pixels != 52) { 2553 + description.alignment.right.pixels = 52; 2554 + description.updateAbsolutePosition(); 2555 + } 2556 + } 2557 + else { 2558 + check.style = SS_Checkbox; 2559 + if(description.alignment.right.pixels != 168) { 2560 + description.alignment.right.pixels = 168; 2561 + description.updateAbsolutePosition(); 2562 + } 2563 + } 2564 + 2565 + name.text = trait.name; 2566 + check.checked = selected; 2567 + } 2568 + 2569 + void draw() { 2570 + if(check.checked) 2571 + skin.draw(SS_Glow, SF_Normal, AbsolutePosition, trait.color); 2572 + skin.draw(SS_Panel, SF_Normal, AbsolutePosition.padded(4), trait.color); 2573 + if(hovered) 2574 + drawRectangle(AbsolutePosition.padded(8), Color(0xffffff10)); 2575 + BaseGuiElement::draw(); 2576 + } 2577 +}; 2578 + 2579 +class SaveRaceDialog : SaveDialog { 2580 + EmpireSettings settings; 2581 + EmpireSetup@ setup; 2582 + 2583 + SaveRaceDialog(IGuiElement@ bind, EmpireSettings@ settings, EmpireSetup@ setup) { 2584 + this.settings = settings; 2585 + @this.setup = setup; 2586 + super(bind, modProfile["races"], settings.raceName+".race"); 2587 + } 2588 + 2589 + void clickConfirm() override { 2590 + exportRace(settings, path); 2591 + } 2592 +}; 2593 + 2594 +class LoadRaceDialog : LoadDialog { 2595 + EmpireSettings settings; 2596 + EmpireSetup@ setup; 2597 + TraitsWindow@ win; 2598 + 2599 + LoadRaceDialog(TraitsWindow@ win, EmpireSettings@ settings, EmpireSetup@ setup) { 2600 + this.settings = settings; 2601 + @this.setup = setup; 2602 + @this.win = win; 2603 + super(win, modProfile["races"]); 2604 + } 2605 + 2606 + void clickConfirm() override { 2607 + importRace(setup.settings, path); 2608 + if(win !is null) 2609 + win.update(); 2610 + setup.submit(); 2611 + } 2612 +}; 2613 + 2614 +class TraitElement : GuiListElement { 2615 + const Trait@ trait; 2616 + 2617 + void draw(GuiListbox@ ele, uint flags, const recti& absPos) { 2618 + recti iconPos = recti_area(absPos.topLeft+vec2i(10, 5), vec2i(absPos.height-10, absPos.height-10)); 2619 + trait.icon.draw(iconPos); 2620 + 2621 + recti textPos = absPos.padded(absPos.height + 10, 0, 10, 4); 2622 + ele.skin.getFont(FT_Medium).draw( 2623 + text=trait.name, pos=textPos); 2624 + } 2625 + 2626 + string get_tooltipText() { 2627 + return format("[color=$1][b]$2[/b][/color]\n$3", 2628 + toString(trait.color), trait.name, trait.description); 2629 + } 2630 +}; 2631 + 2632 +class TraitsWindow : BaseGuiElement { 2633 + GuiOverlay@ overlay; 2634 + EmpireSetup@ setup; 2635 + 2636 + GuiBackgroundPanel@ bg; 2637 + 2638 + GuiListbox@ categories; 2639 + array<const TraitCategory@> usedCategories; 2640 + 2641 + GuiPanel@ profilePanel; 2642 + 2643 + GuiText@ nameLabel; 2644 + GuiTextbox@ name; 2645 + 2646 + GuiText@ portraitLabel; 2647 + PortraitChooser@ portrait; 2648 + 2649 + GuiText@ shipsetLabel; 2650 + ShipsetChooser@ shipset; 2651 + 2652 + GuiText@ weaponSkinLabel; 2653 + WeaponSkinChooser@ weaponSkin; 2654 + 2655 + GuiText@ traitsLabel; 2656 + GuiListbox@ traitList; 2657 + 2658 + GuiText@ pointsLabel; 2659 + 2660 + GuiPanel@ traitPanel; 2661 + GuiText@ noTraits; 2662 + array<TraitDisplay@> traits; 2663 + 2664 + GuiButton@ acceptButton; 2665 + GuiButton@ saveButton; 2666 + GuiButton@ loadButton; 2667 + 2668 + TraitsWindow(EmpireSetup@ setup) { 2669 + @this.setup = setup; 2670 + @overlay = GuiOverlay(null); 2671 + overlay.closeSelf = false; 2672 + super(overlay, Alignment(Left+0.11f, Top+0.11f, Right-0.11f, Bottom-0.11f)); 2673 + updateAbsolutePosition(); 2674 + 2675 + @bg = GuiBackgroundPanel(this, Alignment().fill()); 2676 + bg.titleColor = Color(0xff8000ff); 2677 + bg.title = locale::CUSTOMIZE_RACE; 2678 + 2679 + @categories = GuiListbox(bg, Alignment(Left+4, Top+32, Left+250, Bottom-4)); 2680 + categories.itemHeight = 44; 2681 + categories.style = SS_PlainOverlay; 2682 + categories.itemStyle = SS_TabButton; 2683 + categories.addItem(GuiMarkupListText(locale::RACE_PROFILE)); 2684 + categories.required = true; 2685 + 2686 + for(uint i = 0, cnt = getTraitCategoryCount(); i < cnt; ++i) { 2687 + auto@ cat = getTraitCategory(i); 2688 + bool hasTraits = false; 2689 + for(uint n = 0, ncnt = getTraitCount(); n < ncnt; ++n) { 2690 + if(getTrait(n).category is cat && getTrait(n).available && getTrait(n).hasDLC) { 2691 + hasTraits = true; 2692 + break; 2693 + } 2694 + } 2695 + if(hasTraits) { 2696 + categories.addItem(GuiMarkupListText(cat.name)); 2697 + usedCategories.insertLast(cat); 2698 + } 2699 + } 2700 + 2701 + @acceptButton = GuiButton(bg, Alignment(Right-140, Bottom-40, Right-3, Bottom-3), locale::ACCEPT); 2702 + @loadButton = GuiButton(bg, Alignment(Right-274, Bottom-40, Right-154, Bottom-3), locale::LOAD); 2703 + @saveButton = GuiButton(bg, Alignment(Right-400, Bottom-40, Right-280, Bottom-3), locale::SAVE); 2704 + @pointsLabel = GuiText(bg, Alignment(Left+264, Bottom-40, Right-410, Bottom-3)); 2705 + pointsLabel.font = FT_Medium; 2706 + 2707 + Alignment panelAlign(Left+258, Top+32, Right-4, Bottom-40); 2708 + 2709 + @profilePanel = GuiPanel(bg, panelAlign); 2710 + @traitPanel = GuiPanel(bg, panelAlign); 2711 + traitPanel.visible = false; 2712 + 2713 + int y = 8; 2714 + 2715 + @nameLabel = GuiText(profilePanel, Alignment(Left+12, Top+y, Left+200, Top+y+30), locale::RACE_NAME, FT_Bold); 2716 + @name = GuiTextbox(profilePanel, Alignment(Left+200, Top+y, Right-12, Top+y+30), setup.settings.raceName); 2717 + y += 38; 2718 + 2719 + int h = 80 + (getEmpirePortraitCount() / ((size.width - 200) / 70)) * 80; 2720 + @portraitLabel = GuiText(profilePanel, Alignment(Left+12, Top+y, Left+200, Top+y+30), locale::PORTRAIT, FT_Bold); 2721 + @portrait = PortraitChooser(profilePanel, Alignment(Left+200, Top+y, Right-12, Top+y+h), vec2i(70, 70)); 2722 + portrait.selectedColor = setup.settings.color; 2723 + 2724 + portrait.selected = randomi(0, getEmpirePortraitCount()-1); 2725 + portrait.horizAlign = 0.0; 2726 + for(uint i = 0, cnt = getEmpirePortraitCount(); i < cnt; ++i) { 2727 + auto@ img = getEmpirePortrait(i); 2728 + portrait.add(Sprite(img.portrait)); 2729 + if(img.ident == setup.settings.portrait) 2730 + portrait.selected = i; 2731 + } 2732 + y += h+8; 2733 + 2734 + h = 80 + (getShipsetCount() / ((size.width - 200) / 150)) * 80; 2735 + @shipsetLabel = GuiText(profilePanel, Alignment(Left+12, Top+y, Left+200, Top+y+30), locale::SHIPSET, FT_Bold); 2736 + @shipset = ShipsetChooser(profilePanel, Alignment(Left+200, Top+y, Right-12, Top+y+h), vec2i(150, 70)); 2737 + shipset.selectedColor = setup.settings.color; 2738 + shipset.selected = 0; 2739 + shipset.horizAlign = 0.0; 2740 + for(uint i = 0, cnt = getShipsetCount(); i < cnt; ++i) { 2741 + auto@ ss = getShipset(i); 2742 + if(ss.available && (ss.dlc.length == 0 || hasDLC(ss.dlc))) 2743 + shipset.add(ss); 2744 + if(ss.ident == setup.settings.shipset) 2745 + shipset.selected = shipset.length-1; 2746 + } 2747 + y += h+8; 2748 + 2749 + @weaponSkinLabel = GuiText(profilePanel, Alignment(Left+12, Top+y, Left+200, Top+y+30), locale::WEAPON_SKIN, FT_Bold); 2750 + @weaponSkin = WeaponSkinChooser(profilePanel, Alignment(Left+200, Top+y, Right-12, Top+y+80), vec2i(120, 70)); 2751 + weaponSkin.selectedColor = setup.settings.color; 2752 + weaponSkin.selected = 0; 2753 + weaponSkin.horizAlign = 0.0; 2754 + for(uint i = 0, cnt = getEmpireWeaponSkinCount(); i < cnt; ++i) { 2755 + auto@ skin = getEmpireWeaponSkin(i); 2756 + weaponSkin.add(skin); 2757 + if(skin.ident == setup.settings.effectorSkin) 2758 + weaponSkin.selected = weaponSkin.length-1; 2759 + } 2760 + y += 88; 2761 + 2762 + @traitsLabel = GuiText(profilePanel, Alignment(Left+12, Top+y, Left+200, Top+y+30), locale::TRAITS, FT_Bold); 2763 + @traitList = GuiListbox(profilePanel, Alignment(Left+200, Top+y, Right-12, Bottom-8)); 2764 + traitList.itemStyle = SS_StaticListboxItem; 2765 + traitList.itemHeight = 50; 2766 + addLazyMarkupTooltip(traitList); 2767 + @noTraits = GuiText(profilePanel, Alignment(Left+240, Top+y+10, Right-12, Top+y+50), locale::NO_TRAITS); 2768 + noTraits.color = Color(0xaaaaaaff); 2769 + noTraits.vertAlign = 0.0; 2770 + y += 58; 2771 + 2772 + update(); 2773 + updateAbsolutePosition(); 2774 + } 2775 + 2776 + void update() { 2777 + int sel = categories.selected; 2778 + profilePanel.visible = sel == 0; 2779 + traitPanel.visible = sel != 0; 2780 + 2781 + uint index = 0; 2782 + const TraitCategory@ cat; 2783 + if(sel > 0) 2784 + @cat = usedCategories[sel - 1]; 2785 + 2786 + int points = STARTING_TRAIT_POINTS; 2787 + for(uint i = 0, cnt = setup.settings.traits.length; i < cnt; ++i) { 2788 + points += setup.settings.traits[i].gives; 2789 + points -= setup.settings.traits[i].cost; 2790 + } 2791 + 2792 + if(traitPanel.visible) { 2793 + int y = 0; 2794 + array<const Trait@> list; 2795 + for(uint i = 0, cnt = getTraitCount(); i < cnt; ++i) { 2796 + auto@ trait = getTrait(i); 2797 + if(cat !is null && cat !is trait.category) 2798 + continue; 2799 + if(!setup.player && !trait.aiSupport) 2800 + continue; 2801 + if(!trait.available) 2802 + continue; 2803 + if(!trait.hasDLC) 2804 + continue; 2805 + list.insertLast(trait); 2806 + } 2807 + list.sortAsc(); 2808 + 2809 + for(uint i = 0, cnt = list.length; i < cnt; ++i) { 2810 + auto@ trait = list[i]; 2811 + TraitDisplay@ disp; 2812 + if(index < traits.length) { 2813 + @disp = traits[index]; 2814 + } 2815 + else { 2816 + @disp = TraitDisplay(traitPanel); 2817 + traits.insertLast(disp); 2818 + } 2819 + 2820 + disp.set(trait, setup.settings.hasTrait(trait), trait.hasConflicts(setup.settings.traits)); 2821 + disp.alignment.set(Left, Top+y, Right, Top+y+140); 2822 + disp.updateAbsolutePosition(); 2823 + int needH = disp.description.renderer.height+48; 2824 + if(needH != 140) { 2825 + disp.alignment.set(Left, Top+y, Right, Top+y+needH); 2826 + disp.updateAbsolutePosition(); 2827 + } 2828 + 2829 + ++index; 2830 + y += needH; 2831 + } 2832 + 2833 + for(uint i = index, cnt = traits.length; i < cnt; ++i) 2834 + traits[i].remove(); 2835 + traits.length = index; 2836 + traitPanel.updateAbsolutePosition(); 2837 + } 2838 + 2839 + if(profilePanel.visible) { 2840 + uint cnt = setup.settings.traits.length; 2841 + traitList.removeItemsFrom(cnt); 2842 + for(uint i = 0; i < cnt; ++i) { 2843 + auto@ item = cast<TraitElement>(traitList.getItemElement(i)); 2844 + if(item is null) { 2845 + @item = TraitElement(); 2846 + traitList.addItem(item); 2847 + } 2848 + 2849 + @item.trait = setup.settings.traits[i]; 2850 + } 2851 + noTraits.visible = cnt == 0; 2852 + } 2853 + 2854 + if(points > 0) { 2855 + pointsLabel.color = colors::Green; 2856 + pointsLabel.text = format(locale::RACE_POINTS_AVAIL_POS, toString(points)); 2857 + pointsLabel.visible = true; 2858 + } 2859 + else if(points < 0) { 2860 + pointsLabel.color = colors::Red; 2861 + pointsLabel.text = format(locale::RACE_POINTS_AVAIL_NEG, toString(-points)); 2862 + pointsLabel.visible = true; 2863 + } 2864 + else { 2865 + pointsLabel.color = Color(0xaaaaaaff); 2866 + pointsLabel.text = format(locale::RACE_POINTS_AVAIL_POS, toString(points)); 2867 + pointsLabel.visible = false; 2868 + } 2869 + 2870 + if(points >= 0 && !setup.settings.hasTraitConflicts()) 2871 + acceptButton.color = colors::Green; 2872 + else 2873 + acceptButton.color = colors::Red; 2874 + } 2875 + 2876 + bool onGuiEvent(const GuiEvent& evt) override { 2877 + if(evt.caller is acceptButton) { 2878 + if(evt.type == GUI_Clicked) { 2879 + overlay.close(); 2880 + return true; 2881 + } 2882 + } 2883 + if(evt.caller is saveButton) { 2884 + if(evt.type == GUI_Clicked) { 2885 + SaveRaceDialog(this, setup.settings, setup); 2886 + return true; 2887 + } 2888 + } 2889 + else if(evt.caller is loadButton) { 2890 + if(evt.type == GUI_Clicked) { 2891 + LoadRaceDialog(this, setup.settings, setup); 2892 + return true; 2893 + } 2894 + } 2895 + if(evt.type == GUI_Clicked) { 2896 + if(evt.caller is portrait) { 2897 + int hov = portrait.hovered; 2898 + if(hov >= 0) { 2899 + setup.settings.portrait = getEmpirePortrait(hov).ident; 2900 + portrait.selected = hov; 2901 + } 2902 + setup.submit(); 2903 + return true; 2904 + } 2905 + if(evt.caller is shipset) { 2906 + int hov = shipset.hovered; 2907 + if(hov >= 0) { 2908 + setup.settings.shipset = shipset.items[hov].ident; 2909 + shipset.selected = hov; 2910 + } 2911 + setup.submit(); 2912 + return true; 2913 + } 2914 + if(evt.caller is weaponSkin) { 2915 + int hov = weaponSkin.hovered; 2916 + if(hov >= 0) { 2917 + setup.settings.effectorSkin = weaponSkin.items[hov].ident; 2918 + weaponSkin.selected = hov; 2919 + } 2920 + setup.submit(); 2921 + return true; 2922 + } 2923 + 2924 + auto@ disp = cast<TraitDisplay>(evt.caller); 2925 + if(disp !is null) { 2926 + if(disp.trait.unique.length != 0) 2927 + setup.settings.chooseTrait(disp.trait); 2928 + else if(setup.settings.hasTrait(disp.trait)) 2929 + setup.settings.removeTrait(disp.trait); 2930 + else 2931 + setup.settings.addTrait(disp.trait); 2932 + update(); 2933 + setup.submit(); 2934 + } 2935 + } 2936 + if(evt.type == GUI_Changed) { 2937 + if(evt.caller is name) { 2938 + setup.settings.raceName = name.text; 2939 + setup.submit(); 2940 + return true; 2941 + } 2942 + if(evt.caller is categories) { 2943 + update(); 2944 + return true; 2945 + } 2946 + } 2947 + return BaseGuiElement::onGuiEvent(evt); 2948 + } 2949 + 2950 + void draw() override { 2951 + BaseGuiElement::draw(); 2952 + } 2953 +}; 2954 + 2955 +class GalaxySetup : BaseGuiElement { 2956 + Map@ mp; 2957 + NewGame@ ng; 2958 + GuiText@ name; 2959 + 2960 + GuiText@ timesLabel; 2961 + GuiSpinbox@ timesBox; 2962 + 2963 + GuiPanel@ settings; 2964 + 2965 + GuiButton@ removeButton; 2966 + GuiButton@ hwButton; 2967 + GuiSprite@ hwX; 2968 + 2969 + GalaxySetup(NewGame@ menu, Alignment@ align, Map@ fromMap) { 2970 + super(menu.galaxyPanel, align); 2971 + @mp = fromMap.create(); 2972 + @ng = menu; 2973 + 2974 + @name = GuiText(this, Alignment(Left+6, Top+5, Right-262, Height=28)); 2975 + name.text = mp.name; 2976 + name.font = FT_Medium; 2977 + name.color = mp.color; 2978 + name.stroke = colors::Black; 2979 + 2980 + @timesBox = GuiSpinbox(this, Alignment(Right-190, Top+7, Width=52, Height=22), 1.0); 2981 + timesBox.min = 1.0; 2982 + timesBox.max = 100.0; 2983 + timesBox.decimals = 0; 2984 + timesBox.color = Color(0xffffff60); 2985 + 2986 + @timesLabel = GuiText(this, Alignment(Right-135, Top+7, Width=25, Height=22), "x"); 2987 + 2988 + timesBox.visible = !mp.isUnique; 2989 + timesLabel.visible = !mp.isUnique; 2990 + 2991 + @removeButton = GuiButton(this, Alignment(Right-84, Top+4, Right-25, Top+34)); 2992 + removeButton.setIcon(icons::Remove); 2993 + removeButton.color = colors::Red; 2994 + 2995 + @hwButton = GuiButton(this, Alignment(Right-230, Top+5, Width=26, Height=26)); 2996 + hwButton.setIcon(Sprite(spritesheet::PlanetType, 2, Color(0xffffffaa)), padding=0); 2997 + hwButton.toggleButton = true; 2998 + hwButton.pressed = false; 2999 + hwButton.style = SS_IconButton; 3000 + hwButton.color = Color(0xff0000ff); 3001 + setMarkupTooltip(hwButton, locale::NGTT_MAP_HW); 3002 + @hwX = GuiSprite(hwButton, Alignment(), Sprite(spritesheet::QuickbarIcons, 3, Color(0xffffff80))); 3003 + hwX.visible = false; 3004 + 3005 + @settings = GuiPanel(this, 3006 + Alignment(Left, Top+42, Right, Bottom-4)); 3007 + mp.create(settings); 3008 + } 3009 + 3010 + void setHomeworlds(bool value) { 3011 + hwButton.pressed = !value; 3012 + hwX.visible = hwButton.pressed; 3013 + if(hwButton.pressed) 3014 + hwButton.fullIcon.color = Color(0xffffffff); 3015 + else 3016 + hwButton.fullIcon.color = Color(0xffffffaa); 3017 + } 3018 + 3019 + void apply(MapSettings& set) { 3020 + set.map_id = mp.id; 3021 + set.galaxyCount = timesBox.value; 3022 + @set.parent = ng.settings; 3023 + set.allowHomeworlds = !hwButton.pressed; 3024 + mp.apply(set); 3025 + } 3026 + 3027 + void load(MapSettings& set) { 3028 + auto@ _map = getMap(set.map_id); 3029 + if(getClass(mp) !is getClass(_map)) 3030 + @mp = cast<Map>(getClass(_map).create()); 3031 + timesBox.value = set.galaxyCount; 3032 + 3033 + hwButton.pressed = !set.allowHomeworlds; 3034 + hwX.visible = hwButton.pressed; 3035 + if(hwButton.pressed) 3036 + hwButton.fullIcon.color = Color(0xffffffff); 3037 + else 3038 + hwButton.fullIcon.color = Color(0xffffffaa); 3039 + 3040 + mp.load(set); 3041 + } 3042 + 3043 + bool onGuiEvent(const GuiEvent& evt) { 3044 + switch(evt.type) { 3045 + case GUI_Clicked: 3046 + if(evt.caller is removeButton) { 3047 + ng.removeGalaxy(this); 3048 + return true; 3049 + } 3050 + else if(evt.caller is hwButton) { 3051 + setHomeworlds(!hwButton.pressed); 3052 + return true; 3053 + } 3054 + break; 3055 + } 3056 + return BaseGuiElement::onGuiEvent(evt); 3057 + } 3058 + 3059 + void draw() { 3060 + recti bgPos = AbsolutePosition.padded(-5,0,-4,0); 3061 + clipParent(bgPos); 3062 + skin.draw(SS_GalaxySetupItem, SF_Normal, bgPos.padded(-4,0), mp.color); 3063 + resetClip(); 3064 + auto@ icon = mapIcons[mp.index]; 3065 + if(mp.icon.length != 0 && icon.isLoaded(0)) { 3066 + recti pos = AbsolutePosition.padded(0,42,0,0).aspectAligned(1.0, horizAlign=1.0, vertAlign=1.0); 3067 + icon.draw(pos, Color(0xffffff80)); 3068 + } 3069 + BaseGuiElement::draw(); 3070 + } 3071 +}; 3072 + 3073 +class MapElement : GuiListElement { 3074 + Map@ mp; 3075 + 3076 + MapElement(Map@ _map) { 3077 + @mp = _map; 3078 + } 3079 + 3080 + void draw(GuiListbox@ ele, uint flags, const recti& absPos) override { 3081 + const Font@ title = ele.skin.getFont(FT_Subtitle); 3082 + const Font@ normal = ele.skin.getFont(FT_Normal); 3083 + 3084 + ele.skin.draw(SS_ListboxItem, flags, absPos, mp.color); 3085 + auto@ icon = mapIcons[mp.index]; 3086 + if(mp.icon.length != 0 && icon.isLoaded(0)) { 3087 + recti pos = absPos.aspectAligned(1.0, horizAlign=1.0, vertAlign=1.0); 3088 + icon.draw(pos, Color(0xffffff80)); 3089 + } 3090 + 3091 + title.draw(pos=absPos.resized(0, 32).padded(12,4), 3092 + text=mp.name, color=mp.color, stroke=colors::Black); 3093 + normal.draw(pos=absPos.padded(12,36,12+absPos.height,0), offset=vec2i(), 3094 + lineHeight=-1, text=mp.description, color=colors::White); 3095 + } 3096 +}; 3097 + 3098 +class Quickstart : ConsoleCommand { 3099 + void execute(const string& args) { 3100 + new_game.start(); 3101 + } 3102 +}; 3103 + 3104 +NewGame@ new_game; 3105 +array<DynamicTexture> mapIcons; 3106 + 3107 +void init() { 3108 + @new_game = NewGame(); 3109 + new_game.visible = false; 3110 + 3111 + addConsoleCommand("quickstart", Quickstart()); 3112 +} 3113 + 3114 +array<Player@> connectedPlayers; 3115 +set_int connectedSet; 3116 +void tick(double time) { 3117 + if(new_game.visible) 3118 + new_game.tick(time); 3119 + if(!game_running && mpServer) { 3120 + array<Player@>@ players = getPlayers(); 3121 + 3122 + //Send connect events 3123 + for(uint i = 0, cnt = players.length; i < cnt; ++i) { 3124 + Player@ pl = players[i]; 3125 + if(pl.id == CURRENT_PLAYER.id) 3126 + continue; 3127 + string name = pl.name; 3128 + if(name.length == 0) 3129 + continue; 3130 + if(!connectedSet.contains(pl.id)) { 3131 + string msg = format("[color=#aaa]* "+locale::MP_CONNECT_EVENT+"[/color]", 3132 + format("[b]$1[/b]", bbescape(name))); 3133 + recvMenuJoin(ALL_PLAYERS, msg); 3134 + connectedPlayers.insertLast(pl); 3135 + connectedSet.insert(pl.id); 3136 + } 3137 + } 3138 + 3139 + connectedSet.clear(); 3140 + for(uint i = 0, cnt = players.length; i < cnt; ++i) 3141 + connectedSet.insert(players[i].id); 3142 + 3143 + //Send disconnect events 3144 + for(uint i = 0, cnt = connectedPlayers.length; i < cnt; ++i) { 3145 + if(!connectedSet.contains(connectedPlayers[i].id)) { 3146 + Color color; 3147 + string name = connectedPlayers[i].name; 3148 + 3149 + string msg = format("[color=#aaa]* "+locale::MP_DISCONNECT_EVENT+"[/color]", 3150 + format("[b]$2[/b]", toString(color), bbescape(name))); 3151 + recvMenuLeave(ALL_PLAYERS, msg); 3152 + connectedPlayers.removeAt(i); 3153 + --i; --cnt; 3154 + } 3155 + } 3156 + } 3157 +} 3158 + 3159 +void showNewGame(bool fromMP = false) { 3160 + new_game.visible = true; 3161 + new_game.fromMP = fromMP; 3162 + new_game.init(); 3163 + menu_container.visible = false; 3164 + menu_container.animateOut(); 3165 + new_game.animateIn(); 3166 +} 3167 + 3168 +void hideNewGame(bool snap = false) { 3169 + new_game.fromMP = false; 3170 + menu_container.visible = true; 3171 + if(!snap) { 3172 + menu_container.animateIn(); 3173 + new_game.animateOut(); 3174 + } 3175 + else { 3176 + animate_remove(new_game); 3177 + new_game.visible = false; 3178 + menu_container.show(); 3179 + } 3180 +} 3181 + 3182 +void changeEmpireSettings_client(Player& pl, EmpireSettings@ settings) { 3183 + auto@ emp = new_game.findPlayer(pl.id); 3184 + emp.settings.raceName = settings.raceName; 3185 + emp.settings.traits = settings.traits; 3186 + emp.settings.portrait = settings.portrait; 3187 + emp.settings.shipset = settings.shipset; 3188 + emp.settings.effectorSkin = settings.effectorSkin; 3189 + emp.settings.color = settings.color; 3190 + emp.settings.flag = settings.flag; 3191 + emp.settings.ready = settings.ready; 3192 + emp.settings.team = settings.team; 3193 + emp.update(); 3194 +} 3195 + 3196 +bool sendPeriodic(Message& msg) { 3197 + if(game_running) 3198 + return false; 3199 + new_game.apply(); 3200 + msg << new_game.settings; 3201 + return true; 3202 +} 3203 + 3204 +void recvPeriodic(Message& msg) { 3205 + msg >> new_game.settings; 3206 + new_game.reset(); 3207 + new_game.updateAbsolutePosition(); 3208 +} 3209 + 3210 +void chatMessage(Player& pl, string text) { 3211 + auto@ emp = new_game.findPlayer(pl.id); 3212 + Color color = emp.settings.color; 3213 + string msg = format("[b][color=$1]$2[/color][/b] [offset=100]$3[/offset]", 3214 + toString(color), bbescape(emp.name.text), bbescape(text)); 3215 + recvMenuChat(ALL_PLAYERS, msg); 3216 +} 3217 + 3218 +void chatMessage_client(string text) { 3219 + new_game.addChat(text); 3220 + sound::generic_click.play(); 3221 +} 3222 + 3223 +void chatJoin_client(string text) { 3224 + new_game.addChat(text); 3225 + sound::generic_ok.play(); 3226 +} 3227 + 3228 +void chatLeave_client(string text) { 3229 + new_game.addChat(text); 3230 + sound::generic_warn.play(); 3231 +}
Added scripts/server/cheats.as.
1 +import orbitals; 2 +import object_creation; 3 +import tile_resources; 4 +import void setInstantColonize(bool) from "planets.SurfaceComponent"; 5 +from empire import sendChatMessage; 6 + 7 +import influence; 8 +from bonus_effects import BonusEffect; 9 +from generic_effects import GenericEffect; 10 +import hooks; 11 + 12 +bool CHEATS_ENABLED_THIS_GAME = false; 13 +bool CHEATS_ENABLED = false; 14 +bool getCheatsEnabled() { 15 + return CHEATS_ENABLED; 16 +} 17 + 18 +bool getCheatsEverOn() { 19 + return CHEATS_ENABLED_THIS_GAME; 20 +} 21 + 22 +void setCheatsEnabled(Player& player, bool enabled) { 23 + if(player != HOST_PLAYER) 24 + return; 25 + CHEATS_ENABLED = enabled; 26 + if(enabled) 27 + CHEATS_ENABLED_THIS_GAME = true; 28 + cheatsEnabled(ALL_PLAYERS, enabled); 29 + if(mpServer) { 30 + if(enabled) 31 + sendChatMessage(locale::MP_CHEATS_ENABLED, color=Color(0xaaaaaaff), offset=30); 32 + else 33 + sendChatMessage(locale::MP_CHEATS_DISABLED, color=Color(0xaaaaaaff), offset=30); 34 + } 35 +} 36 + 37 +void cheatSeeAll(Player& player, bool enabled) { 38 + if(!CHEATS_ENABLED || player.emp is null || !player.emp.valid) 39 + return; 40 + player.emp.visionMask = enabled ? ~0 : player.emp.mask; 41 +} 42 + 43 +void cheatColonize(bool enabled) { 44 + if(!CHEATS_ENABLED) 45 + return; 46 + setInstantColonize(enabled); 47 +} 48 + 49 +void cheatSpawnFlagship(Object@ spawnAt, const Design@ design, Empire@ owner) { 50 + if(!CHEATS_ENABLED) 51 + return; 52 + if(design.hasTag(ST_IsSupport)) 53 + return; 54 + createShip(spawnAt, design, owner, free=true); 55 +} 56 + 57 +void cheatSpawnFlagship(vec3d spawnAt, const Design@ design, Empire@ owner) { 58 + if(!CHEATS_ENABLED) 59 + return; 60 + if(design.hasTag(ST_IsSupport)) 61 + return; 62 + Ship@ ship = createShip(spawnAt, design, owner, free=true); 63 + ship.addMoveOrder(spawnAt); 64 +} 65 + 66 +void cheatSpawnSupports(Object@ spawnAt, const Design@ design, uint count) { 67 + if(!CHEATS_ENABLED) 68 + return; 69 + if(!design.hasTag(ST_IsSupport)) 70 + return; 71 + if(!spawnAt.hasLeaderAI || spawnAt.owner is null || !spawnAt.owner.valid) 72 + return; 73 + for(uint i = 0; i < count; ++i) 74 + createShip(spawnAt, design, spawnAt.owner); 75 +} 76 + 77 +void cheatSpawnSupports(vec3d spawnAt, const Design@ design, uint count, Empire@ owner) { 78 + if(!CHEATS_ENABLED) 79 + return; 80 + if(!design.hasTag(ST_IsSupport)) 81 + return; 82 + for(uint i = 0; i < count; ++i) 83 + createShip(spawnAt, design, owner); 84 +} 85 + 86 +void cheatSpawnOrbital(vec3d spawnAt, uint orbitalType, Empire@ owner) { 87 + if(!CHEATS_ENABLED) 88 + return; 89 + const OrbitalModule@ def = getOrbitalModule(orbitalType); 90 + if(def is null) 91 + return; 92 + createOrbital(spawnAt, def, owner); 93 +} 94 + 95 +void cheatInfluence(Player& player, int amount) { 96 + if(!CHEATS_ENABLED || player.emp is null || !player.emp.valid) 97 + return; 98 + player.emp.addInfluence(amount); 99 +} 100 + 101 +void cheatResearch(Player& player, double amount) { 102 + if(!CHEATS_ENABLED || player.emp is null || !player.emp.valid) 103 + return; 104 + player.emp.generatePoints(amount); 105 +} 106 + 107 +void cheatMoney(Player& player, int amount) { 108 + if(!CHEATS_ENABLED || player.emp is null || !player.emp.valid) 109 + return; 110 + player.emp.addBonusBudget(amount); 111 +} 112 + 113 +void cheatEnergy(Player& player, int amount) { 114 + if(!CHEATS_ENABLED || player.emp is null || !player.emp.valid) 115 + return; 116 + player.emp.modEnergyStored(amount); 117 +} 118 + 119 +void cheatFTL(Player& player, int amount) { 120 + if(!CHEATS_ENABLED || player.emp is null || !player.emp.valid) 121 + return; 122 + if(player.emp.FTLCapacity < amount) 123 + player.emp.modFTLCapacity(amount); 124 + player.emp.modFTLStored(amount); 125 +} 126 + 127 +void cheatActivateAI(Player& player) { 128 + if(!CHEATS_ENABLED || player.emp is null || !player.emp.valid) 129 + return; 130 + player.emp.initBasicAI(); 131 +} 132 + 133 +void cheatDebugAI(Empire@ emp) { 134 + if(!CHEATS_ENABLED || emp is null) 135 + return; 136 + emp.debugAI(); 137 +} 138 + 139 +void commandPlayerAI(string cmd) { 140 + playerEmpire.commandAI(cmd); 141 +} 142 + 143 +void cheatCommandAI(Empire@ emp, string cmd) { 144 + if(emp is playerEmpire) { 145 + if (cmd == "no achievements") { 146 + CHEATS_ENABLED_THIS_GAME = true; 147 + } 148 + else { 149 + emp.commandAI(cmd); 150 + } 151 + return; 152 + } 153 + if(!CHEATS_ENABLED || emp is null) 154 + return; 155 + emp.commandAI(cmd); 156 +} 157 + 158 +void cheatTrigger(Player& player, Object@ obj, Empire@ emp, string hook) { 159 + Empire@ plEmp = player.emp; 160 + if(!CHEATS_ENABLED || plEmp is null || !plEmp.valid) 161 + return; 162 + BonusEffect@ trig = cast<BonusEffect>(parseHook(hook, "bonus_effects::", required=false)); 163 + if(trig !is null) { 164 + trig.activate(obj, emp); 165 + return; 166 + } 167 + GenericEffect@ eff = cast<GenericEffect>(parseHook(hook, "planet_effects::")); 168 + if(eff !is null) { 169 + eff.enable(obj, null); 170 + return; 171 + } 172 +} 173 + 174 +void cheatChangeOwner(Object@ obj, Empire@ newOwner) { 175 + if(!CHEATS_ENABLED || obj is null || newOwner is null) 176 + return; 177 + if(obj.isPlanet) { 178 + obj.takeoverPlanet(newOwner); 179 + } 180 + else if(obj.isShip) { 181 + if(obj.hasLeaderAI) { 182 + uint cnt = obj.supportCount; 183 + for(uint i = 0; i < cnt; ++i) 184 + @obj.supportShip[i].owner = newOwner; 185 + } 186 + 187 + @obj.owner = newOwner; 188 + } 189 + else { 190 + @obj.owner = newOwner; 191 + } 192 +} 193 + 194 +void cheatAlliance(Empire& from, Empire& to) { 195 + if(!CHEATS_ENABLED) 196 + return; 197 + if(from is to) 198 + return; 199 + if(!from.valid || !to.valid) 200 + return; 201 +} 202 + 203 +void cheatDestroy(Object@ obj) { 204 + if(!CHEATS_ENABLED || obj is null) 205 + return; 206 + obj.destroy(); 207 +} 208 + 209 +void cheatLabor(Object@ obj, double amount) { 210 + if(!CHEATS_ENABLED || obj is null) 211 + return; 212 + obj.modLaborIncome(amount); 213 +} 214 + 215 +void syncInitial(Message& msg) { 216 + msg << CHEATS_ENABLED; 217 + msg << CHEATS_ENABLED_THIS_GAME; 218 +} 219 + 220 +void save(SaveFile& msg) { 221 + msg << CHEATS_ENABLED; 222 + msg << CHEATS_ENABLED_THIS_GAME; 223 +} 224 + 225 +void load(SaveFile& msg) { 226 + msg >> CHEATS_ENABLED; 227 + if(msg >= SV_0025) 228 + msg >> CHEATS_ENABLED_THIS_GAME; 229 + else 230 + CHEATS_ENABLED_THIS_GAME = CHEATS_ENABLED; 231 +}
Added scripts/server/empire_ai/EmpireAI.as.
1 +import settings.game_settings; 2 + 3 +import AIController@ createBumAI() from "empire_ai.BumAI"; 4 +import AIController@ createBasicAI() from "empire_ai.BasicAI"; 5 +import AIController@ createWeaselAI() from "empire_ai.weasel.WeaselAI"; 6 + 7 +interface AIController { 8 + void debugAI(); 9 + void commandAI(string cmd); 10 + void aiPing(Empire@ fromEmpire, vec3d position, uint type); 11 + void init(Empire& emp, EmpireSettings& settings); 12 + void init(Empire& emp); 13 + void tick(Empire& emp, double time); 14 + void pause(Empire& emp); 15 + void resume(Empire& emp); 16 + void load(SaveFile& msg); 17 + void save(SaveFile& msg); 18 + int getDifficultyLevel(); 19 + vec3d get_aiFocus(); 20 + string getOpinionOf(Empire& emp, Empire@ other); 21 + int getStandingTo(Empire& emp, Empire@ other); 22 +} 23 + 24 +class EmpireAI : Component_EmpireAI, Savable { 25 + AIController@ ctrl; 26 + uint aiType; 27 + bool paused = false; 28 + bool override = true; 29 + 30 + EmpireAI() { 31 + } 32 + 33 + vec3d get_aiFocus() { 34 + if(ctrl !is null) 35 + return ctrl.aiFocus; 36 + else 37 + return vec3d(); 38 + } 39 + 40 + int get_difficulty() { 41 + if(ctrl !is null) 42 + return ctrl.getDifficultyLevel(); 43 + else 44 + return -1; 45 + } 46 + 47 + bool get_isAI(Empire& emp) { 48 + return ctrl !is null && (emp.player is null || override); 49 + } 50 + 51 + string getRelation(Player& pl, Empire& emp) { 52 + auto@ other = pl.emp; 53 + if(ctrl is null || other is null || !other.valid || other.ContactMask.value & emp.mask == 0) 54 + return ""; 55 + else 56 + return ctrl.getOpinionOf(emp, other); 57 + } 58 + 59 + int getRelationState(Player& pl, Empire& emp) { 60 + auto@ other = pl.emp; 61 + if(ctrl is null || other is null || !other.valid || other.ContactMask.value & emp.mask == 0) 62 + return 0; 63 + else 64 + return ctrl.getStandingTo(emp, other); 65 + } 66 + 67 + uint getAIType() { 68 + return aiType; 69 + } 70 + 71 + void debugAI() { 72 + if(ctrl !is null) 73 + ctrl.debugAI(); 74 + } 75 + 76 + void commandAI(string cmd) { 77 + if(ctrl !is null) 78 + ctrl.commandAI(cmd); 79 + } 80 + 81 + void load(SaveFile& msg) { 82 + msg >> paused; 83 + msg >> override; 84 + msg >> aiType; 85 + 86 + createAI(aiType); 87 + if(ctrl !is null) 88 + ctrl.load(msg); 89 + } 90 + 91 + void save(SaveFile& msg) { 92 + msg << paused; 93 + msg << override; 94 + msg << aiType; 95 + 96 + if(ctrl !is null) 97 + ctrl.save(msg); 98 + } 99 + 100 + void createAI(uint type) { 101 + aiType = type; 102 + 103 + //Create the controller 104 + switch(type) { 105 + case ET_Player: 106 + //Do nothing 107 + break; 108 + case ET_BumAI: 109 + @ctrl = createBasicAI(); 110 + break; 111 + case ET_WeaselAI: 112 + @ctrl = createWeaselAI(); 113 + break; 114 + } 115 + 116 + } 117 + 118 + void aiPing(Empire@ fromEmpire, vec3d position, uint type = 0) { 119 + if(ctrl !is null) 120 + ctrl.aiPing(fromEmpire, position, type); 121 + } 122 + 123 + void init(Empire& emp, EmpireSettings& settings) { 124 + createAI(settings.type); 125 + 126 + //Initialize 127 + if(ctrl !is null) 128 + ctrl.init(emp, settings); 129 + } 130 + 131 + void initBasicAI(Empire& emp) { 132 + override = true; 133 + if(ctrl !is null) 134 + return; 135 + 136 + createAI(ET_WeaselAI); 137 + 138 + if(ctrl !is null) { 139 + EmpireSettings settings; 140 + settings.difficulty = 2; 141 + settings.aiFlags |= AIF_Aggressive; 142 + ctrl.init(emp, settings); 143 + ctrl.init(emp); 144 + } 145 + } 146 + 147 + void init(Empire& emp) { 148 + if(ctrl !is null) 149 + ctrl.init(emp); 150 + } 151 + 152 + void aiTick(Empire& emp, double tick) { 153 + if(ctrl is null) 154 + return; 155 + 156 + if(emp.player is null || override) { 157 + if(paused) { 158 + ctrl.resume(emp); 159 + paused = false; 160 + } 161 + ctrl.tick(emp, tick); 162 + } 163 + else { 164 + if(!paused) { 165 + ctrl.pause(emp); 166 + paused = true; 167 + } 168 + } 169 + } 170 +};
Added scripts/server/empire_ai/weasel/Budget.as.
1 +// Budget 2 +// ------ 3 +// Tasked with managing the empire's money and making sure we have enough to spend 4 +// on various things, as well as dealing with prioritization and budget allocation. 5 +// 6 + 7 +import empire_ai.weasel.WeaselAI; 8 + 9 +enum BudgetType { 10 + BT_Military, 11 + BT_Infrastructure, 12 + BT_Colonization, 13 + BT_Development, 14 + 15 + BT_COUNT 16 +}; 17 + 18 +final class AllocateBudget { 19 + int id = -1; 20 + uint type; 21 + int cost = 0; 22 + int maintenance = 0; 23 + 24 + double requestTime = 0; 25 + double priority = 1; 26 + 27 + bool allocated = false; 28 + int opCmp(const AllocateBudget@ other) const { 29 + if(priority < other.priority) 30 + return -1; 31 + if(priority > other.priority) 32 + return 1; 33 + if(requestTime < other.requestTime) 34 + return 1; 35 + if(requestTime > other.requestTime) 36 + return -1; 37 + return 0; 38 + } 39 + 40 + void save(SaveFile& file) { 41 + file << id; 42 + file << type; 43 + file << cost; 44 + file << maintenance; 45 + file << requestTime; 46 + file << priority; 47 + file << allocated; 48 + } 49 + 50 + void load(SaveFile& file) { 51 + file >> id; 52 + file >> type; 53 + file >> cost; 54 + file >> maintenance; 55 + file >> requestTime; 56 + file >> priority; 57 + file >> allocated; 58 + } 59 +}; 60 + 61 +final class BudgetPart { 62 + uint type; 63 + 64 + array<AllocateBudget@> allocations; 65 + 66 + //How much we've spent this cycle 67 + int spent = 0; 68 + 69 + //How much is remaining to be spent this cycle 70 + int remaining = 0; 71 + 72 + //How much maintenance we've gained this cycle 73 + int gainedMaintenance = 0; 74 + 75 + //How much maintenance we can still gain this cycle 76 + int remainingMaintenance = 0; 77 + 78 + void update(AI& ai, Budget& budget) { 79 + 80 + for(uint i = 0, cnt = allocations.length; i < cnt; ++i) { 81 + auto@ alloc = allocations[i]; 82 + if(alloc.priority < 1.0) { 83 + if(alloc.cost >= remaining && alloc.maintenance >= remainingMaintenance) { 84 + budget.spend(type, alloc.cost, alloc.maintenance); 85 + alloc.allocated = true; 86 + allocations.removeAt(i); 87 + break; 88 + } 89 + } 90 + else { 91 + if(budget.canSpend(type, alloc.cost, alloc.maintenance, alloc.priority)) { 92 + budget.spend(type, alloc.cost, alloc.maintenance); 93 + alloc.allocated = true; 94 + allocations.removeAt(i); 95 + break; 96 + } 97 + } 98 + } 99 + } 100 + 101 + void turn(AI& ai, Budget& budget) { 102 + spent = 0; 103 + remaining = 0; 104 + 105 + gainedMaintenance = 0; 106 + remainingMaintenance = 0; 107 + } 108 + 109 + void save(Budget& budget, SaveFile& file) { 110 + file << spent; 111 + file << remaining; 112 + file << gainedMaintenance; 113 + file << remainingMaintenance; 114 + 115 + uint cnt = allocations.length; 116 + file << cnt; 117 + for(uint i = 0; i < cnt; ++i) { 118 + budget.saveAlloc(file, allocations[i]); 119 + allocations[i].save(file); 120 + } 121 + } 122 + 123 + void load(Budget& budget, SaveFile& file) { 124 + file >> spent; 125 + file >> remaining; 126 + file >> gainedMaintenance; 127 + file >> remainingMaintenance; 128 + 129 + uint cnt = 0; 130 + file >> cnt; 131 + for(uint i = 0; i < cnt; ++i) { 132 + auto@ alloc = budget.loadAlloc(file); 133 + alloc.load(file); 134 + } 135 + } 136 +}; 137 + 138 +final class Budget : AIComponent { 139 + //Budget thresholds 140 + private int _criticalThreshold = 350; 141 + private int _lowThreshold = 400; 142 + private int _mediumThreshold = 500; 143 + private int _highThreshold = 1000; 144 + private int _veryHighThreshold = 2000; 145 + 146 + //Focus flags 147 + private bool _askedFocus = false; 148 + private bool _focusing = false; 149 + //Focused budget type 150 + private uint _focus; 151 + 152 + int get_criticalThreshold() const { return _criticalThreshold; } 153 + int get_lowThreshold() const { return _lowThreshold; } 154 + int get_mediumThreshold() const { return _mediumThreshold; } 155 + int get_highThreshold() const { return _highThreshold; } 156 + int get_veryHighThreshold() const { return _veryHighThreshold; } 157 + 158 + array<BudgetPart@> parts; 159 + int NextAllocId = 0; 160 + 161 + int InitialBudget = 0; 162 + int InitialUpcoming = 0; 163 + 164 + double Progress = 0; 165 + double RemainingTime = 0; 166 + 167 + int FreeBudget = 0; 168 + int FreeMaintenance = 0; 169 + 170 + bool checkedMilitarySpending = false; 171 + 172 + void create() { 173 + parts.length = BT_COUNT; 174 + for(uint i = 0; i < BT_COUNT; ++i) { 175 + @parts[i] = BudgetPart(); 176 + parts[i].type = BudgetType(i); 177 + } 178 + } 179 + 180 + void save(SaveFile& file) { 181 + file << InitialBudget; 182 + file << InitialUpcoming; 183 + file << Progress; 184 + file << RemainingTime; 185 + file << FreeBudget; 186 + file << FreeMaintenance; 187 + file << NextAllocId; 188 + file << checkedMilitarySpending; 189 + file << _askedFocus; 190 + file << _focusing; 191 + file << _focus; 192 + 193 + for(uint i = 0, cnt = parts.length; i < cnt; ++i) 194 + parts[i].save(this, file); 195 + } 196 + 197 + void load(SaveFile& file) { 198 + file >> InitialBudget; 199 + file >> InitialUpcoming; 200 + file >> Progress; 201 + file >> RemainingTime; 202 + file >> FreeBudget; 203 + file >> FreeMaintenance; 204 + file >> NextAllocId; 205 + file >> checkedMilitarySpending; 206 + file >> _askedFocus; 207 + file >> _focusing; 208 + file >> _focus; 209 + 210 + for(uint i = 0, cnt = parts.length; i < cnt; ++i) 211 + parts[i].load(this, file); 212 + } 213 + 214 + array<AllocateBudget@> loadIds; 215 + AllocateBudget@ loadAlloc(int id) { 216 + if(id == -1) 217 + return null; 218 + for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) { 219 + if(loadIds[i].id == id) 220 + return loadIds[i]; 221 + } 222 + AllocateBudget alloc; 223 + alloc.id = id; 224 + loadIds.insertLast(alloc); 225 + return alloc; 226 + } 227 + AllocateBudget@ loadAlloc(SaveFile& file) { 228 + int id = -1; 229 + file >> id; 230 + if(id == -1) 231 + return null; 232 + else 233 + return loadAlloc(id); 234 + } 235 + void saveAlloc(SaveFile& file, AllocateBudget@ alloc) { 236 + int id = -1; 237 + if(alloc !is null) 238 + id = alloc.id; 239 + file << id; 240 + } 241 + void postLoad(AI& ai) { 242 + loadIds.length = 0; 243 + } 244 + 245 + void spend(uint type, int money, int maint = 0) { 246 + auto@ part = parts[type]; 247 + 248 + part.spent += money; 249 + part.gainedMaintenance += maint; 250 + 251 + if(part.remaining >= money) { 252 + part.remaining -= money; 253 + } 254 + else if(part.remaining >= 0) { 255 + money -= part.remaining; 256 + FreeBudget -= money; 257 + part.remaining = 0; 258 + } 259 + else { 260 + FreeBudget -= money; 261 + } 262 + 263 + if(part.remainingMaintenance >= maint) { 264 + part.remainingMaintenance -= maint; 265 + } 266 + else if(part.remainingMaintenance >= 0) { 267 + maint -= part.remainingMaintenance; 268 + FreeMaintenance -= maint; 269 + part.remainingMaintenance = 0; 270 + } 271 + else { 272 + FreeMaintenance -= money; 273 + } 274 + } 275 + 276 + bool canSpend(uint type, int money, int maint = 0, double priority = 1.0) { 277 + int canFree = FreeBudget; 278 + int canFreeMaint = FreeMaintenance; 279 + 280 + if (priority < 2.0) { 281 + //Rules for normal priority requests 282 + //Don't allow any spending not in our current focus 283 + if (_focusing) { 284 + if (type != _focus) 285 + return false; 286 + } 287 + if (type != BT_Colonization 288 + && (maint > 200 && ai.empire.EstNextBudget < mediumThreshold) 289 + || (maint > 100 && ai.empire.EstNextBudget < lowThreshold) 290 + || (maint > 0 && ai.empire.EstNextBudget < criticalThreshold)) 291 + //Don't allow any high maintenance cost if our estimated next budget is too low 292 + return false; 293 + 294 + //Don't allow generic spending until we've checked if we need to spend on military this cycle 295 + if(type == BT_Development && !checkedMilitarySpending && Progress < 0.33) 296 + canFree = 0; 297 + if(type == BT_Colonization) 298 + canFree += 160; 299 + } 300 + else { 301 + //Rules for high priority requests 302 + if (money > FreeBudget && ai.empire.canBorrow(money - FreeBudget)) 303 + //Allow borrowing from next budget for high priority requests 304 + canFree = money; 305 + } 306 + 307 + auto@ part = parts[type]; 308 + if(money > part.remaining + canFree) 309 + return false; 310 + if(maint != 0 && maint > part.remainingMaintenance + canFreeMaint) 311 + return false; 312 + return true; 313 + } 314 + 315 + int spendable(uint type) { 316 + return FreeBudget + parts[type].remaining; 317 + } 318 + 319 + int maintainable(uint type) { 320 + return FreeMaintenance + parts[type].remainingMaintenance; 321 + } 322 + 323 + void claim(uint type, int money, int maint = 0) { 324 + auto@ part = parts[type]; 325 + 326 + FreeBudget -= money; 327 + part.remaining += money; 328 + 329 + FreeMaintenance -= maint; 330 + part.remainingMaintenance += maint; 331 + } 332 + 333 + void turn() { 334 + if(log && gameTime > 10.0) { 335 + ai.print("=============="); 336 + ai.print("Unspent:"); 337 + ai.print(" Military: "+parts[BT_Military].remaining+" / "+parts[BT_Military].remainingMaintenance); 338 + ai.print(" Infrastructure: "+parts[BT_Infrastructure].remaining+" / "+parts[BT_Infrastructure].remainingMaintenance); 339 + ai.print(" Colonization: "+parts[BT_Colonization].remaining+" / "+parts[BT_Colonization].remainingMaintenance); 340 + ai.print(" Development: "+parts[BT_Development].remaining+" / "+parts[BT_Development].remainingMaintenance); 341 + ai.print(" FREE: "+FreeBudget+" / "+FreeMaintenance); 342 + ai.print("=============="); 343 + ai.print("Total Expenditures:"); 344 + ai.print(" Military: "+parts[BT_Military].spent+" / "+parts[BT_Military].gainedMaintenance); 345 + ai.print(" Infrastructure: "+parts[BT_Infrastructure].spent+" / "+parts[BT_Infrastructure].gainedMaintenance); 346 + ai.print(" Colonization: "+parts[BT_Colonization].spent+" / "+parts[BT_Colonization].gainedMaintenance); 347 + ai.print(" Development: "+parts[BT_Development].spent+" / "+parts[BT_Development].gainedMaintenance); 348 + ai.print("=============="); 349 + } 350 + 351 + //Collect some data about this turn 352 + InitialBudget = ai.empire.RemainingBudget; 353 + InitialUpcoming = ai.empire.EstNextBudget; 354 + 355 + FreeBudget = InitialBudget; 356 + FreeMaintenance = InitialUpcoming; 357 + 358 + checkedMilitarySpending = false; 359 + 360 + //Handle focus status 361 + if (_focusing) { 362 + _focusing = false; 363 + if (log) 364 + ai.print("Budget: ending focus"); 365 + } 366 + else if (_askedFocus) { 367 + _focusing = true; 368 + _askedFocus = false; 369 + if (log) 370 + ai.print("Budget: starting focus"); 371 + } 372 + 373 + //Tell the budget parts to perform turns 374 + for(uint i = 0, cnt = parts.length; i < cnt; ++i) 375 + parts[i].turn(ai, this); 376 + } 377 + 378 + void remove(AllocateBudget@ alloc) { 379 + if(alloc is null) 380 + return; 381 + if(alloc.allocated) { 382 + FreeBudget += alloc.cost; 383 + FreeMaintenance += alloc.maintenance; 384 + } 385 + parts[alloc.type].allocations.remove(alloc); 386 + } 387 + 388 + AllocateBudget@ allocate(uint type, int cost, int maint = 0, double priority = 1.0) { 389 + AllocateBudget alloc; 390 + alloc.id = NextAllocId++; 391 + alloc.type = type; 392 + alloc.cost = cost; 393 + alloc.maintenance = maint; 394 + alloc.priority = priority; 395 + 396 + return allocate(alloc); 397 + } 398 + 399 + AllocateBudget@ allocate(AllocateBudget@ allocation) { 400 + allocation.requestTime = gameTime; 401 + parts[allocation.type].allocations.insertLast(allocation); 402 + parts[allocation.type].allocations.sortDesc(); 403 + return allocation; 404 + } 405 + 406 + void applyNow(AllocateBudget@ alloc) { 407 + auto@ part = parts[alloc.type]; 408 + spend(alloc.type, alloc.cost, alloc.maintenance); 409 + alloc.allocated = true; 410 + part.allocations.remove(alloc); 411 + } 412 + 413 + void grantBonus(int cost, int maint = 0) { 414 + //Spread some bonus budget across all the different parts 415 + FreeBudget += cost; 416 + FreeMaintenance += maint; 417 + } 418 + 419 + bool canFocus() { 420 + return !(ai.empire.EstNextBudget <= criticalThreshold || _askedFocus || _focusing); 421 + } 422 + 423 + //Focus spendings on one particular budget part for one turn 424 + //Only high priority requests will be considered for other parts 425 + //Should be called at the start of a turn for best results 426 + void focus(BudgetType type) { 427 + if (ai.empire.EstNextBudget > criticalThreshold && !_askedFocus && !_focusing) { 428 + _focus = type; 429 + //If we are still at the start of a turn, focus immediately, else wait until next turn 430 + //The second condition compensates for slight timing inaccuracies and execution delay 431 + if (Progress < 0.33 || Progress > 0.995) { 432 + _focusing = true; 433 + _askedFocus = false; 434 + if (log) 435 + ai.print("Budget: starting focus"); 436 + } 437 + else 438 + _askedFocus = true; 439 + } 440 + } 441 + 442 + void tick(double time) { 443 + //Record some simple data 444 + Progress = ai.empire.BudgetTimer / ai.empire.BudgetCycle; 445 + RemainingTime = ai.empire.BudgetCycle - ai.empire.BudgetTimer; 446 + 447 + //Update one of the budget parts 448 + for(uint i = 0, cnt = parts.length; i < cnt; ++i) { 449 + auto@ part = parts[i]; 450 + part.update(ai, this); 451 + } 452 + } 453 + 454 + void focusTick(double time) { 455 + //Detect any extra budget we need to use 456 + int ExpectBudget = FreeBudget; 457 + int ExpectMaint = FreeMaintenance; 458 + for(uint i = 0, cnt = parts.length; i < cnt; ++i) { 459 + ExpectBudget += parts[i].remaining; 460 + ExpectMaint += parts[i].remainingMaintenance; 461 + } 462 + 463 + int HaveBudget = ai.empire.RemainingBudget; 464 + int HaveMaint = ai.empire.EstNextBudget; 465 + if(ExpectBudget != HaveBudget || ExpectMaint != HaveMaint) 466 + grantBonus(HaveBudget - ExpectBudget, max(0, HaveMaint - ExpectMaint)); 467 + } 468 +}; 469 + 470 +AIComponent@ createBudget() { 471 + return Budget(); 472 +}
Added scripts/server/empire_ai/weasel/Colonization.as.
1 +// Colonization 2 +// ------------ 3 +// Deals with colonization for requested resources. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 +import empire_ai.weasel.ImportData; 8 +import empire_ai.weasel.Resources; 9 +import empire_ai.weasel.Planets; 10 +import empire_ai.weasel.Systems; 11 +import empire_ai.weasel.Budget; 12 +import empire_ai.weasel.Creeping; 13 + 14 +import util.formatting; 15 + 16 +import systems; 17 + 18 +enum ColonizationPhase { 19 + CP_Expansion, 20 + CP_Stabilization, 21 +}; 22 + 23 +interface RaceColonization { 24 + bool orderColonization(ColonizeData& data, Planet@ sourcePlanet); 25 + double getGenericUsefulness(const ResourceType@ type); 26 +}; 27 + 28 +final class ColonizeData { 29 + int id = -1; 30 + Planet@ target; 31 + Planet@ colonizeFrom; 32 + bool completed = false; 33 + bool canceled = false; 34 + double checkTime = -1.0; 35 + 36 + void save(Colonization& colonization, SaveFile& file) { 37 + file << target; 38 + file << colonizeFrom; 39 + file << completed; 40 + file << canceled; 41 + file << checkTime; 42 + } 43 + 44 + void load(Colonization& colonization, SaveFile& file) { 45 + file >> target; 46 + file >> colonizeFrom; 47 + file >> completed; 48 + file >> canceled; 49 + file >> checkTime; 50 + } 51 +}; 52 + 53 +tidy final class WaitUsed { 54 + ImportData@ forData; 55 + ExportData@ resource; 56 + 57 + void save(Colonization& colonization, SaveFile& file) { 58 + colonization.resources.saveImport(file, forData); 59 + colonization.resources.saveExport(file, resource); 60 + } 61 + 62 + void load(Colonization& colonization, SaveFile& file) { 63 + @forData = colonization.resources.loadImport(file); 64 + @resource = colonization.resources.loadExport(file); 65 + } 66 +}; 67 + 68 +final class ColonizePenalty : Savable { 69 + Planet@ pl; 70 + double until; 71 + 72 + void save(SaveFile& file) { 73 + file << pl; 74 + file << until; 75 + } 76 + 77 + void load(SaveFile& file) { 78 + file >> pl; 79 + file >> until; 80 + } 81 +}; 82 + 83 +final class PotentialColonize { 84 + Planet@ pl; 85 + const ResourceType@ resource; 86 + double weight = 0; 87 +}; 88 + 89 +final class ColonizeLog { 90 + int typeId; 91 + double time; 92 +}; 93 + 94 +tidy final class ColonizeQueue { 95 + ResourceSpec@ spec; 96 + Planet@ target; 97 + ColonizeData@ step; 98 + ImportData@ forData; 99 + ColonizeQueue@ parent; 100 + array<ColonizeQueue@> children; 101 + 102 + void save(Colonization& colonization, SaveFile& file) { 103 + file << spec; 104 + file << target; 105 + 106 + colonization.saveColonize(file, step); 107 + colonization.resources.saveImport(file, forData); 108 + 109 + uint cnt = children.length; 110 + file << cnt; 111 + for(uint i = 0; i < cnt; ++i) 112 + children[i].save(colonization, file); 113 + } 114 + 115 + void load(Colonization& colonization, SaveFile& file) { 116 + @spec = ResourceSpec(); 117 + file >> spec; 118 + file >> target; 119 + 120 + @step = colonization.loadColonize(file); 121 + @forData = colonization.resources.loadImport(file); 122 + 123 + uint cnt = 0; 124 + file >> cnt; 125 + children.length = cnt; 126 + for(uint i = 0; i < cnt; ++i) { 127 + @children[i] = ColonizeQueue(); 128 + @children[i].parent = this; 129 + children[i].load(colonization, file); 130 + } 131 + } 132 +}; 133 + 134 +final class Colonization : AIComponent { 135 + const ResourceClass@ foodClass, waterClass, scalableClass; 136 + 137 + Resources@ resources; 138 + Planets@ planets; 139 + Systems@ systems; 140 + Budget@ budget; 141 + Creeping@ creeping; 142 + RaceColonization@ race; 143 + 144 + array<ColonizeQueue@> queue; 145 + array<ColonizeData@> colonizing; 146 + array<ColonizeData@> awaitingSource; 147 + array<WaitUsed@> waiting; 148 + array<ColonizePenalty@> penalties; 149 + set_int penaltySet; 150 + int nextColonizeId = 0; 151 + array<ColonizeLog@> colonizeLog; 152 + 153 + array<PotentialSource@> sources; 154 + double sourceUpdate = 0; 155 + 156 + //Maximum colonizations that can still be done this turn 157 + uint remainColonizations = 0; 158 + //Amount of colonizations that have happened so far this budget cycle 159 + uint curColonizations = 0; 160 + //Amount of colonizations that happened the previous budget cycle 161 + uint prevColonizations = 0; 162 + 163 + //Whether to automatically find sources and order colonizations 164 + bool performColonization = true; 165 + bool queueColonization = true; 166 + 167 + //Colonization focus 168 + private uint _phase = CP_Expansion; 169 + 170 + //Territory request data 171 + private bool _needsMoreTerritory = false; 172 + private bool _needsNewTerritory = false; 173 + private uint _territoryRequests = 0; 174 + private Region@ _newTerritoryTarget; 175 + 176 + Object@ colonizeWeightObj; 177 + 178 + bool get_needsMoreTerritory() const { return _needsMoreTerritory; } 179 + bool get_needsNewTerritory() const { return _needsNewTerritory; } 180 + 181 + void create() { 182 + @resources = cast<Resources>(ai.resources); 183 + @planets = cast<Planets>(ai.planets); 184 + @systems = cast<Systems>(ai.systems); 185 + @budget = cast<Budget>(ai.budget); 186 + @creeping = cast<Creeping>(ai.creeping); 187 + @race = cast<RaceColonization>(ai.race); 188 + 189 + //Get some heuristic resource classes 190 + @foodClass = getResourceClass("Food"); 191 + @waterClass = getResourceClass("WaterType"); 192 + @scalableClass = getResourceClass("Scalable"); 193 + 194 + } 195 + 196 + void save(SaveFile& file) { 197 + file << nextColonizeId; 198 + file << remainColonizations; 199 + file << curColonizations; 200 + file << prevColonizations; 201 + file << _phase; 202 + file << _needsMoreTerritory; 203 + file << _needsNewTerritory; 204 + file << _territoryRequests; 205 + if (_newTerritoryTarget !is null) { 206 + file.write1(); 207 + file << _newTerritoryTarget; 208 + } 209 + else 210 + file.write0(); 211 + 212 + uint cnt = colonizing.length; 213 + file << cnt; 214 + for(uint i = 0; i < cnt; ++i) { 215 + saveColonize(file, colonizing[i]); 216 + colonizing[i].save(this, file); 217 + } 218 + 219 + cnt = waiting.length; 220 + file << cnt; 221 + for(uint i = 0; i < cnt; ++i) 222 + waiting[i].save(this, file); 223 + 224 + cnt = penalties.length; 225 + file << cnt; 226 + for(uint i = 0; i < cnt; ++i) 227 + penalties[i].save(file); 228 + 229 + cnt = colonizeLog.length; 230 + file << cnt; 231 + for(uint i = 0; i < cnt; ++i) { 232 + file.writeIdentifier(SI_Resource, colonizeLog[i].typeId); 233 + file << colonizeLog[i].time; 234 + } 235 + 236 + cnt = queue.length; 237 + file << cnt; 238 + for(uint i = 0; i < cnt; ++i) 239 + queue[i].save(this, file); 240 + } 241 + 242 + void load(SaveFile& file) { 243 + file >> nextColonizeId; 244 + file >> remainColonizations; 245 + file >> curColonizations; 246 + file >> prevColonizations; 247 + file >> _phase; 248 + file >> _needsMoreTerritory; 249 + file >> _needsNewTerritory; 250 + file >> _territoryRequests; 251 + if(file.readBit()) { 252 + file >> _newTerritoryTarget; 253 + } 254 + 255 + uint cnt = 0; 256 + file >> cnt; 257 + for(uint i = 0; i < cnt; ++i) { 258 + auto@ data = loadColonize(file); 259 + if(data !is null) { 260 + data.load(this, file); 261 + if(data.target !is null) { 262 + colonizing.insertLast(data); 263 + if(data.colonizeFrom is null) 264 + awaitingSource.insertLast(data); 265 + } 266 + else { 267 + data.canceled = true; 268 + } 269 + } 270 + else { 271 + ColonizeData().load(this, file); 272 + } 273 + } 274 + 275 + file >> cnt; 276 + waiting.length = cnt; 277 + for(uint i = 0; i < cnt; ++i) { 278 + @waiting[i] = WaitUsed(); 279 + waiting[i].load(this, file); 280 + } 281 + 282 + file >> cnt; 283 + for(uint i = 0; i < cnt; ++i) { 284 + ColonizePenalty pen; 285 + pen.load(file); 286 + if(pen.pl !is null) { 287 + penaltySet.insert(pen.pl.id); 288 + penalties.insertLast(pen); 289 + } 290 + } 291 + 292 + file >> cnt; 293 + for(uint i = 0; i < cnt; ++i) { 294 + ColonizeLog logEntry; 295 + logEntry.typeId = file.readIdentifier(SI_Resource); 296 + file >> logEntry.time; 297 + colonizeLog.insertLast(logEntry); 298 + } 299 + 300 + file >> cnt; 301 + queue.length = cnt; 302 + for(uint i = 0; i < cnt; ++i) { 303 + @queue[i] = ColonizeQueue(); 304 + queue[i].load(this, file); 305 + } 306 + } 307 + 308 + array<ColonizeData@> loadIds; 309 + ColonizeData@ loadColonize(int id) { 310 + if(id == -1) 311 + return null; 312 + for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) { 313 + if(loadIds[i].id == id) 314 + return loadIds[i]; 315 + } 316 + ColonizeData data; 317 + data.id = id; 318 + loadIds.insertLast(data); 319 + return data; 320 + } 321 + ColonizeData@ loadColonize(SaveFile& file) { 322 + int id = -1; 323 + file >> id; 324 + if(id == -1) 325 + return null; 326 + else 327 + return loadColonize(id); 328 + } 329 + void saveColonize(SaveFile& file, ColonizeData@ data) { 330 + int id = -1; 331 + if(data !is null) 332 + id = data.id; 333 + file << id; 334 + } 335 + void postLoad(AI& ai) { 336 + loadIds.length = 0; 337 + } 338 + 339 + bool canBeColonized(Planet& target) { 340 + if(!target.valid) 341 + return false; 342 + if(target.owner.valid) 343 + return false; 344 + return true; 345 + } 346 + 347 + bool canColonize(Planet& source) { 348 + if(source.level == 0) 349 + return false; 350 + if(source.owner !is ai.empire) 351 + return false; 352 + return true; 353 + } 354 + 355 + bool shouldForceExpansion() { 356 + uint otherColonizedSystems = 0; 357 + for (uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) { 358 + auto@ sys = systems.outsideBorder[i]; 359 + //Check if any system in our tradable area is unexplored 360 + if (!sys.explored) 361 + return false; 362 + if (sys.planets.length > 0) { 363 + uint otherColonizedPlanets = 0; 364 + for (uint j = 0, cnt = sys.planets.length; j < cnt; ++j) { 365 + auto@ pl = sys.planets[j]; 366 + int resId = pl.primaryResourceType; 367 + if (resId != -1) { 368 + //Check if any planet can still be colonized in our tradable area 369 + if (!pl.owner.valid && !pl.quarantined) 370 + return false; 371 + else 372 + ++otherColonizedPlanets; 373 + } 374 + } 375 + //Check if all planets in the system are colonized 376 + if (otherColonizedPlanets == sys.planets.length) 377 + ++otherColonizedSystems; 378 + } 379 + } 380 + //Check if all systems in our tradable area belong to other empires 381 + if (otherColonizedSystems == systems.outsideBorder.length) 382 + //If 0, we colonized everything! 383 + return false; 384 + 385 + return true; 386 + } 387 + 388 + double getSourceWeight(PotentialSource& source, ColonizeData& data) { 389 + double w = source.weight; 390 + w /= data.target.position.distanceTo(source.pl.position); 391 + return w; 392 + } 393 + 394 + void updateSources() { 395 + planets.getColonizeSources(sources); 396 + } 397 + 398 + void focusTick(double time) { 399 + if(sourceUpdate < gameTime && performColonization) { 400 + updateSources(); 401 + if(sources.length == 0 && gameTime < 60.0) 402 + sourceUpdate = gameTime + 1.0; 403 + else 404 + sourceUpdate = gameTime + 10.0; 405 + } 406 + 407 + if (ai.behavior.forbidColonization) return; 408 + 409 + //Find some new colonizations we can queue up from resources 410 + fillQueueFromRequests(); 411 + 412 + //If we've gained any requests, see if we can order another colonize 413 + if(remainColonizations > 0 414 + && (budget.Progress < ai.behavior.colonizeMaxBudgetProgress || gameTime < 3.0 * 60.0) 415 + && (sources.length > 0 || !performColonization) && canColonize() 416 + && queueColonization) { 417 + //Actually go order some colonizations from the queue 418 + if(orderFromQueue()) { 419 + doColonize(); 420 + } 421 + else if(awaitingSource.length == 0) { 422 + if(genericExpand() !is null) 423 + doColonize(); 424 + } 425 + } 426 + 427 + //Find colonization sources for everything that needs them 428 + if(awaitingSource.length != 0 && performColonization) { 429 + for(uint i = 0, cnt = awaitingSource.length; i < cnt; ++i) { 430 + auto@ target = awaitingSource[i]; 431 + 432 + PotentialSource@ src; 433 + double bestSource = 0; 434 + 435 + for(uint j = 0, jcnt = sources.length; j < jcnt; ++j) { 436 + double w = getSourceWeight(sources[j], target); 437 + if(w > bestSource) { 438 + bestSource = w; 439 + @src = sources[j]; 440 + } 441 + } 442 + 443 + if(src !is null) { 444 + orderColonization(target, src.pl); 445 + sources.remove(src); 446 + --i; --cnt; 447 + } 448 + } 449 + } 450 + 451 + //Check if any resources we're waiting for are being used 452 + for(uint i = 0, cnt = waiting.length; i < cnt; ++i) { 453 + auto@ wait = waiting[i]; 454 + if(wait.resource.obj is null || !wait.resource.obj.valid || wait.resource.obj.owner !is ai.empire || wait.resource.request !is null) { 455 + wait.forData.isColonizing = false; 456 + waiting.removeAt(i); 457 + --i; --cnt; 458 + } 459 + } 460 + 461 + //Prune old colonization penalties 462 + for(uint i = 0, cnt = penalties.length; i < cnt; ++i) { 463 + auto@ pen = penalties[i]; 464 + if(pen.pl !is null && pen.pl.owner is ai.empire) 465 + pen.pl.forceAbandon(); 466 + if(pen.until < gameTime) { 467 + if(pen.pl !is null) 468 + penaltySet.erase(pen.pl.id); 469 + penalties.removeAt(i); 470 + --i; --cnt; 471 + } 472 + } 473 + } 474 + 475 + void orderColonization(ColonizeData& data, Planet& sourcePlanet) { 476 + if(log) 477 + ai.print("start colonizing "+data.target.name, sourcePlanet); 478 + 479 + if(race !is null) { 480 + if(race.orderColonization(data, sourcePlanet)) 481 + return; 482 + } 483 + 484 + @data.colonizeFrom = sourcePlanet; 485 + awaitingSource.remove(data); 486 + 487 + sourcePlanet.colonize(data.target); 488 + } 489 + 490 + void tick(double time) { 491 + //Check if we've finished colonizing anything 492 + for(uint i = 0, cnt = colonizing.length; i < cnt; ++i) { 493 + auto@ c = colonizing[i]; 494 + 495 + //Remove if we can no longer colonize it 496 + Empire@ visOwner = c.target.visibleOwnerToEmp(ai.empire); 497 + if(visOwner !is ai.empire && (visOwner is null || visOwner.valid)) { 498 + //Fail out this colonization 499 + cancelColonization(c); 500 + --i; --cnt; 501 + continue; 502 + } 503 + 504 + //Check for succesful colonization 505 + if(visOwner is ai.empire) { 506 + double population = c.target.population; 507 + if(population >= 1.0) { 508 + finishColonization(c); 509 + colonizing.removeAt(i); 510 + --i; --cnt; 511 + continue; 512 + } 513 + else { 514 + if(c.checkTime == -1.0) { 515 + c.checkTime = gameTime; 516 + } 517 + else { 518 + double grace = ai.behavior.colonizeFailGraceTime; 519 + if(population > 0.9) 520 + grace *= 2.0; 521 + if(c.checkTime + grace < gameTime) { 522 + //Fail out this colonization and penalize the target 523 + creeping.requestClear(systems.getAI(c.target.region)); 524 + cancelColonization(c, penalize=ai.behavior.colonizePenalizeTime); 525 + --i; --cnt; 526 + continue; 527 + } 528 + } 529 + } 530 + } 531 + 532 + //This colonization is still waiting for a good source 533 + if(c.colonizeFrom is null) 534 + continue; 535 + 536 + //Check for failed colonization 537 + if(!canColonize(c.colonizeFrom) || !performColonization) { 538 + if(c.target.owner is ai.empire && performColonization) 539 + c.target.stopColonizing(c.target); 540 + 541 + @c.colonizeFrom = null; 542 + awaitingSource.insertAt(0, c); 543 + } 544 + } 545 + 546 + //Update the colonization queue 547 + updateQueue(); 548 + } 549 + 550 + void cancelColonization(ColonizeData@ data, double penalize = 0) { 551 + if(data.colonizeFrom !is null && data.colonizeFrom.owner is ai.empire) 552 + data.colonizeFrom.stopColonizing(data.target); 553 + if(data.colonizeFrom is null) 554 + awaitingSource.remove(data); 555 + if(data.target.owner is ai.empire) 556 + data.target.forceAbandon(); 557 + data.canceled = true; 558 + sourceUpdate = 0; 559 + colonizing.remove(data); 560 + 561 + if(penalize != 0) { 562 + ColonizePenalty pen; 563 + @pen.pl = data.target; 564 + pen.until = gameTime + penalize; 565 + 566 + penaltySet.insert(pen.pl.id); 567 + penalties.insertLast(pen); 568 + } 569 + } 570 + 571 + void finishColonization(ColonizeData@ data) { 572 + if(data.colonizeFrom is null) 573 + awaitingSource.remove(data); 574 + 575 + //If we just colonized a new territory, reset request data 576 + if (data.target.region is _newTerritoryTarget) { 577 + _needsNewTerritory = false; 578 + @_newTerritoryTarget = null; 579 + } 580 + 581 + PlanetAI@ plAI = planets.register(data.target); 582 + 583 + ColonizeLog logEntry; 584 + logEntry.typeId = data.target.primaryResourceType; 585 + logEntry.time = gameTime; 586 + colonizeLog.insertLast(logEntry); 587 + 588 + data.completed = true; 589 + sourceUpdate = 0; 590 + } 591 + 592 + double timeSinceMatchingColonize(ResourceSpec& spec) { 593 + for(int i = colonizeLog.length - 1; i >= 0; --i) { 594 + auto@ res = getResource(colonizeLog[i].typeId); 595 + if(res !is null && spec.meets(res)) 596 + return gameTime - colonizeLog[i].time; 597 + } 598 + return gameTime; 599 + } 600 + 601 + bool isColonizing(Planet& pl) { 602 + for(uint i = 0, cnt = colonizing.length; i < cnt; ++i) { 603 + if(colonizing[i].target is pl) 604 + return true; 605 + } 606 + for(uint i = 0, cnt = queue.length; i < cnt; ++i) { 607 + if(isColonizing(pl, queue[i])) 608 + return true; 609 + } 610 + return false; 611 + } 612 + 613 + bool isColonizing(Planet& pl, ColonizeQueue@ q) { 614 + if(q.target is pl) 615 + return true; 616 + for(uint i = 0, cnt = q.children.length; i < cnt; ++i) { 617 + if(isColonizing(pl, q.children[i])) 618 + return true; 619 + } 620 + return false; 621 + } 622 + 623 + double getGenericUsefulness(const ResourceType@ type) { 624 + //Return a relative value for colonizing the resource this planet has in a vacuum, 625 + //rather than as an explicit requirement for a planet. 626 + double weight = 1.0; 627 + if(type.level == 0) { 628 + weight *= 2.0; 629 + } 630 + else { 631 + weight /= sqr(double(1 + type.level)); 632 + weight *= 0.001; 633 + } 634 + if(type.cls is foodClass || type.cls is waterClass) 635 + weight *= 10.0; 636 + if(type.cls is scalableClass) 637 + weight *= 0.0001; 638 + if(type.totalPressure > 0) 639 + weight *= double(type.totalPressure); 640 + if(race !is null) 641 + weight *= race.getGenericUsefulness(type); 642 + return weight; 643 + } 644 + 645 + ColonizeData@ colonize(Planet& pl) { 646 + if(log) 647 + ai.print("queue colonization", pl); 648 + 649 + ColonizeData data; 650 + data.id = nextColonizeId++; 651 + @data.target = pl; 652 + 653 + budget.spend(BT_Colonization, 0, ai.behavior.colonizeBudgetCost); 654 + 655 + colonizing.insertLast(data); 656 + awaitingSource.insertLast(data); 657 + return data; 658 + } 659 + 660 + ColonizeData@ colonize(ResourceSpec@ spec) { 661 + Planet@ newColony; 662 + double w; 663 + double bestWeight = 0.0; 664 + 665 + for(uint i = 0, cnt = potentials.length; i < cnt; ++i) { 666 + auto@ p = potentials[i]; 667 + 668 + Region@ reg = p.pl.region; 669 + if(reg is null) 670 + continue; 671 + if(!spec.meets(p.resource)) 672 + continue; 673 + if(isColonizing(p.pl)) 674 + continue; 675 + //Skip planets out of our new territory target if we are colonizing a new one 676 + if (_newTerritoryTarget !is null && p.pl.region !is _newTerritoryTarget) 677 + continue; 678 + 679 + auto@ sys = systems.getAI(reg); 680 + w = 1.0; 681 + if (sys.border) 682 + w *= 0.25; 683 + if (!sys.owned && !sys.border) 684 + w /= 0.25; 685 + if (sys.obj.PlanetsMask & ~ai.mask != 0) 686 + w *= 0.25; 687 + if (w > bestWeight) { 688 + @newColony = p.pl; 689 + bestWeight = w; 690 + } 691 + } 692 + 693 + if(newColony !is null) 694 + return colonize(newColony); 695 + else 696 + return null; 697 + } 698 + 699 + array<PotentialColonize@> potentials; 700 + void checkSystem(SystemAI@ sys) { 701 + uint presentMask = sys.seenPresent; 702 + if(presentMask & ai.mask == 0) { 703 + if(!ai.behavior.colonizeEnemySystems && (presentMask & ai.enemyMask) != 0) 704 + return; 705 + if(!ai.behavior.colonizeNeutralOwnedSystems && (presentMask & ai.neutralMask) != 0) 706 + return; 707 + if(!ai.behavior.colonizeAllySystems && (presentMask & ai.allyMask) != 0) 708 + return; 709 + } 710 + 711 + double sysWeight = 1.0; 712 + if(presentMask & ai.mask == 0) 713 + sysWeight *= ai.behavior.weightOutwardExpand; 714 + 715 + uint plCnt = sys.planets.length; 716 + for(uint n = 0; n < plCnt; ++n) { 717 + Planet@ pl = sys.planets[n]; 718 + Empire@ visOwner = pl.visibleOwnerToEmp(ai.empire); 719 + if(!pl.valid || visOwner.valid) 720 + continue; 721 + if(isColonizing(pl)) 722 + continue; 723 + if(penaltySet.contains(pl.id)) 724 + continue; 725 + if(pl.quarantined) 726 + continue; 727 + 728 + int resId = pl.primaryResourceType; 729 + if(resId == -1) 730 + continue; 731 + 732 + PotentialColonize p; 733 + @p.pl = pl; 734 + @p.resource = getResource(resId); 735 + p.weight = 1.0 * sysWeight; 736 + //TODO: this should be weighted according to the position of the planet, 737 + //we should try to colonize things in favorable positions 738 + potentials.insertLast(p); 739 + } 740 + } 741 + 742 + double nextPotentialCheck = 0.0; 743 + array<PotentialColonize@>@ getPotentialColonize() { 744 + if(gameTime < nextPotentialCheck) 745 + return potentials; 746 + 747 + potentials.length = 0; 748 + for(uint i = 0, cnt = systems.owned.length; i < cnt; ++i) 749 + checkSystem(systems.owned[i]); 750 + for(uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) 751 + checkSystem(systems.outsideBorder[i]); 752 + 753 + if(needsNewTerritory) { 754 + for(uint i = 0, cnt = systems.all.length; i < cnt; ++i) { 755 + if(systems.all[i].explored) 756 + checkSystem(systems.all[i]); 757 + } 758 + } 759 + 760 + if(systems.owned.length == 0) { 761 + Region@ homeSys = ai.empire.HomeSystem; 762 + if(homeSys !is null) { 763 + auto@ homeAI = systems.getAI(homeSys); 764 + if(homeAI !is null) 765 + checkSystem(homeAI); 766 + } 767 + else { 768 + for(uint i = 0, cnt = systems.all.length; i < cnt; ++i) { 769 + if(systems.all[i].visible) 770 + checkSystem(systems.all[i]); 771 + } 772 + } 773 + } 774 + 775 + if(potentials.length == 0 && gameTime < 60.0) 776 + nextPotentialCheck = gameTime + 1.0; 777 + else 778 + nextPotentialCheck = gameTime + randomd(10.0, 40.0); 779 + 780 + //TODO: This should be able to colonize across empires we have trade agreements with? 781 + return potentials; 782 + } 783 + 784 + bool canColonize() { 785 + if(remainColonizations == 0) 786 + return false; 787 + if(curColonizations >= ai.behavior.guaranteeColonizations) { 788 + if(!budget.canSpend(BT_Colonization, 0, ai.behavior.colonizeBudgetCost)) 789 + return false; 790 + } 791 + if(ai.behavior.maxConcurrentColonizations <= colonizing.length) 792 + return false; 793 + return true; 794 + } 795 + 796 + void doColonize() { 797 + remainColonizations -= 1; 798 + curColonizations += 1; 799 + budget.spend(BT_Colonization, 0, ai.behavior.colonizeBudgetCost); 800 + } 801 + 802 + ColonizeData@ genericExpand() { 803 + auto@ potentials = getPotentialColonize(); 804 + 805 + //Do generic expansion using any remaining colonization steps we have 806 + if(ai.behavior.colonizeGenericExpand) { 807 + PotentialColonize@ expand; 808 + double w; 809 + double bestWeight = 0.0; 810 + 811 + for(uint i = 0, cnt = potentials.length; i < cnt; ++i) { 812 + auto@ p = potentials[i]; 813 + w = p.weight * getGenericUsefulness(p.resource); 814 + modPotentialWeight(p, w); 815 + 816 + Region@ reg = p.pl.region; 817 + if(reg is null) 818 + continue; 819 + if(reg.PlanetsMask & ai.mask != 0) 820 + continue; 821 + //Skip planets out of our new territory target if we are colonizing a new one 822 + if (_newTerritoryTarget !is null && p.pl.region !is _newTerritoryTarget) 823 + continue; 824 + if(w == 0) 825 + continue; 826 + if (w > bestWeight) { 827 + @expand = p; 828 + bestWeight = w; 829 + } 830 + } 831 + 832 + if(expand !is null) { 833 + auto@ data = colonize(expand.pl); 834 + potentials.remove(expand); 835 + if (needsNewTerritory && _newTerritoryTarget is null) { 836 + //Check if our target planet is outside our tradable area 837 + bool found = false; 838 + for (uint i = 0, cnt = systems.owned.length; i < cnt; ++i) { 839 + if (systems.owned[i].obj is expand.pl.region) 840 + found = true; 841 + } 842 + for (uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) { 843 + if (systems.outsideBorder[i].obj is expand.pl.region) 844 + found = true; 845 + } 846 + if (!found) 847 + @_newTerritoryTarget = expand.pl.region; 848 + } 849 + return data; 850 + } 851 + } 852 + return null; 853 + } 854 + 855 + void turn() { 856 + //Figure out how much we can colonize 857 + remainColonizations = ai.behavior.maxColonizations; 858 + 859 + //Decide colonization phase 860 + if (_phase == CP_Expansion) { 861 + if (ai.empire.EstNextBudget < budget.criticalThreshold) { 862 + remainColonizations = 0; 863 + _phase = CP_Stabilization; 864 + if (log) 865 + ai.print("Colonization: entering stabilization phase with estimated next budget: " + ai.empire.EstNextBudget); 866 + } 867 + else if (ai.empire.EstNextBudget < budget.lowThreshold) { 868 + remainColonizations = 1; 869 + if (log) 870 + ai.print("Colonization: continuing expansion phase with estimated next budget: " + ai.empire.EstNextBudget); 871 + } 872 + else if (ai.empire.EstNextBudget < budget.mediumThreshold) { 873 + remainColonizations = min(2, ai.behavior.maxColonizations); 874 + if (log) 875 + ai.print("Colonization: continuing expansion phase with estimated next budget: " + ai.empire.EstNextBudget); 876 + } 877 + } 878 + else if (_phase == CP_Stabilization) { 879 + if (ai.empire.RemainingBudget > budget.mediumThreshold) { 880 + _phase = CP_Expansion; 881 + if (log) 882 + ai.print("Colonization: entering expansion phase with budget: " + ai.empire.RemainingBudget); 883 + } 884 + else { 885 + remainColonizations = 1; 886 + if (log) 887 + ai.print("Colonization: continuing stabilization phase with budget: " + ai.empire.RemainingBudget); 888 + } 889 + } 890 + 891 + if (ai.empire.EstNextBudget <= 0 && !ai.behavior.forbidScuttle) { 892 + //We are in trouble. Abandon planets sucking budget up 893 + if (log) 894 + ai.print("Colonization: negative budget, abandoning planets"); 895 + auto@ homeworld = ai.empire.Homeworld; 896 + for (uint i = 0, cnt = planets.planets.length; i < cnt; i++) { 897 + auto@ pl = planets.planets[i].obj; 898 + if (pl is homeworld) 899 + continue; 900 + int resId = pl.primaryResourceType; 901 + if(resId == -1) 902 + continue; 903 + const ResourceType@ type = getResource(resId); 904 + if ((type.cls is scalableClass || type.level > 0) && pl.resourceLevel == 0) { 905 + pl.forceAbandon(); 906 + } 907 + } 908 + //If we are still in trouble, abandon more planets 909 + if (ai.empire.EstNextBudget <= 0) { 910 + for (uint i = 0, cnt = planets.planets.length; i < cnt; i++) { 911 + auto@ pl = planets.planets[i].obj; 912 + if (pl is homeworld) 913 + continue; 914 + int resId = pl.primaryResourceType; 915 + if(resId == -1) 916 + continue; 917 + const ResourceType@ type = getResource(resId); 918 + if ((type.cls is foodClass || type.cls is waterClass) && !pl.primaryResourceExported) 919 + pl.forceAbandon(); 920 + } 921 + //More! 922 + if (ai.empire.EstNextBudget <= 0) { 923 + for (uint i = 0, cnt = planets.planets.length; i < cnt; i++) { 924 + auto@ pl = planets.planets[i].obj; 925 + if (pl is homeworld) 926 + continue; 927 + int resId = pl.primaryResourceType; 928 + if(resId == -1) 929 + continue; 930 + const ResourceType@ type = getResource(resId); 931 + if (!(type.cls is foodClass || type.cls is waterClass || type.cls is scalableClass) && type.level == 0) 932 + pl.forceAbandon(); 933 + } 934 + } 935 + } 936 + } 937 + 938 + //Check if we need to push for territory 939 + if (shouldForceExpansion()) { 940 + //If we already spent at least three turns trying to extend our territory, colonize a new one 941 + if (needsMoreTerritory && _territoryRequests >= 3) 942 + _needsNewTerritory = true; 943 + else { 944 + _needsMoreTerritory = true; 945 + _territoryRequests++; 946 + } 947 + } 948 + else { 949 + _needsMoreTerritory = false; 950 + _needsNewTerritory = false; 951 + _territoryRequests = 0; 952 + @_newTerritoryTarget = null; 953 + } 954 + 955 + prevColonizations = curColonizations; 956 + curColonizations = 0; 957 + 958 + updateSources(); 959 + 960 + if(log) { 961 + ai.print("Empire colonization standings at "+formatGameTime(gameTime)+":"); 962 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 963 + Empire@ other = getEmpire(i); 964 + if(other.major) 965 + ai.print(" "+ai.pad(other.name, 20)+" - "+ai.pad(other.TotalPlanets.value+" planets", 15)+" - "+other.points.value+" points"); 966 + } 967 + } 968 + } 969 + 970 + bool shouldQueueFor(const ResourceSpec@ spec, ColonizeQueue@ inside = null) { 971 + auto@ list = inside is null ? this.queue : inside.children; 972 + for(uint i = 0, cnt = list.length; i < cnt; ++i) { 973 + auto@ q = list[i]; 974 + 975 + //haven't managed to resolve it fully, skip it as well 976 + if(spec.type == RST_Level_Specific) { 977 + if(q.spec.type == RST_Level_Specific && q.spec.level == spec.level) { 978 + if(!isResolved(q)) 979 + return false; 980 + } 981 + } 982 + 983 + //Check anything inner to this tree element 984 + if(!shouldQueueFor(spec, q)) 985 + return false; 986 + } 987 + 988 + return true; 989 + } 990 + 991 + bool shouldQueueFor(ImportData@ imp, ColonizeQueue@ inside = null) { 992 + auto@ list = inside is null ? this.queue : inside.children; 993 + for(uint i = 0, cnt = list.length; i < cnt; ++i) { 994 + auto@ q = list[i]; 995 + 996 + //If we already have this in our queue tree, don't colonize it again 997 + if(imp.forLevel) { 998 + if(q.forData is imp) 999 + return false; 1000 + if(q.parent !is null && q.parent.step !is null && q.parent.step.target is imp.obj) { 1001 + if(q.spec == imp.spec) 1002 + return false; 1003 + } 1004 + } 1005 + 1006 + //If we're already trying to get something of this level, but we 1007 + //haven't managed to resolve it fully, skip it as well 1008 + if(imp.spec.type == RST_Level_Specific) { 1009 + if(q.spec.type == RST_Level_Specific && q.spec.level == imp.spec.level) { 1010 + if(!isResolved(q)) 1011 + return false; 1012 + } 1013 + } 1014 + 1015 + //Check anything inner to this tree element 1016 + if(!shouldQueueFor(imp, q)) 1017 + return false; 1018 + } 1019 + 1020 + return true; 1021 + } 1022 + 1023 + ColonizeQueue@ queueColonize(ResourceSpec& spec, bool place = true) { 1024 + ColonizeQueue q; 1025 + @q.spec = spec; 1026 + 1027 + if(place) 1028 + queue.insertLast(q); 1029 + return q; 1030 + } 1031 + 1032 + bool unresolvedInQueue() { 1033 + for(uint i = 0, cnt = queue.length; i < cnt; ++i) { 1034 + auto@ q = queue[i]; 1035 + if(q.parent !is null) 1036 + continue; 1037 + if(!isResolved(q)) 1038 + return true; 1039 + } 1040 + return false; 1041 + } 1042 + 1043 + bool isResolved(ColonizeQueue@ q) { 1044 + if(q.step is null || q.step.canceled) 1045 + return false; 1046 + for(uint i = 0 , cnt = q.children.length; i < cnt; ++i) { 1047 + if(!isResolved(q.children[i])) 1048 + return false; 1049 + } 1050 + return true; 1051 + } 1052 + 1053 + bool isResolved(ImportData@ req, ColonizeQueue@ inside = null) { 1054 + auto@ list = inside is null ? this.queue : inside.children; 1055 + for(uint i = 0, cnt = list.length; i < cnt; ++i) { 1056 + auto@ q = list[i]; 1057 + if(q.forData is req) 1058 + return isResolved(q); 1059 + if(isResolved(req, inside=q)) 1060 + return true; 1061 + } 1062 + return false; 1063 + } 1064 + 1065 + Planet@ resolve(ColonizeQueue@ q) { 1066 + if(q.step !is null) 1067 + return q.step.target; 1068 + 1069 + auto@ potentials = getPotentialColonize(); 1070 + PotentialColonize@ take; 1071 + double takeWeight = 0.0; 1072 + 1073 + for(uint i = 0, cnt = potentials.length; i < cnt; ++i) { 1074 + auto@ p = potentials[i]; 1075 + if(!q.spec.meets(p.resource)) 1076 + continue; 1077 + 1078 + //Skip planets out of our new territory target if we are colonizing a new one 1079 + if (_newTerritoryTarget !is null && p.pl.region !is _newTerritoryTarget) 1080 + continue; 1081 + 1082 + if(p.weight > takeWeight) { 1083 + takeWeight = p.weight; 1084 + @take = p; 1085 + } 1086 + } 1087 + 1088 + if(take !is null) { 1089 + @q.target = take.pl; 1090 + potentials.remove(take); 1091 + 1092 + array<ResourceSpec@> allReqs; 1093 + for(uint i = 1, cnt = take.resource.level; i <= cnt; ++i) { 1094 + const PlanetLevel@ lvl = getPlanetLevel(take.pl, i); 1095 + if(lvl !is null) { 1096 + array<ResourceSpec@> reqList; 1097 + array<ResourceSpec@> curReqs; 1098 + curReqs = allReqs; 1099 + 1100 + const ResourceRequirements@ reqs = lvl.reqs; 1101 + for(uint i = 0, cnt = reqs.reqs.length; i < cnt; ++i) { 1102 + auto@ need = reqs.reqs[i]; 1103 + 1104 + bool found = false; 1105 + for(uint n = 0, ncnt = curReqs.length; n < ncnt; ++n) { 1106 + if(curReqs[n].implements(need)) { 1107 + found = true; 1108 + curReqs.removeAt(n); 1109 + break; 1110 + } 1111 + } 1112 + 1113 + if(!found) 1114 + reqList.insertLast(implementSpec(need)); 1115 + } 1116 + 1117 + reqList.sortDesc(); 1118 + 1119 + auto@ resRace = cast<RaceResources>(race); 1120 + if(resRace !is null) 1121 + resRace.levelRequirements(take.pl, i, reqList); 1122 + 1123 + for(uint i = 0, cnt = reqList.length; i < cnt; ++i) { 1124 + auto@ spec = reqList[i]; 1125 + allReqs.insertLast(spec); 1126 + 1127 + auto@ inner = queueColonize(spec, place=false); 1128 + 1129 + @inner.parent = q; 1130 + q.children.insertLast(inner); 1131 + 1132 + resolve(inner); 1133 + } 1134 + } 1135 + } 1136 + 1137 + return take.pl; 1138 + } 1139 + 1140 + return null; 1141 + } 1142 + 1143 + void kill(ColonizeQueue@ q) { 1144 + for(uint i = 0, cnt = q.children.length; i < cnt; ++i) 1145 + kill(q.children[i]); 1146 + q.children.length = 0; 1147 + if(q.forData !is null) 1148 + q.forData.isColonizing = false; 1149 + @q.parent = null; 1150 + } 1151 + 1152 + void modPotentialWeight(PotentialColonize@ c, double& weight) { 1153 + if(colonizeWeightObj !is null) 1154 + weight /= c.pl.position.distanceTo(colonizeWeightObj.position)/1000.0; 1155 + } 1156 + 1157 + bool update(ColonizeQueue@ q) { 1158 + //See if we can find a matching import request 1159 + if(q.forData is null && q.parent !is null && q.parent.target !is null) { 1160 + for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) { 1161 + auto@ req = resources.requested[i]; 1162 + if(req.isColonizing) 1163 + continue; 1164 + if(req.obj !is q.parent.target) 1165 + continue; 1166 + if(req.spec != q.spec) 1167 + continue; 1168 + 1169 + req.isColonizing = true; 1170 + @q.forData = req; 1171 + } 1172 + } 1173 + 1174 + //Cancel everything if our request is already being met 1175 + if(q.forData !is null && q.forData.beingMet) { 1176 + kill(q); 1177 + return false; 1178 + } 1179 + 1180 + //If it's not resolved, try to resolve it 1181 + if(q.target is null) 1182 + resolve(q); 1183 + 1184 + //If the colonization failed, try to find a new planet for it 1185 + if((q.step !is null && q.step.canceled) || (q.step is null && q.target !is null && !canBeColonized(q.target))) { 1186 + auto@ potentials = getPotentialColonize(); 1187 + PotentialColonize@ take; 1188 + double takeWeight = 0.0; 1189 + 1190 + for(uint i = 0, cnt = potentials.length; i < cnt; ++i) { 1191 + auto@ p = potentials[i]; 1192 + if(!q.spec.meets(p.resource)) 1193 + continue; 1194 + 1195 + //Skip planets out of our new territory target if we are colonizing a new one 1196 + if (_newTerritoryTarget !is null && p.pl.region !is _newTerritoryTarget) 1197 + continue; 1198 + 1199 + double w = p.weight; 1200 + modPotentialWeight(p, w); 1201 + 1202 + if(w > takeWeight) { 1203 + takeWeight = p.weight; 1204 + @take = p; 1205 + } 1206 + } 1207 + 1208 + if(take !is null) { 1209 + @q.target = take.pl; 1210 + @q.step = null; 1211 + potentials.remove(take); 1212 + } 1213 + } 1214 + 1215 + for(uint i = 0, cnt = q.children.length; i < cnt; ++i) { 1216 + if(!update(q.children[i])) { 1217 + @q.children[i].parent = null; 1218 + q.children.removeAt(i); 1219 + --i; --cnt; 1220 + } 1221 + } 1222 + 1223 + if(q.children.length == 0 && q.step !is null && q.step.completed) { 1224 + if(q.forData !is null) { 1225 + q.forData.isColonizing = false; 1226 + 1227 + PlanetAI@ plAI = planets.getAI(q.target); 1228 + if(plAI !is null) { 1229 + if(plAI.resources.length != 0) { 1230 + WaitUsed wait; 1231 + @wait.forData = q.forData; 1232 + @wait.resource = plAI.resources[0]; 1233 + waiting.insertLast(wait); 1234 + q.forData.isColonizing = true; 1235 + } 1236 + } 1237 + } 1238 + return false; 1239 + } 1240 + return true; 1241 + } 1242 + 1243 + void updateQueue() { 1244 + for(uint i = 0, cnt = queue.length; i < cnt; ++i) { 1245 + auto@ q = queue[i]; 1246 + if(!update(q)) { 1247 + queue.removeAt(i); 1248 + --i; --cnt; 1249 + } 1250 + } 1251 + } 1252 + 1253 + bool orderFromQueue(ColonizeQueue@ inside = null) { 1254 + auto@ list = inside is null ? this.queue : inside.children; 1255 + for(uint i = 0, cnt = list.length; i < cnt; ++i) { 1256 + auto@ q = list[i]; 1257 + if(q.step is null && q.target !is null) { 1258 + @q.step = colonize(q.target); 1259 + return true; 1260 + } 1261 + 1262 + if(orderFromQueue(q)) 1263 + return true; 1264 + } 1265 + return false; 1266 + } 1267 + 1268 + void dumpQueue(ColonizeQueue@ inside = null) { 1269 + auto@ list = inside is null ? this.queue : inside.children; 1270 + 1271 + string prefix = ""; 1272 + if(inside !is null) { 1273 + prefix += " "; 1274 + ColonizeQueue@ top = inside.parent; 1275 + while(top !is null) { 1276 + prefix += " "; 1277 + @top = top.parent; 1278 + } 1279 + } 1280 + 1281 + for(uint i = 0, cnt = list.length; i < cnt; ++i) { 1282 + auto@ q = list[i]; 1283 + 1284 + string txt = "- "+q.spec.dump(); 1285 + if(q.forData !is null) 1286 + txt += " for request "+q.forData.obj.name+""; 1287 + else if(q.parent !is null && q.parent.target !is null) 1288 + txt += " for parent "+q.parent.target.name+""; 1289 + if(q.target !is null) 1290 + txt += " ==> "+q.target.name; 1291 + print(prefix+txt); 1292 + 1293 + dumpQueue(q); 1294 + } 1295 + } 1296 + 1297 + void fillQueueFromRequests() { 1298 + for(uint i = 0, cnt = resources.requested.length; i < cnt && remainColonizations > 0; ++i) { 1299 + auto@ req = resources.requested[i]; 1300 + if(!req.isOpen) 1301 + continue; 1302 + if(!req.cycled) 1303 + continue; 1304 + if(req.claimedFor) 1305 + continue; 1306 + if(req.isColonizing) 1307 + continue; 1308 + 1309 + if(shouldQueueFor(req)) { 1310 + auto@ q = queueColonize(req.spec); 1311 + @q.forData = req; 1312 + req.isColonizing = true; 1313 + } 1314 + } 1315 + } 1316 + 1317 +}; 1318 + 1319 +AIComponent@ createColonization() { 1320 + return Colonization(); 1321 +}
Added scripts/server/empire_ai/weasel/Consider.as.
1 +// Consider 2 +// -------- 3 +// Helps AI usage hints to consider various things in the empire. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 +import empire_ai.weasel.Systems; 8 +import empire_ai.weasel.Planets; 9 +import empire_ai.weasel.Development; 10 +import empire_ai.weasel.Construction; 11 +import empire_ai.weasel.Fleets; 12 +import empire_ai.weasel.Resources; 13 +import empire_ai.weasel.Colonization; 14 +import empire_ai.weasel.Intelligence; 15 + 16 +import buildings; 17 +import ai.consider; 18 + 19 +from ai.artifacts import ArtifactConsider; 20 +from orbitals import OrbitalModule; 21 + 22 +class Consider : AIComponent, Considerer { 23 + Systems@ systems; 24 + Fleets@ fleets; 25 + Planets@ planets; 26 + Construction@ construction; 27 + Development@ development; 28 + Resources@ resources; 29 + Intelligence@ intelligence; 30 + Colonization@ colonization; 31 + 32 + void create() { 33 + @systems = cast<Systems>(ai.systems); 34 + @fleets = cast<Fleets>(ai.fleets); 35 + @planets = cast<Planets>(ai.planets); 36 + @development = cast<Development>(ai.development); 37 + @construction = cast<Construction>(ai.construction); 38 + @resources = cast<Resources>(ai.resources); 39 + @intelligence = cast<Intelligence>(ai.intelligence); 40 + @colonization = cast<Colonization>(ai.colonization); 41 + } 42 + 43 + Empire@ get_empire() { 44 + return ai.empire; 45 + } 46 + 47 + Object@ secondary; 48 + ArtifactConsider@ artifactConsider; 49 + double bestWeight; 50 + ImportData@ request; 51 + const BuildingType@ bldType; 52 + const OrbitalModule@ _module; 53 + ConsiderComponent@ comp; 54 + ConsiderFilter@ cfilter; 55 + 56 + double get_selectedWeight() { 57 + return bestWeight; 58 + } 59 + 60 + Object@ get_currentSupplier() { 61 + return secondary; 62 + } 63 + 64 + ArtifactConsider@ get_artifact() { 65 + return artifactConsider; 66 + } 67 + 68 + void set_artifact(ArtifactConsider@ cons) { 69 + @artifactConsider = cons; 70 + } 71 + 72 + double get_idleTime() { 73 + if(request !is null) 74 + return gameTime - request.idleSince; 75 + return 0.0; 76 + } 77 + 78 + double timeSinceMatchingColonize() { 79 + if(request is null) 80 + return INFINITY; 81 + return colonization.timeSinceMatchingColonize(request.spec); 82 + } 83 + 84 + const BuildingType@ get_building() { 85 + return bldType; 86 + } 87 + 88 + void set_building(const BuildingType@ type) { 89 + @bldType = type; 90 + } 91 + 92 + const OrbitalModule@ get_module() { 93 + return _module; 94 + } 95 + 96 + void set_module(const OrbitalModule@ type) { 97 + @_module = type; 98 + } 99 + 100 + ConsiderComponent@ get_component() { 101 + return comp; 102 + } 103 + 104 + void set_component(ConsiderComponent@ comp) { 105 + @this.comp = comp; 106 + } 107 + 108 + void set_filter(ConsiderFilter@ filter) { 109 + @this.cfilter = filter; 110 + } 111 + 112 + void clear() { 113 + @secondary = null; 114 + @artifactConsider = null; 115 + @request = null; 116 + @comp = null; 117 + @bldType = null; 118 + @cfilter = null; 119 + } 120 + 121 + Object@ OwnedSystems(const ConsiderHook& hook, uint limit = uint(-1)) { 122 + Object@ best; 123 + bestWeight = 0.0; 124 + 125 + uint offset = randomi(0, systems.owned.length-1); 126 + uint cnt = min(systems.owned.length, limit); 127 + for(uint i = 0; i < cnt; ++i) { 128 + uint index = (i+offset) % systems.owned.length; 129 + Region@ obj = systems.owned[index].obj; 130 + if(obj !is null) { 131 + if(cfilter !is null && !cfilter.filter(obj)) 132 + continue; 133 + double w = hook.consider(this, obj); 134 + if(w > bestWeight) { 135 + bestWeight = w; 136 + @best = obj; 137 + } 138 + } 139 + } 140 + 141 + clear(); 142 + return best; 143 + } 144 + 145 + Object@ Fleets(const ConsiderHook& hook) { 146 + Object@ best; 147 + bestWeight = 0.0; 148 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 149 + Object@ fleet = fleets.fleets[i].obj; 150 + if(fleet !is null) { 151 + if(cfilter !is null && !cfilter.filter(fleet)) 152 + continue; 153 + double w = hook.consider(this, fleet); 154 + if(w > bestWeight) { 155 + bestWeight = w; 156 + @best = fleet; 157 + } 158 + } 159 + } 160 + 161 + clear(); 162 + return best; 163 + } 164 + 165 + Object@ BorderSystems(const ConsiderHook& hook) { 166 + Object@ best; 167 + bestWeight = 0.0; 168 + for(uint i = 0, cnt = systems.border.length; i < cnt; ++i) { 169 + Region@ obj = systems.border[i].obj; 170 + if(obj.PlanetsMask & ~ai.mask == 0) 171 + continue; 172 + if(obj !is null) { 173 + if(cfilter !is null && !cfilter.filter(obj)) 174 + continue; 175 + double w = hook.consider(this, obj); 176 + if(w > bestWeight) { 177 + bestWeight = w; 178 + @best = obj; 179 + } 180 + } 181 + } 182 + for(uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) { 183 + Region@ obj = systems.outsideBorder[i].obj; 184 + if(obj.PlanetsMask & ~ai.mask == 0) 185 + continue; 186 + if(obj !is null) { 187 + if(cfilter !is null && !cfilter.filter(obj)) 188 + continue; 189 + double w = hook.consider(this, obj); 190 + if(w > bestWeight) { 191 + bestWeight = w; 192 + @best = obj; 193 + } 194 + } 195 + } 196 + 197 + clear(); 198 + return best; 199 + } 200 + 201 + Object@ OtherSystems(const ConsiderHook& hook) { 202 + Object@ best; 203 + bestWeight = 0.0; 204 + for(uint i = 0, cnt = intelligence.intel.length; i < cnt; ++i) { 205 + auto@ intel = intelligence.intel[i]; 206 + if(intel is null) 207 + continue; 208 + 209 + for(uint i = 0, cnt = intel.theirOwned.length; i < cnt; ++i) { 210 + Region@ obj = intel.theirOwned[i].obj; 211 + if(obj.PlanetsMask & ~ai.mask == 0) 212 + continue; 213 + if(obj !is null) { 214 + if(cfilter !is null && !cfilter.filter(obj)) 215 + continue; 216 + double w = hook.consider(this, obj); 217 + if(w > bestWeight) { 218 + bestWeight = w; 219 + @best = obj; 220 + } 221 + } 222 + } 223 + } 224 + 225 + clear(); 226 + return best; 227 + } 228 + 229 + Object@ SystemsInTerritory(const ConsiderHook& hook, const Territory& territory, uint limit = uint(-1)) { 230 + Object@ best; 231 + bestWeight = 0.0; 232 + 233 + uint regionCount = territory.getRegionCount(); 234 + uint offset = randomi(0, regionCount -1); 235 + uint cnt = min(regionCount, limit); 236 + for(uint i = 0; i < cnt; ++i) { 237 + uint index = (i+offset) % regionCount; 238 + Region@ obj = territory.getRegion(index); 239 + if(obj !is null) { 240 + if(cfilter !is null && !cfilter.filter(obj)) 241 + continue; 242 + double w = hook.consider(this, obj); 243 + if(w > bestWeight) { 244 + bestWeight = w; 245 + @best = obj; 246 + } 247 + } 248 + } 249 + 250 + clear(); 251 + return best; 252 + } 253 + 254 + Object@ ImportantPlanets(const ConsiderHook& hook) { 255 + Object@ best; 256 + bestWeight = 0.0; 257 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 258 + Object@ obj = development.focuses[i].obj; 259 + if(obj !is null) { 260 + if(cfilter !is null && !cfilter.filter(obj)) 261 + continue; 262 + double w = hook.consider(this, obj); 263 + if(w > bestWeight) { 264 + bestWeight = w; 265 + @best = obj; 266 + } 267 + } 268 + } 269 + 270 + clear(); 271 + return best; 272 + } 273 + 274 + Object@ ImportantPlanetsInTerritory(const ConsiderHook& hook, const Territory& territory) { 275 + Object@ best;; 276 + bestWeight = 0.0; 277 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 278 + Object@ obj = development.focuses[i].obj; 279 + if(obj !is null) { 280 + if(cfilter !is null && !cfilter.filter(obj)) 281 + continue; 282 + if (obj.region !is null) { 283 + if (obj.region.getTerritory(ai.empire) !is territory) 284 + continue; 285 + double w = hook.consider(this, obj); 286 + if(w > bestWeight) { 287 + bestWeight = w; 288 + @best = obj; 289 + } 290 + } 291 + } 292 + } 293 + 294 + clear(); 295 + return best; 296 + } 297 + 298 + Object@ AllPlanets(const ConsiderHook& hook) { 299 + Object@ best; 300 + bestWeight = 0.0; 301 + for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) { 302 + Object@ obj = planets.planets[i].obj; 303 + if(obj !is null) { 304 + if(cfilter !is null && !cfilter.filter(obj)) 305 + continue; 306 + double w = hook.consider(this, obj); 307 + if(w > bestWeight) { 308 + bestWeight = w; 309 + @best = obj; 310 + } 311 + } 312 + } 313 + 314 + clear(); 315 + return best; 316 + } 317 + 318 + Object@ PlanetsInTerritory(const ConsiderHook& hook, const Territory& territory) { 319 + Object@ best; 320 + bestWeight = 0.0; 321 + for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) { 322 + Object@ obj = planets.planets[i].obj; 323 + if(obj !is null) { 324 + if(cfilter !is null && !cfilter.filter(obj)) 325 + continue; 326 + if (obj.region !is null) { 327 + if (obj.region.getTerritory(ai.empire) !is territory) 328 + continue; 329 + double w = hook.consider(this, obj); 330 + if(w > bestWeight) { 331 + bestWeight = w; 332 + @best = obj; 333 + } 334 + } 335 + } 336 + } 337 + 338 + clear(); 339 + return best; 340 + } 341 + 342 + Object@ SomePlanets(const ConsiderHook& hook, uint count, bool alwaysImportant) { 343 + Object@ best; 344 + bestWeight = 0.0; 345 + if(alwaysImportant) { 346 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 347 + Object@ obj = development.focuses[i].obj; 348 + if(obj !is null) { 349 + if(cfilter !is null && !cfilter.filter(obj)) 350 + continue; 351 + double w = hook.consider(this, obj); 352 + if(w > bestWeight) { 353 + bestWeight = w; 354 + @best = obj; 355 + } 356 + } 357 + } 358 + } 359 + 360 + uint planetCount = planets.planets.length; 361 + uint offset = randomi(0, planetCount-1); 362 + uint cnt = min(count, planetCount); 363 + for(uint i = 0; i < cnt; ++i) { 364 + uint index = (offset+i) % planetCount; 365 + Object@ obj = planets.planets[index].obj; 366 + if(obj !is null) { 367 + if(cfilter !is null && !cfilter.filter(obj)) 368 + continue; 369 + double w = hook.consider(this, obj); 370 + if(w > bestWeight) { 371 + bestWeight = w; 372 + @best = obj; 373 + } 374 + } 375 + } 376 + 377 + clear(); 378 + return best; 379 + } 380 + 381 + Object@ FactoryPlanets(const ConsiderHook& hook) { 382 + Object@ best; 383 + bestWeight = 0.0; 384 + for(uint i = 0, cnt = construction.factories.length; i < cnt; ++i) { 385 + Object@ obj = construction.factories[i].obj; 386 + if(obj !is null && obj.isPlanet) { 387 + if(cfilter !is null && !cfilter.filter(obj)) 388 + continue; 389 + double w = hook.consider(this, obj); 390 + if(w > bestWeight) { 391 + bestWeight = w; 392 + @best = obj; 393 + } 394 + } 395 + } 396 + 397 + clear(); 398 + return best; 399 + } 400 + 401 + Object@ MatchingImportRequests(const ConsiderHook& hook, const ResourceType@ type, bool considerExisting) { 402 + Object@ best; 403 + bestWeight = 0.0; 404 + for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) { 405 + ImportData@ req = resources.requested[i]; 406 + if(!considerExisting) { 407 + if(req.beingMet || req.claimedFor) 408 + continue; 409 + } 410 + if(req.spec.meets(type, req.obj, req.obj)) { 411 + @secondary = null; 412 + @request = req; 413 + double w = hook.consider(this, req.obj); 414 + if(w > bestWeight) { 415 + if(cfilter !is null && !cfilter.filter(req.obj)) 416 + continue; 417 + bestWeight = w; 418 + @best = req.obj; 419 + } 420 + } 421 + } 422 + if(considerExisting) { 423 + for(uint i = 0, cnt = resources.used.length; i < cnt; ++i) { 424 + ExportData@ res = resources.used[i]; 425 + ImportData@ req = res.request; 426 + if(req !is null && req.spec.meets(type, req.obj, req.obj)) { 427 + @secondary = res.obj; 428 + @request = req; 429 + double w = hook.consider(this, req.obj); 430 + if(w > bestWeight) { 431 + if(cfilter !is null && !cfilter.filter(req.obj)) 432 + continue; 433 + bestWeight = w; 434 + @best = req.obj; 435 + } 436 + } 437 + } 438 + } 439 + 440 + clear(); 441 + return best; 442 + } 443 +}; 444 + 445 +AIComponent@ createConsider() { 446 + return Consider(); 447 +}
Added scripts/server/empire_ai/weasel/Construction.as.
1 +// Construction 2 +// ------------ 3 +// Manages factories and allows build requests for flagships, orbitals, and 4 +// anything else that requires labor. 5 +// 6 + 7 +import empire_ai.weasel.WeaselAI; 8 +import empire_ai.weasel.Budget; 9 +import empire_ai.weasel.Planets; 10 +import empire_ai.weasel.Designs; 11 +import empire_ai.weasel.Resources; 12 +import empire_ai.weasel.Systems; 13 +import empire_ai.weasel.Orbitals; 14 + 15 +import orbitals; 16 +import saving; 17 + 18 +import systems; 19 +import regions.regions; 20 + 21 +import ai.construction; 22 + 23 +from constructible import ConstructibleType; 24 +from constructions import ConstructionType, getConstructionType; 25 + 26 +class AllocateConstruction : IConstruction { 27 + protected bool _completed = false; 28 + protected bool _started = false; 29 + 30 + protected int _id = -1; 31 + uint moneyType = BT_Development; 32 + Factory@ tryFactory; 33 + double maxTime = INFINITY; 34 + double completedAt = 0; 35 + AllocateBudget@ alloc; 36 + int cost = 0; 37 + int maintenance = 0; 38 + double priority = 1.0; 39 + 40 + AllocateConstruction() { 41 + } 42 + 43 + int id { 44 + get const { return _id; } 45 + set { _id = value; } 46 + } 47 + 48 + bool get_started() const { return _started; } 49 + 50 + bool completed { 51 + get const { return _completed; } 52 + set { _completed = value; } 53 + } 54 + 55 + void _save(Construction& construction, SaveFile& file) { 56 + file << moneyType; 57 + construction.saveFactory(file, tryFactory); 58 + file << maxTime; 59 + file << _completed; 60 + file << _started; 61 + file << completedAt; 62 + construction.budget.saveAlloc(file, alloc); 63 + file << cost; 64 + file << maintenance; 65 + file << priority; 66 + save(construction, file); 67 + } 68 + 69 + void save(Construction& construction, SaveFile& file) { 70 + } 71 + 72 + void _load(Construction& construction, SaveFile& file) { 73 + file >> moneyType; 74 + @tryFactory = construction.loadFactory(file); 75 + file >> maxTime; 76 + file >> _completed; 77 + file >> _started; 78 + file >> completedAt; 79 + @alloc = construction.budget.loadAlloc(file); 80 + file >> cost; 81 + file >> maintenance; 82 + file >> priority; 83 + load(construction, file); 84 + } 85 + 86 + void load(Construction& construction, SaveFile& file) { 87 + } 88 + 89 + bool tick(AI& ai, Construction& construction, double time) { 90 + if(tryFactory !is null && alloc.allocated) { 91 + construction.start(tryFactory, this); 92 + return false; 93 + } 94 + return true; 95 + } 96 + 97 + void update(AI& ai, Factory@ f) { 98 + @alloc = cast<Budget>(ai.budget).allocate(moneyType, cost, maintenance, priority); 99 + } 100 + 101 + double laborCost(AI& ai, Object@ obj) { 102 + return 0.0; 103 + } 104 + 105 + bool canBuild(AI& ai, Factory@ f) { 106 + return true; 107 + } 108 + 109 + void construct(AI& ai, Factory@ f) { 110 + _started = true; 111 + } 112 + 113 + string toString() { 114 + return "construction"; 115 + } 116 +}; 117 + 118 +class BuildFlagship : AllocateConstruction, IFlagshipConstruction { 119 + protected const Design@ _design; 120 + double baseLabor = 0.0; 121 + DesignTarget@ target; 122 + 123 + BuildFlagship() { 124 + } 125 + 126 + BuildFlagship(const Design@ dsg) { 127 + set(dsg); 128 + } 129 + 130 + BuildFlagship(DesignTarget@ target) { 131 + @this.target = target; 132 + } 133 + 134 + const Design@ get_design() const { return _design; } 135 + 136 + void save(Construction& construction, SaveFile& file) { 137 + file << baseLabor; 138 + if(_design !is null) { 139 + file.write1(); 140 + file << _design; 141 + } 142 + else { 143 + file.write0(); 144 + } 145 + construction.designs.saveDesign(file, target); 146 + } 147 + 148 + void load(Construction& construction, SaveFile& file) { 149 + file >> baseLabor; 150 + if(file.readBit()) 151 + file >> _design; 152 + @target = construction.designs.loadDesign(file); 153 + } 154 + 155 + void set(const Design& dsg) { 156 + @_design = dsg.mostUpdated(); 157 + baseLabor = _design.total(HV_LaborCost); 158 + } 159 + 160 + double laborCost(AI& ai, Object@ obj) { 161 + return baseLabor; 162 + } 163 + 164 + bool tick(AI& ai, Construction& construction, double time) override { 165 + if(target !is null) { 166 + if(target.active !is null) { 167 + set(target.active); 168 + @target = null; 169 + } 170 + } 171 + return AllocateConstruction::tick(ai, construction, time); 172 + } 173 + 174 + bool canBuild(AI& ai, Factory@ f) override { 175 + if(!f.obj.canBuildShips) 176 + return false; 177 + return _design !is null; 178 + } 179 + 180 + void update(AI& ai, Factory@ f) { 181 + double c = _design.total(HV_BuildCost); 182 + c *= double(f.obj.shipBuildCost) / 100.0; 183 + c *= f.obj.constructionCostMod; 184 + 185 + cost = ceil(c); 186 + maintenance = ceil(_design.total(HV_MaintainCost)); 187 + 188 + AllocateConstruction::update(ai, f); 189 + } 190 + 191 + void construct(AI& ai, Factory@ f) { 192 + f.obj.buildFlagship(_design); 193 + AllocateConstruction::construct(ai, f); 194 + } 195 + 196 + string toString() { 197 + if(_design is null) 198 + return "flagship (design in progress)"; 199 + return "flagship " + _design.name; 200 + } 201 +}; 202 + 203 +class BuildFlagshipSourced : BuildFlagship { 204 + Object@ buildAt; 205 + Object@ buildFrom; 206 + 207 + BuildFlagshipSourced() { 208 + } 209 + 210 + BuildFlagshipSourced(const Design@ dsg) { 211 + set(dsg); 212 + } 213 + 214 + BuildFlagshipSourced(DesignTarget@ target) { 215 + @this.target = target; 216 + } 217 + 218 + void save(Construction& construction, SaveFile& file) override { 219 + BuildFlagship::save(construction, file); 220 + file << buildAt; 221 + file << buildFrom; 222 + } 223 + 224 + void load(Construction& construction, SaveFile& file) override { 225 + BuildFlagship::load(construction, file); 226 + file >> buildAt; 227 + file >> buildFrom; 228 + } 229 + 230 + bool canBuild(AI& ai, Factory@ f) override { 231 + if(buildAt !is null && f.obj !is buildAt) 232 + return false; 233 + return BuildFlagship::canBuild(ai, f); 234 + } 235 + 236 + void construct(AI& ai, Factory@ f) override { 237 + f.obj.buildFlagship(_design, constructFrom=buildFrom); 238 + AllocateConstruction::construct(ai, f); 239 + } 240 +}; 241 + 242 +class BuildStation : AllocateConstruction, IStationConstruction { 243 + protected const Design@ _design; 244 + double baseLabor = 0.0; 245 + DesignTarget@ target; 246 + vec3d position; 247 + bool local = false; 248 + 249 + BuildStation() { 250 + } 251 + 252 + BuildStation(const Design@ dsg, const vec3d& position) { 253 + this.position = position; 254 + set(dsg); 255 + } 256 + 257 + BuildStation(DesignTarget@ target, const vec3d& position) { 258 + this.position = position; 259 + @this.target = target; 260 + } 261 + 262 + BuildStation(const Design@ dsg, bool local) { 263 + this.local = true; 264 + set(dsg); 265 + } 266 + 267 + BuildStation(DesignTarget@ target, bool local) { 268 + @this.target = target; 269 + this.local = true; 270 + } 271 + 272 + const Design@ get_design() const { return _design; } 273 + 274 + void save(Construction& construction, SaveFile& file) { 275 + file << baseLabor; 276 + file << position; 277 + file << local; 278 + if(_design !is null) { 279 + file.write1(); 280 + file << _design; 281 + } 282 + else { 283 + file.write0(); 284 + } 285 + construction.designs.saveDesign(file, target); 286 + } 287 + 288 + void load(Construction& construction, SaveFile& file) { 289 + file >> baseLabor; 290 + file >> position; 291 + file >> local; 292 + if(file.readBit()) 293 + file >> _design; 294 + @target = construction.designs.loadDesign(file); 295 + } 296 + 297 + void set(const Design& dsg) { 298 + @_design = dsg.mostUpdated(); 299 + baseLabor = _design.total(HV_LaborCost); 300 + } 301 + 302 + double laborCost(AI& ai, Object@ obj) { 303 + double labor = baseLabor; 304 + 305 + labor *= obj.owner.OrbitalLaborCostFactor; 306 + 307 + if(!local) { 308 + Region@ reg = getRegion(position); 309 + Region@ targReg = obj.region; 310 + if(reg !is null && targReg !is null) { 311 + int hops = cast<Systems>(ai.systems).tradeDistance(targReg, reg); 312 + if(hops > 0) { 313 + double penalty = 1.0 + config::ORBITAL_LABOR_COST_STEP * double(hops); 314 + baseLabor *= penalty; 315 + } 316 + } 317 + } 318 + return labor; 319 + } 320 + 321 + bool tick(AI& ai, Construction& construction, double time) override { 322 + if(target !is null) { 323 + if(target.active !is null) { 324 + set(target.active); 325 + @target = null; 326 + } 327 + } 328 + return AllocateConstruction::tick(ai, construction, time); 329 + } 330 + 331 + bool canBuild(AI& ai, Factory@ f) override { 332 + if(_design is null) 333 + return false; 334 + if(!f.obj.canBuildOrbitals) 335 + return false; 336 + Region@ targReg = f.obj.region; 337 + if(targReg is null) 338 + return false; 339 + if(!local) { 340 + Region@ reg = getRegion(position); 341 + if(reg is null) 342 + return false; 343 + if(!cast<Systems>(ai.systems).canTrade(targReg, reg)) 344 + return false; 345 + } 346 + return true; 347 + } 348 + 349 + void update(AI& ai, Factory@ f) { 350 + double c = _design.total(HV_BuildCost); 351 + c *= f.obj.owner.OrbitalBuildCostFactor; 352 + c *= f.obj.constructionCostMod; 353 + 354 + cost = ceil(c); 355 + maintenance = ceil(_design.total(HV_MaintainCost)); 356 + 357 + AllocateConstruction::update(ai, f); 358 + } 359 + 360 + void construct(AI& ai, Factory@ f) { 361 + if(local) { 362 + position = f.obj.position; 363 + vec2d offset = random2d(f.obj.radius + 10.0, f.obj.radius + 100.0); 364 + position.x += offset.x; 365 + position.z += offset.y; 366 + } 367 + f.obj.buildStation(_design, position); 368 + AllocateConstruction::construct(ai, f); 369 + } 370 + 371 + string toString() { 372 + if(_design is null) 373 + return "station (design in progress)"; 374 + return "station " + _design.name; 375 + } 376 +}; 377 + 378 +class BuildOrbital : AllocateConstruction, IOrbitalConstruction { 379 + protected const OrbitalModule@ _module; 380 + double baseLabor = 0.0; 381 + const Planet@ planet; 382 + bool local = false; 383 + vec3d position; 384 + 385 + BuildOrbital() { 386 + } 387 + 388 + BuildOrbital(const OrbitalModule@ module, const vec3d& position) { 389 + this.position = position; 390 + @this._module = module; 391 + baseLabor = module.laborCost; 392 + } 393 + 394 + BuildOrbital(const OrbitalModule@ module, bool local) { 395 + this.local = true; 396 + @this._module = module; 397 + baseLabor = module.laborCost; 398 + } 399 + 400 + BuildOrbital(const OrbitalModule@ module, const Planet@ planet) { 401 + this.local = true; 402 + @this.planet = planet; 403 + @this._module = module; 404 + baseLabor = module.laborCost; 405 + } 406 + 407 + const OrbitalModule@ get_module() const { return _module; } 408 + 409 + void save(Construction& construction, SaveFile& file) { 410 + file << baseLabor; 411 + file << position; 412 + file << local; 413 + file.writeIdentifier(SI_Orbital, _module.id); 414 + } 415 + 416 + void load(Construction& construction, SaveFile& file) { 417 + file >> baseLabor; 418 + file >> position; 419 + file >> local; 420 + @_module = getOrbitalModule(file.readIdentifier(SI_Orbital)); 421 + } 422 + 423 + double laborCost(AI& ai, Object@ obj) { 424 + double labor = baseLabor; 425 + 426 + labor *= obj.owner.OrbitalLaborCostFactor; 427 + 428 + if(!local) { 429 + Region@ reg = getRegion(position); 430 + Region@ targReg = obj.region; 431 + if(reg !is null && targReg !is null) { 432 + int hops = cast<Systems>(ai.systems).tradeDistance(targReg, reg); 433 + if(hops > 0) { 434 + double penalty = 1.0 + config::ORBITAL_LABOR_COST_STEP * double(hops); 435 + baseLabor *= penalty; 436 + } 437 + } 438 + } 439 + return labor; 440 + } 441 + 442 + bool tick(AI& ai, Construction& construction, double time) override { 443 + return AllocateConstruction::tick(ai, construction, time); 444 + } 445 + 446 + bool canBuild(AI& ai, Factory@ f) override { 447 + if(_module is null) 448 + return false; 449 + if(!f.obj.canBuildOrbitals) 450 + return false; 451 + Region@ targReg = f.obj.region; 452 + if(targReg is null) 453 + return false; 454 + if(!local) { 455 + Region@ reg = getRegion(position); 456 + if(reg is null) 457 + return false; 458 + if(!cast<Systems>(ai.systems).canTrade(targReg, reg)) 459 + return false; 460 + } 461 + return true; 462 + } 463 + 464 + void update(AI& ai, Factory@ f) { 465 + double c = _module.buildCost; 466 + c *= f.obj.owner.OrbitalBuildCostFactor; 467 + c *= f.obj.constructionCostMod; 468 + 469 + cost = ceil(c); 470 + maintenance = _module.maintenance; 471 + 472 + AllocateConstruction::update(ai, f); 473 + } 474 + 475 + void construct(AI& ai, Factory@ f) { 476 + if(local) { 477 + if (planet !is null) { 478 + position = planet.position; 479 + vec2d offset = random2d(planet.OrbitSize * 0.8, planet.OrbitSize * 0.9); 480 + position.x += offset.x; 481 + position.z += offset.y; 482 + } 483 + else { 484 + position = f.obj.position; 485 + //vec2d offset = random2d(f.obj.radius + 10.0, f.obj.radius + 100.0); 486 + if (f.plAI !is null) { 487 + vec2d offset = random2d(f.plAI.obj.OrbitSize * 0.8, f.plAI.obj.OrbitSize * 0.9); 488 + position.x += offset.x; 489 + position.z += offset.y; 490 + } 491 + } 492 + } 493 + f.obj.buildOrbital(_module.id, position); 494 + AllocateConstruction::construct(ai, f); 495 + } 496 + 497 + string toString() { 498 + return "orbital " + _module.name; 499 + } 500 +}; 501 + 502 +class RetrofitShip : AllocateConstruction { 503 + Ship@ ship; 504 + double labor; 505 + 506 + RetrofitShip() { 507 + } 508 + 509 + RetrofitShip(Ship@ ship) { 510 + @this.ship = ship; 511 + labor = ship.getRetrofitLabor(); 512 + cost = ship.getRetrofitCost(); 513 + } 514 + 515 + void save(Construction& construction, SaveFile& file) { 516 + file << ship; 517 + file << labor; 518 + } 519 + 520 + void load(Construction& construction, SaveFile& file) { 521 + file >> ship; 522 + file >> labor; 523 + } 524 + 525 + double laborCost(AI& ai, Object@ obj) { 526 + return labor; 527 + } 528 + 529 + bool canBuild(AI& ai, Factory@ f) override { 530 + if(!f.obj.canBuildShips) 531 + return false; 532 + Region@ reg = ship.region; 533 + return reg !is null && reg is f.obj.region; 534 + } 535 + 536 + void construct(AI& ai, Factory@ f) { 537 + ship.retrofitFleetAt(f.obj); 538 + AllocateConstruction::construct(ai, f); 539 + } 540 + 541 + string toString() { 542 + return "retrofit "+ship.name; 543 + } 544 +}; 545 + 546 +class BuildConstruction : AllocateConstruction { 547 + const ConstructionType@ consType; 548 + 549 + BuildConstruction() { 550 + } 551 + 552 + BuildConstruction(const ConstructionType@ consType) { 553 + @this.consType = consType; 554 + } 555 + 556 + void save(Construction& construction, SaveFile& file) { 557 + file.writeIdentifier(SI_ConstructionType, consType.id); 558 + } 559 + 560 + void load(Construction& construction, SaveFile& file) { 561 + @consType = getConstructionType(file.readIdentifier(SI_ConstructionType)); 562 + } 563 + 564 + double laborCost(AI& ai, Object@ obj) { 565 + if(obj is null) 566 + return consType.laborCost; 567 + return consType.getLaborCost(obj); 568 + } 569 + 570 + bool canBuild(AI& ai, Factory@ f) override { 571 + return consType.canBuild(f.obj, ignoreCost=true); 572 + } 573 + 574 + void update(AI& ai, Factory@ f) { 575 + cost = consType.getBuildCost(f.obj); 576 + maintenance = consType.getMaintainCost(f.obj); 577 + 578 + AllocateConstruction::update(ai, f); 579 + } 580 + 581 + void construct(AI& ai, Factory@ f) { 582 + f.obj.buildConstruction(consType.id); 583 + AllocateConstruction::construct(ai, f); 584 + } 585 + 586 + string toString() { 587 + return "construction "+consType.name; 588 + } 589 +}; 590 + 591 +class Factory { 592 + Object@ obj; 593 + PlanetAI@ plAI; 594 + 595 + Factory@ exportingTo; 596 + 597 + AllocateConstruction@ active; 598 + double laborAim = 0.0; 599 + double laborIncome = 0.0; 600 + 601 + double idleSince = 0.0; 602 + double storedLabor = 0.0; 603 + double laborMaxStorage = 0.0; 604 + double buildingPenalty = 0.0; 605 + 606 + bool needsSupportLabor = false; 607 + double waitingSupportLabor = 0.0; 608 + uint curConstructionType = 0; 609 + bool valid = true; 610 + bool significantLabor = true; 611 + 612 + uint backgrounded = 0; 613 + Asteroid@ bgAsteroid; 614 + 615 + BuildingRequest@ curBuilding; 616 + ImportData@ curImport; 617 + 618 + void save(Construction& construction, SaveFile& file) { 619 + construction.planets.saveAI(file, plAI); 620 + construction.saveConstruction(file, active); 621 + file << laborAim; 622 + file << laborIncome; 623 + file << idleSince; 624 + file << storedLabor; 625 + file << laborMaxStorage; 626 + file << buildingPenalty; 627 + construction.planets.saveBuildingRequest(file, curBuilding); 628 + construction.resources.saveImport(file, curImport); 629 + file << backgrounded; 630 + file << bgAsteroid; 631 + file << curConstructionType; 632 + file << valid; 633 + file << needsSupportLabor; 634 + file << waitingSupportLabor; 635 + construction.saveFactory(file, exportingTo); 636 + } 637 + 638 + void load(Construction& construction, SaveFile& file) { 639 + @plAI = construction.planets.loadAI(file); 640 + @active = construction.loadConstruction(file); 641 + file >> laborAim; 642 + file >> laborIncome; 643 + file >> idleSince; 644 + file >> storedLabor; 645 + file >> laborMaxStorage; 646 + file >> buildingPenalty; 647 + @curBuilding = construction.planets.loadBuildingRequest(file); 648 + @curImport = construction.resources.loadImport(file); 649 + file >> backgrounded; 650 + file >> bgAsteroid; 651 + file >> curConstructionType; 652 + file >> valid; 653 + file >> needsSupportLabor; 654 + file >> waitingSupportLabor; 655 + @exportingTo = construction.loadFactory(file); 656 + } 657 + 658 + bool get_busy() { 659 + return active !is null; 660 + } 661 + 662 + bool get_needsLabor() { 663 + if(!valid) 664 + return false; 665 + if(obj.hasOrderedSupports) 666 + return true; 667 + if(active !is null) 668 + return true; 669 + if(needsSupportLabor) 670 + return true; 671 + if(obj.constructionCount > 0 && curConstructionType != CT_Export) 672 + return true; 673 + return false; 674 + } 675 + 676 + double laborToBear(AI& ai) { 677 + return laborIncome * ai.behavior.constructionMaxTime + storedLabor; 678 + } 679 + 680 + bool viable(AI& ai, AllocateConstruction@ alloc) { 681 + double labor = obj.laborIncome; 682 + double estTime = (alloc.laborCost(ai, obj) - storedLabor) / labor; 683 + if(estTime > alloc.maxTime) 684 + return false; 685 + return true; 686 + } 687 + 688 + bool tick(AI& ai, Construction& construction, double time) { 689 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 690 + valid = false; 691 + return false; 692 + } 693 + 694 + if(ai.behavior.forbidConstruction) return true; 695 + 696 + uint curCount = obj.constructionCount; 697 + curConstructionType = 0; 698 + bool isBackground = false; 699 + if(curCount != 0) { 700 + curConstructionType = obj.constructionType; 701 + isBackground = curConstructionType == CT_Asteroid || curConstructionType == CT_Export; 702 + } 703 + if(active !is null) { 704 + if(curCount <= backgrounded || (curCount == 1 && isBackground)) { 705 + if(construction.log) 706 + ai.print("Completed construction of "+active.toString()+" "+backgrounded+" / "+curCount, obj); 707 + active.completed = true; 708 + active.completedAt = gameTime; 709 + @active = null; 710 + idleSince = gameTime; 711 + backgrounded = 0; 712 + } 713 + } 714 + else { 715 + if(curCount < backgrounded) { 716 + backgrounded = curCount; 717 + } 718 + } 719 + 720 + //Background constructibles we don't need to do right now 721 + if(curCount > 1 && curConstructionType == CT_Asteroid && bgAsteroid !is null) { 722 + obj.moveConstruction(obj.constructionID[0], -1); 723 + backgrounded += 1; 724 + } 725 + if(curCount > 1 && curConstructionType == CT_Export && exportingTo !is null) { 726 + obj.cancelConstruction(obj.constructionID[0]); 727 + @exportingTo = null; 728 + } 729 + if(bgAsteroid !is null && (bgAsteroid.owner.valid || curCount == 0)) { 730 + if(bgAsteroid.owner is ai.empire) 731 + construction.planets.register(bgAsteroid); 732 + @bgAsteroid = null; 733 + } 734 + 735 + //Build warehouse(s) if we've been idle 736 + laborIncome = obj.laborIncome; 737 + storedLabor = obj.currentLaborStored; 738 + laborMaxStorage = obj.laborStorageCapacity; 739 + significantLabor = laborIncome >= 0.4 * construction.bestLabor && obj.baseLaborIncome > 4.0/60.0; 740 + if(storedLabor < laborMaxStorage) 741 + idleSince = gameTime; 742 + if(active is null && curBuilding is null && plAI !is null && gameTime - idleSince > ai.behavior.laborStoreIdleTimer && ai.behavior.buildLaborStorage && (laborMaxStorage+50) < ai.behavior.laborStoreMaxFillTime * max(obj.baseLaborIncome, laborAim) && significantLabor) { 743 + auto@ bld = ai.defs.LaborStorage; 744 + if(bld !is null && buildingPenalty < gameTime) { 745 + if(construction.log) 746 + ai.print("Build building "+bld.name+" for labor storage", obj); 747 + 748 + @curBuilding = construction.planets.requestBuilding(plAI, bld); 749 + } 750 + } 751 + 752 + //Remove waits on completed labor gains 753 + if(curBuilding !is null) { 754 + if(curBuilding.canceled || (curBuilding.built && curBuilding.getProgress() >= 1.0)) { 755 + if(construction.log) 756 + ai.print("Building construction for labor finished", obj); 757 + if(curBuilding.canceled) 758 + buildingPenalty = gameTime + 60.0; 759 + @curBuilding = null; 760 + } 761 + } 762 + if(curImport !is null) { 763 + if(curImport.beingMet) { 764 + if(construction.log) 765 + ai.print("Resource import for labor finished", obj); 766 + @curImport = null; 767 + } 768 + } 769 + 770 + //See if we need a new labor gain 771 + if(laborIncome < laborAim) { 772 + if(curImport is null && plAI !is null && obj.isPressureSaturated(TR_Labor) && obj.pressureCap < uint(obj.totalPressure) && gameTime > 6.0 * 60.0 && ai.behavior.buildFactoryForLabor) { 773 + ResourceSpec spec; 774 + spec.type = RST_Pressure_Level0; 775 + spec.pressureType = TR_Labor; 776 + 777 + if(construction.log) 778 + ai.print("Queue resource import for labor", obj); 779 + 780 + @curImport = construction.resources.requestResource(obj, spec, prioritize=true); 781 + } 782 + if(curBuilding is null && plAI !is null && ai.behavior.buildLaborStorage) { 783 + auto@ bld = ai.defs.Factory; 784 + if(bld !is null && buildingPenalty < gameTime) { 785 + if(construction.log) 786 + ai.print("Build building "+bld.name+" for labor", obj); 787 + 788 + @curBuilding = construction.planets.requestBuilding(plAI, bld); 789 + } 790 + } 791 + } 792 + 793 + //See if we should spend our labor on a labor export somewhere else 794 + if(exportingTo !is null && curConstructionType == CT_Export) { 795 + if(!exportingTo.valid || (!exportingTo.needsLabor && exportingTo !is construction.primaryFactory)) { 796 + obj.cancelConstruction(obj.constructionID[0]); 797 + @exportingTo = null; 798 + } 799 + } 800 + if(ai.behavior.distributeLaborExports) { 801 + if(curCount == 0 && obj.canExportLabor) { 802 + uint offset = randomi(0, construction.factories.length-1); 803 + for(uint i = 0, cnt = construction.factories.length; i < cnt; ++i) { 804 + auto@ other = construction.factories[(i+offset) % cnt]; 805 + if(other is this) 806 + continue; 807 + if(!other.obj.canImportLabor) 808 + continue; 809 + 810 + //Check if this is currently busy 811 + if(other !is construction.primaryFactory) { 812 + if(!other.needsLabor) 813 + continue; 814 + } 815 + 816 + obj.exportLaborTo(other.obj); 817 + @exportingTo = other; 818 + } 819 + } 820 + } 821 + 822 + //See if we should spend our labor trying to build an asteroid 823 + if(ai.behavior.backgroundBuildAsteroids) { 824 + if((curCount == 0 || (curConstructionType == CT_Export && curCount == 1)) && storedLabor >= laborMaxStorage * 0.5 && obj.canBuildAsteroids) { 825 + Asteroid@ roid = construction.getBackgroundAsteroid(this); 826 + if(roid !is null) { 827 + uint resCount = roid.getAvailableCount(); 828 + if(resCount != 0) { 829 + uint bestIndex = 0; 830 + int bestId = -1; 831 + double bestWeight = 0.0; 832 + 833 + if(ai.behavior.chooseAsteroidResource) { 834 + for(uint i = 0; i < resCount; ++i) { 835 + int resourceId = roid.getAvailable(i); 836 + double w = asteroidResourceValue(getResource(resourceId)); 837 + if(w > bestWeight) { 838 + bestWeight = w; 839 + bestId = resourceId; 840 + bestIndex = i; 841 + } 842 + } 843 + } 844 + else { 845 + bestIndex = randomi(0, resCount-1); 846 + bestId = roid.getAvailable(bestIndex); 847 + } 848 + 849 + double laborCost = roid.getAvailableCost(bestIndex); 850 + 851 + Region@ fromReg = obj.region; 852 + Region@ toReg = roid.region; 853 + if(fromReg !is null && toReg !is null) 854 + laborCost *= 1.0 + config::ASTEROID_COST_STEP * double(construction.systems.hopDistance(fromReg, toReg)); 855 + 856 + double timeTaken = laborIncome / laborCost; 857 + if(timeTaken < ai.behavior.constructionMaxTime || storedLabor >= laborMaxStorage * 0.95) { 858 + @bgAsteroid = roid; 859 + obj.buildAsteroid(roid, bestId); 860 + 861 + if(construction.log) 862 + ai.print("Use background labor to mine "+roid.name+" in "+roid.region.name, obj); 863 + } 864 + } 865 + } 866 + } 867 + } 868 + 869 + return true; 870 + } 871 + 872 + void aimForLabor(double labor) { 873 + if(labor > laborAim) 874 + laborAim = labor; 875 + } 876 +}; 877 + 878 +class Construction : AIComponent { 879 + array<Factory@> factories; 880 + Factory@ primaryFactory; 881 + double noFactoryTimer = 0.0; 882 + 883 + int nextAllocId = 0; 884 + array<AllocateConstruction@> allocations; 885 + 886 + double totalLabor = 0.0; 887 + double bestLabor = 0.0; 888 + 889 + BuildOrbital@ buildConsolidate; 890 + 891 + Budget@ budget; 892 + Planets@ planets; 893 + Orbitals@ orbitals; 894 + Resources@ resources; 895 + Designs@ designs; 896 + Systems@ systems; 897 + 898 + void create() { 899 + @budget = cast<Budget>(ai.budget); 900 + @planets = cast<Planets>(ai.planets); 901 + @resources = cast<Resources>(ai.resources); 902 + @designs = cast<Designs>(ai.designs); 903 + @systems = cast<Systems>(ai.systems); 904 + @orbitals = cast<Orbitals>(ai.orbitals); 905 + } 906 + 907 + void save(SaveFile& file) { 908 + file << nextAllocId; 909 + 910 + uint cnt = allocations.length; 911 + file << cnt; 912 + for(uint i = 0; i < cnt; ++i) 913 + saveConstruction(file, allocations[i]); 914 + 915 + cnt = factories.length; 916 + file << cnt; 917 + for(uint i = 0; i < cnt; ++i) { 918 + saveFactory(file, factories[i]); 919 + factories[i].save(this, file); 920 + } 921 + 922 + saveFactory(file, primaryFactory); 923 + file << noFactoryTimer; 924 + 925 + saveConstruction(file, buildConsolidate); 926 + } 927 + 928 + void load(SaveFile& file) { 929 + file >> nextAllocId; 930 + 931 + uint cnt = 0; 932 + file >> cnt; 933 + for(uint i = 0; i < cnt; ++i) { 934 + auto@ alloc = loadConstruction(file); 935 + if(alloc !is null) 936 + allocations.insertLast(alloc); 937 + } 938 + 939 + file >> cnt; 940 + for(uint i = 0; i < cnt; ++i) { 941 + Factory@ f = loadFactory(file); 942 + if(f !is null) 943 + f.load(this, file); 944 + else 945 + Factory().load(this, file); 946 + } 947 + 948 + @primaryFactory = loadFactory(file); 949 + file >> noFactoryTimer; 950 + 951 + @buildConsolidate = cast<BuildOrbital>(loadConstruction(file)); 952 + } 953 + 954 + void saveFactory(SaveFile& file, Factory@ f) { 955 + if(f !is null) { 956 + file.write1(); 957 + file << f.obj; 958 + } 959 + else { 960 + file.write0(); 961 + } 962 + } 963 + 964 + Factory@ loadFactory(SaveFile& file) { 965 + if(!file.readBit()) 966 + return null; 967 + 968 + Object@ obj; 969 + file >> obj; 970 + 971 + if(obj is null) 972 + return null; 973 + 974 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 975 + if(factories[i].obj is obj) 976 + return factories[i]; 977 + } 978 + 979 + Factory f; 980 + @f.obj = obj; 981 + factories.insertLast(f); 982 + return f; 983 + } 984 + 985 + array<AllocateConstruction@> savedConstructions; 986 + array<AllocateConstruction@> loadedConstructions; 987 + void postSave(AI& ai) { 988 + savedConstructions.length = 0; 989 + } 990 + void postLoad(AI& ai) { 991 + loadedConstructions.length = 0; 992 + } 993 + 994 + void saveConstruction(SaveFile& file, AllocateConstruction@ alloc) { 995 + if(alloc is null) { 996 + file.write0(); 997 + return; 998 + } 999 + 1000 + file.write1(); 1001 + file << alloc.id; 1002 + if(alloc.id == -1) { 1003 + storeConstruction(file, alloc); 1004 + } 1005 + else { 1006 + bool found = false; 1007 + for(uint i = 0, cnt = savedConstructions.length; i < cnt; ++i) { 1008 + if(savedConstructions[i] is alloc) { 1009 + found = true; 1010 + break; 1011 + } 1012 + } 1013 + 1014 + if(!found) { 1015 + storeConstruction(file, alloc); 1016 + savedConstructions.insertLast(alloc); 1017 + } 1018 + } 1019 + } 1020 + 1021 + AllocateConstruction@ loadConstruction(SaveFile& file) { 1022 + if(!file.readBit()) 1023 + return null; 1024 + 1025 + int id = 0; 1026 + file >> id; 1027 + if(id == -1) { 1028 + AllocateConstruction@ alloc = createConstruction(file); 1029 + alloc.id = id; 1030 + return alloc; 1031 + } 1032 + else { 1033 + for(uint i = 0, cnt = loadedConstructions.length; i < cnt; ++i) { 1034 + if(loadedConstructions[i].id == id) 1035 + return loadedConstructions[i]; 1036 + } 1037 + 1038 + AllocateConstruction@ alloc = createConstruction(file); 1039 + alloc.id = id; 1040 + loadedConstructions.insertLast(alloc); 1041 + return alloc; 1042 + } 1043 + } 1044 + 1045 + void storeConstruction(SaveFile& file, AllocateConstruction@ alloc) { 1046 + auto@ cls = getClass(alloc); 1047 + auto@ mod = cls.module; 1048 + 1049 + file << mod.name; 1050 + file << cls.name; 1051 + alloc._save(this, file); 1052 + } 1053 + 1054 + AllocateConstruction@ createConstruction(SaveFile& file) { 1055 + string modName; 1056 + string clsName; 1057 + 1058 + file >> modName; 1059 + file >> clsName; 1060 + 1061 + auto@ mod = getScriptModule(modName); 1062 + if(mod is null) { 1063 + error("ERROR: AI Load could not find module for alloc "+modName+"::"+clsName); 1064 + return null; 1065 + } 1066 + 1067 + auto@ cls = mod.getClass(clsName); 1068 + if(cls is null) { 1069 + error("ERROR: AI Load could not find class for alloc "+modName+"::"+clsName); 1070 + return null; 1071 + } 1072 + 1073 + auto@ alloc = cast<AllocateConstruction>(cls.create()); 1074 + if(alloc is null) { 1075 + error("ERROR: AI Load could not create class instance for alloc "+modName+"::"+clsName); 1076 + return null; 1077 + } 1078 + 1079 + alloc._load(this, file); 1080 + return alloc; 1081 + } 1082 + 1083 + void start() { 1084 + Object@ hw = ai.empire.Homeworld; 1085 + if(hw !is null) { 1086 + Factory f; 1087 + @f.obj = hw; 1088 + @f.plAI = planets.getAI(cast<Planet>(hw)); 1089 + 1090 + factories.insertLast(f); 1091 + } 1092 + } 1093 + 1094 + Factory@ get(Object@ obj) { 1095 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1096 + if(factories[i].obj is obj) 1097 + return factories[i]; 1098 + } 1099 + return null; 1100 + } 1101 + 1102 + Factory@ registerFactory(Object@ obj) { 1103 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1104 + if(factories[i].obj is obj) 1105 + return factories[i]; 1106 + } 1107 + Factory f; 1108 + @f.obj = obj; 1109 + factories.insertLast(f); 1110 + return f; 1111 + } 1112 + 1113 + Factory@ getFactory(Region@ region) { 1114 + Factory@ best; 1115 + double bestLabor = 0; 1116 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1117 + if(factories[i].obj.region !is region) 1118 + continue; 1119 + double l = factories[i].obj.laborIncome; 1120 + if(l > bestLabor) { 1121 + bestLabor = l; 1122 + @best = factories[i]; 1123 + } 1124 + } 1125 + return best; 1126 + } 1127 + 1128 + BuildConstruction@ buildConstruction(const ConstructionType@ type, double priority = 1.0, bool force = false, uint moneyType = BT_Development) { 1129 + //Potentially build a flagship 1130 + BuildConstruction f(type); 1131 + f.moneyType = moneyType; 1132 + f.priority = priority; 1133 + build(f, force=force); 1134 + return f; 1135 + } 1136 + 1137 + BuildFlagship@ buildFlagship(const Design@ dsg, double priority = 1.0, bool force = false) { 1138 + //Potentially build a flagship 1139 + BuildFlagship f(dsg); 1140 + f.moneyType = BT_Military; 1141 + f.priority = priority; 1142 + build(f, force=force); 1143 + return f; 1144 + } 1145 + 1146 + BuildFlagship@ buildFlagship(DesignTarget@ target, double priority = 1.0, bool force = false) { 1147 + //Potentially build a flagship 1148 + BuildFlagship f(target); 1149 + f.moneyType = BT_Military; 1150 + f.priority = priority; 1151 + build(f, force=force); 1152 + return f; 1153 + } 1154 + 1155 + BuildStation@ buildStation(const Design@ dsg, const vec3d& position, double priority = 1.0, bool force = false) { 1156 + //Potentially build a flagship 1157 + BuildStation f(dsg, position); 1158 + f.moneyType = BT_Military; 1159 + f.priority = priority; 1160 + build(f, force=force); 1161 + return f; 1162 + } 1163 + 1164 + BuildStation@ buildStation(DesignTarget@ target, const vec3d& position, double priority = 1.0, bool force = false) { 1165 + //Potentially build a flagship 1166 + BuildStation f(target, position); 1167 + f.moneyType = BT_Military; 1168 + f.priority = priority; 1169 + build(f, force=force); 1170 + return f; 1171 + } 1172 + 1173 + BuildOrbital@ buildOrbital(const OrbitalModule@ module, const vec3d& position, double priority = 1.0, bool force = false, uint moneyType = BT_Infrastructure) { 1174 + //Potentially build a flagship 1175 + BuildOrbital f(module, position); 1176 + f.moneyType = moneyType; 1177 + f.priority = priority; 1178 + build(f, force=force); 1179 + return f; 1180 + } 1181 + 1182 + BuildStation@ buildLocalStation(const Design@ dsg, double priority = 1.0, bool force = false) { 1183 + //Potentially build a flagship 1184 + BuildStation f(dsg, local=true); 1185 + f.moneyType = BT_Military; 1186 + f.priority = priority; 1187 + build(f, force=force); 1188 + return f; 1189 + } 1190 + 1191 + BuildStation@ buildLocalStation(DesignTarget@ target, double priority = 1.0, bool force = false) { 1192 + //Potentially build a flagship 1193 + BuildStation f(target, local=true); 1194 + f.moneyType = BT_Military; 1195 + f.priority = priority; 1196 + build(f, force=force); 1197 + return f; 1198 + } 1199 + 1200 + BuildOrbital@ buildLocalOrbital(const OrbitalModule@ module, double priority = 1.0, bool force = false, uint moneyType = BT_Infrastructure) { 1201 + //Potentially build a flagship 1202 + BuildOrbital f(module, local=true); 1203 + f.moneyType = moneyType; 1204 + f.priority = priority; 1205 + build(f, force=force); 1206 + return f; 1207 + } 1208 + 1209 + BuildOrbital@ buildLocalOrbital(const OrbitalModule@ module, Planet@ planet, double priority = 1.0, bool force = false, uint moneyType = BT_Infrastructure) { 1210 + //Potentially build a flagship 1211 + BuildOrbital f(module, planet); 1212 + f.moneyType = moneyType; 1213 + f.priority = priority; 1214 + build(f, force=force); 1215 + return f; 1216 + } 1217 + 1218 + RetrofitShip@ retrofit(Ship@ ship, double priority = 1.0, bool force = false) { 1219 + //Potentially build a flagship 1220 + RetrofitShip f(ship); 1221 + f.moneyType = BT_Military; 1222 + f.priority = priority; 1223 + build(f, force=force); 1224 + return f; 1225 + } 1226 + 1227 + AllocateConstruction@ build(AllocateConstruction@ alloc, bool force = false) { 1228 + //Add a construction into the potential constructions queue 1229 + if(!force) 1230 + alloc.maxTime = ai.behavior.constructionMaxTime; 1231 + alloc.id = nextAllocId++; 1232 + allocations.insertLast(alloc); 1233 + 1234 + if(log) 1235 + ai.print("Queue construction: "+alloc.toString()); 1236 + 1237 + return alloc; 1238 + } 1239 + 1240 + AllocateConstruction@ buildNow(AllocateConstruction@ alloc, Factory@ f) { 1241 + if(f.busy) 1242 + return null; 1243 + if(alloc.alloc !is null) 1244 + budget.applyNow(alloc.alloc); 1245 + start(f, alloc); 1246 + allocations.remove(alloc); 1247 + return alloc; 1248 + } 1249 + 1250 + void cancel(AllocateConstruction@ alloc) { 1251 + if(alloc.started || (alloc.alloc !is null && alloc.alloc.allocated)) 1252 + return; //TODO 1253 + 1254 + allocations.remove(alloc); 1255 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1256 + if(factories[i].active is alloc) 1257 + @factories[i].active = null; 1258 + } 1259 + 1260 + if(alloc.alloc !is null) 1261 + budget.remove(alloc.alloc); 1262 + } 1263 + 1264 + uint factInd = 0; 1265 + void tick(double time) { 1266 + //Manage factories 1267 + if(factories.length != 0) { 1268 + factInd = (factInd+1) % factories.length; 1269 + auto@ f = factories[factInd]; 1270 + if(!f.tick(ai, this, time)) 1271 + factories.removeAt(factInd); 1272 + } 1273 + } 1274 + 1275 + void start(Factory@ f, AllocateConstruction@ c) { 1276 + if(ai.behavior.forbidConstruction) { 1277 + cancel(c); 1278 + return; 1279 + } 1280 + //Actually construct something we've allocated budget for 1281 + @f.active = c; 1282 + @c.tryFactory = null; 1283 + 1284 + c.construct(ai, f); 1285 + 1286 + if(log) 1287 + ai.print("Construct: "+c.toString(), f.obj); 1288 + 1289 + for(uint i = 0, cnt = allocations.length; i < cnt; ++i) { 1290 + if(allocations[i].tryFactory is f) 1291 + @allocations[i].tryFactory = null; 1292 + } 1293 + } 1294 + 1295 + uint plCheck = 0; 1296 + uint orbCheck = 0; 1297 + double consTimer = 0.0; 1298 + void focusTick(double time) { 1299 + //Progress the allocations 1300 + for(uint n = 0, ncnt = allocations.length; n < ncnt; ++n) { 1301 + if(!allocations[n].tick(ai, this, time)) { 1302 + allocations.removeAt(n); 1303 + --n; --ncnt; 1304 + } 1305 + } 1306 + 1307 + if(ai.behavior.forbidConstruction) return; 1308 + 1309 + //See if anything we can potentially construct is constructible 1310 + totalLabor = 0.0; 1311 + bestLabor = 0.0; 1312 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1313 + auto@ f = factories[i]; 1314 + totalLabor += f.laborIncome; 1315 + if(f.laborIncome > bestLabor) 1316 + bestLabor = f.laborIncome; 1317 + } 1318 + 1319 + for(uint n = 0, ncnt = allocations.length; n < ncnt; ++n) { 1320 + auto@ alloc = allocations[n]; 1321 + if(alloc.tryFactory !is null) 1322 + continue; 1323 + 1324 + Factory@ bestFact; 1325 + double bestCur = 0.0; 1326 + 1327 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1328 + auto@ f = factories[i]; 1329 + if(f.busy) 1330 + continue; 1331 + if(!alloc.canBuild(ai, f)) 1332 + continue; 1333 + if(!f.viable(ai, alloc)) 1334 + continue; 1335 + 1336 + double w = f.laborIncome; 1337 + if(f is primaryFactory) 1338 + w *= 1.5; 1339 + if(f.exportingTo !is null) 1340 + w /= 0.75; 1341 + 1342 + if(w > bestCur) { 1343 + bestCur = w; 1344 + @bestFact = f; 1345 + } 1346 + } 1347 + 1348 + if(bestFact !is null) { 1349 + @alloc.tryFactory = bestFact; 1350 + alloc.update(ai, bestFact); 1351 + } 1352 + } 1353 + 1354 + //Classify our primary factory 1355 + if(primaryFactory is null) { 1356 + //Find our best factory 1357 + Factory@ best; 1358 + double bestWeight = 0.0; 1359 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1360 + auto@ f = factories[i]; 1361 + 1362 + double w = f.laborIncome; 1363 + w += 0.1 * f.laborAim; 1364 + if(f.obj.isPlanet) 1365 + w *= 100.0; 1366 + if(f.obj.isShip) 1367 + w *= 0.1; 1368 + 1369 + if(w > bestWeight) { 1370 + bestWeight = w; 1371 + @best = f; 1372 + } 1373 + } 1374 + 1375 + if(best !is null) { 1376 + @primaryFactory = best; 1377 + } 1378 + else { 1379 + noFactoryTimer += time; 1380 + if(noFactoryTimer > 3.0 * 60.0 && ai.defs.Factory !is null) { 1381 + //Just pick our highest level planet and hope for the best 1382 + PlanetAI@ best; 1383 + double bestWeight = 0.0; 1384 + 1385 + for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) { 1386 + auto@ plAI = planets.planets[i]; 1387 + double w = plAI.obj.level; 1388 + w += 0.5 * plAI.obj.resourceLevel; 1389 + 1390 + if(w > bestWeight) { 1391 + bestWeight = w; 1392 + @best = plAI; 1393 + } 1394 + } 1395 + 1396 + if(best !is null) { 1397 + Factory f; 1398 + @f.obj = best.obj; 1399 + @f.plAI = best; 1400 + 1401 + factories.insertLast(f); 1402 + @primaryFactory = f; 1403 + } 1404 + 1405 + noFactoryTimer = 0.0; 1406 + } 1407 + } 1408 + } 1409 + else { 1410 + noFactoryTimer = 0.0; 1411 + } 1412 + 1413 + //Find new factories 1414 + if(planets.planets.length != 0) { 1415 + plCheck = (plCheck+1) % planets.planets.length; 1416 + PlanetAI@ plAI = planets.planets[plCheck]; 1417 + if(plAI.obj.laborIncome > 0 && plAI.obj.canBuildShips) { 1418 + if(get(plAI.obj) is null) { 1419 + Factory f; 1420 + @f.obj = plAI.obj; 1421 + @f.plAI = plAI; 1422 + 1423 + factories.insertLast(f); 1424 + } 1425 + } 1426 + } 1427 + if(orbitals.orbitals.length != 0) { 1428 + orbCheck = (orbCheck+1) % orbitals.orbitals.length; 1429 + OrbitalAI@ orbAI = orbitals.orbitals[orbCheck]; 1430 + if(orbAI.obj.hasConstruction && orbAI.obj.laborIncome > 0 1431 + && !cast<Orbital>(orbAI.obj).hasMaster()) { 1432 + if(get(orbAI.obj) is null) { 1433 + Factory f; 1434 + @f.obj = orbAI.obj; 1435 + 1436 + factories.insertLast(f); 1437 + } 1438 + } 1439 + } 1440 + 1441 + //See if we should switch our primary factory 1442 + if(primaryFactory !is null) { 1443 + if(!primaryFactory.valid) { 1444 + @primaryFactory = null; 1445 + } 1446 + else { 1447 + Factory@ best; 1448 + double bestLabor = 0.0; 1449 + double primaryLabor = primaryFactory.laborIncome; 1450 + bool canImport = primaryFactory.obj.canImportLabor; 1451 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1452 + auto@ f = factories[i]; 1453 + double checkLabor = f.laborIncome; 1454 + if(f.obj.isShip) 1455 + checkLabor *= 0.1; 1456 + if(f.exportingTo !is primaryFactory && canImport) 1457 + primaryLabor += checkLabor * 0.75; 1458 + if(checkLabor > bestLabor) { 1459 + bestLabor = checkLabor; 1460 + @best = f; 1461 + } 1462 + } 1463 + 1464 + if(best !is null && bestLabor > 1.5 * primaryLabor) 1465 + @primaryFactory = best; 1466 + } 1467 + } 1468 + 1469 + //See if we should consolidate at a shipyard 1470 + if(buildConsolidate !is null && buildConsolidate.completed) { 1471 + @buildConsolidate = null; 1472 + consTimer = gameTime + 60.0; 1473 + } 1474 + else if(ai.behavior.consolidateLaborExports && primaryFactory !is null && ai.defs.Shipyard !is null && buildConsolidate is null && !primaryFactory.obj.canImportLabor && consTimer < gameTime) { 1475 + double totalLabor = 0.0, bestLabor = 0.0; 1476 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1477 + double inc = factories[i].obj.baseLaborIncome; 1478 + if(factories[i].obj.canExportLabor) 1479 + totalLabor += inc; 1480 + if(factories[i].laborIncome > bestLabor) 1481 + bestLabor = factories[i].laborIncome; 1482 + } 1483 + 1484 + if(bestLabor < totalLabor * 0.6) { 1485 + Factory@ bestConsolidate; 1486 + double bestWeight = 0.0; 1487 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1488 + auto@ f = factories[i]; 1489 + if(!f.obj.canImportLabor) 1490 + continue; 1491 + 1492 + double w = f.obj.baseLaborIncome; 1493 + w /= f.obj.position.distanceTo(primaryFactory.obj.position); 1494 + 1495 + if(w > bestWeight) { 1496 + bestWeight = w; 1497 + @bestConsolidate = f; 1498 + } 1499 + } 1500 + 1501 + if(bestConsolidate !is null) { 1502 + if(log) 1503 + ai.print("Set shipyard for consolidate.", bestConsolidate.obj.region); 1504 + @primaryFactory = bestConsolidate; 1505 + } 1506 + else { 1507 + Region@ reg = primaryFactory.obj.region; 1508 + if(reg !is null) { 1509 + vec3d pos = reg.position; 1510 + vec2d offset = random2d(reg.radius * 0.4, reg.radius * 0.8); 1511 + pos.x += offset.x; 1512 + pos.z += offset.y; 1513 + 1514 + if(log) 1515 + ai.print("Build shipyard for consolidate.", reg); 1516 + 1517 + @buildConsolidate = buildOrbital(ai.defs.Shipyard, pos); 1518 + } 1519 + } 1520 + } 1521 + } 1522 + } 1523 + 1524 + bool isGettingAsteroid(Asteroid@ asteroid) { 1525 + for(uint i = 0, cnt = factories.length; i < cnt; ++i) { 1526 + if(factories[i].bgAsteroid is asteroid) 1527 + return true; 1528 + } 1529 + return false; 1530 + } 1531 + 1532 + Asteroid@ getBackgroundAsteroid(Factory& f) { 1533 + double closest = INFINITY; 1534 + Asteroid@ best; 1535 + Region@ reg = f.obj.region; 1536 + if(reg is null) 1537 + return null; 1538 + 1539 + uint cnt = systems.owned.length; 1540 + uint offset = randomi(0, cnt-1); 1541 + for(uint i = 0, check = min(3, cnt); i < check; ++i) { 1542 + auto@ sys = systems.owned[(i+offset)%cnt]; 1543 + double dist = sys.obj.position.distanceToSQ(f.obj.position); 1544 + if(dist > closest) 1545 + continue; 1546 + if(!sys.obj.sharesTerritory(ai.empire, reg)) 1547 + continue; 1548 + 1549 + for(uint n = 0, ncnt = sys.asteroids.length; n < ncnt; ++n) { 1550 + Asteroid@ roid = sys.asteroids[n]; 1551 + if(roid.owner.valid) 1552 + continue; 1553 + if(roid.getAvailableCount() == 0) 1554 + continue; 1555 + if(isGettingAsteroid(roid)) 1556 + continue; 1557 + 1558 + closest = dist; 1559 + @best = roid; 1560 + break; 1561 + } 1562 + } 1563 + 1564 + cnt = systems.outsideBorder.length; 1565 + offset = randomi(0, cnt-1); 1566 + for(uint i = 0, check = min(3, cnt); i < check; ++i) { 1567 + auto@ sys = systems.outsideBorder[(i+offset)%cnt]; 1568 + double dist = sys.obj.position.distanceToSQ(f.obj.position); 1569 + if(dist > closest) 1570 + continue; 1571 + if(!sys.obj.sharesTerritory(ai.empire, reg)) 1572 + continue; 1573 + 1574 + for(uint n = 0, ncnt = sys.asteroids.length; n < ncnt; ++n) { 1575 + Asteroid@ roid = sys.asteroids[n]; 1576 + if(roid.owner.valid) 1577 + continue; 1578 + if(roid.getAvailableCount() == 0) 1579 + continue; 1580 + if(isGettingAsteroid(roid)) 1581 + continue; 1582 + 1583 + closest = dist; 1584 + @best = roid; 1585 + break; 1586 + } 1587 + } 1588 + 1589 + return best; 1590 + } 1591 +}; 1592 + 1593 +double asteroidResourceValue(const ResourceType@ type) { 1594 + if(type is null) 1595 + return 0.0; 1596 + double w = 1.0; 1597 + w += type.level * 10.0; 1598 + w += type.totalPressure; 1599 + if(type.cls !is null) 1600 + w += 5.0; 1601 + return w; 1602 +} 1603 + 1604 +AIComponent@ createConstruction() { 1605 + return Construction(); 1606 +}
Added scripts/server/empire_ai/weasel/Creeping.as.
1 +// Creeping 2 +// -------- 3 +// Uses fleets that aren't currently doing anything to eliminate creeps. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 + 8 +import empire_ai.weasel.Fleets; 9 +import empire_ai.weasel.Systems; 10 +import empire_ai.weasel.Movement; 11 +import empire_ai.weasel.searches; 12 + 13 +import saving; 14 +from empire import Creeps; 15 + 16 +class CreepingMission : Mission { 17 + Pickup@ pickup; 18 + Object@ protector; 19 + 20 + MoveOrder@ move; 21 + 22 + void save(Fleets& fleets, SaveFile& file) { 23 + file << pickup; 24 + file << protector; 25 + fleets.movement.saveMoveOrder(file, move); 26 + } 27 + 28 + void load(Fleets& fleets, SaveFile& file) { 29 + file >> pickup; 30 + file >> protector; 31 + @move = fleets.movement.loadMoveOrder(file); 32 + } 33 + 34 + void start(AI& ai, FleetAI& fleet) override { 35 + vec3d position = pickup.position; 36 + double dist = fleet.radius; 37 + if(protector !is null && protector.valid) 38 + dist = fleet.obj.getEngagementRange(); 39 + position += (fleet.obj.position - pickup.position).normalized(dist); 40 + 41 + @move = cast<Movement>(ai.movement).move(fleet.obj, position); 42 + } 43 + 44 + void tick(AI& ai, FleetAI& fleet, double time) { 45 + if(move !is null) { 46 + if(move.completed) { 47 + if(protector !is null && protector.valid) { 48 + if(!protector.isVisibleTo(ai.empire)) { //Yo nebulas are scary yo 49 + fleet.obj.addMoveOrder(protector.position); 50 + fleet.obj.addAttackOrder(protector, append=true); 51 + } 52 + else { 53 + fleet.obj.addAttackOrder(protector); 54 + } 55 + } 56 + @move = null; 57 + } 58 + else if(move.failed) { 59 + canceled = true; 60 + return; 61 + } 62 + else 63 + return; 64 + } 65 + if(protector is null || !protector.valid) { 66 + if(!fleet.obj.hasOrders) { 67 + if(pickup is null || !pickup.valid) { 68 + if(cast<Creeping>(ai.creeping).log) 69 + ai.print("Finished clearing creep camp", fleet.obj); 70 + completed = true; 71 + } 72 + else { 73 + fleet.obj.addPickupOrder(pickup); 74 + @protector = null; 75 + } 76 + } 77 + } 78 + else { 79 + if((fleet.filled < 0.3 || fleet.supplies < 0.3 || fleet.flagshipHealth < 0.4) 80 + && protector.getFleetStrength() * ai.behavior.remnantOverkillFactor > fleet.strength) { 81 + //Holy shit what's going on? ABORT! ABORT! 82 + if(cast<Creeping>(ai.creeping).logCritical) 83 + ai.print("ABORTED CREEPING: About to lose fight", fleet.obj); 84 + canceled = true; 85 + cast<Fleets>(ai.fleets).returnToBase(fleet, MP_Critical); 86 + } 87 + } 88 + } 89 +}; 90 + 91 +class ClearMission : Mission { 92 + Region@ region; 93 + Object@ eliminate; 94 + 95 + MoveOrder@ move; 96 + 97 + void save(Fleets& fleets, SaveFile& file) { 98 + file << region; 99 + file << eliminate; 100 + fleets.movement.saveMoveOrder(file, move); 101 + } 102 + 103 + void load(Fleets& fleets, SaveFile& file) { 104 + file >> region; 105 + file >> eliminate; 106 + @move = fleets.movement.loadMoveOrder(file); 107 + } 108 + 109 + void start(AI& ai, FleetAI& fleet) override { 110 + @move = cast<Movement>(ai.movement).move(fleet.obj, region); 111 + } 112 + 113 + void tick(AI& ai, FleetAI& fleet, double time) { 114 + if(move !is null) { 115 + if(move.completed) { 116 + @move = null; 117 + } 118 + else if(move.failed) { 119 + canceled = true; 120 + return; 121 + } 122 + else 123 + return; 124 + } 125 + 126 + if(ai.behavior.forbidCreeping) return; 127 + 128 + if(eliminate is null) { 129 + @eliminate = cast<Creeping>(ai.creeping).findRemnants(region); 130 + if(eliminate is null) { 131 + completed = true; 132 + return; 133 + } 134 + } 135 + 136 + if(eliminate !is null) { 137 + if(!eliminate.valid) { 138 + @eliminate = null; 139 + } 140 + else { 141 + if(!fleet.obj.hasOrders) 142 + fleet.obj.addAttackOrder(eliminate); 143 + 144 + if((fleet.filled < 0.3 || fleet.supplies < 0.3 || fleet.flagshipHealth < 0.4) 145 + && eliminate.getFleetStrength() * ai.behavior.remnantOverkillFactor > fleet.strength) { 146 + //Holy shit what's going on? ABORT! ABORT! 147 + if(cast<Creeping>(ai.creeping).logCritical) 148 + ai.print("ABORTED CREEPING: About to lose fight", fleet.obj); 149 + canceled = true; 150 + cast<Fleets>(ai.fleets).returnToBase(fleet, MP_Critical); 151 + } 152 + } 153 + } 154 + } 155 +}; 156 + 157 +final class CreepPenalty : Savable { 158 + Object@ obj; 159 + double until; 160 + 161 + void save(SaveFile& file) { 162 + file << obj; 163 + file << until; 164 + } 165 + 166 + void load(SaveFile& file) { 167 + file >> obj; 168 + file >> until; 169 + } 170 +}; 171 + 172 +final class ClearSystem { 173 + SystemAI@ sys; 174 + array<Ship@> remnants; 175 + 176 + void save(Creeping& creeping, SaveFile& file) { 177 + creeping.systems.saveAI(file, sys); 178 + uint cnt = remnants.length; 179 + file << cnt; 180 + for(uint i = 0; i < cnt; ++i) 181 + file << remnants[i]; 182 + } 183 + 184 + void load(Creeping& creeping, SaveFile& file) { 185 + @sys = creeping.systems.loadAI(file); 186 + uint cnt = 0; 187 + file >> cnt; 188 + for(uint i = 0; i < cnt; ++i) { 189 + Ship@ remn; 190 + file >> remn; 191 + if(remn !is null) 192 + remnants.insertLast(remn); 193 + } 194 + } 195 + 196 + void record() { 197 + auto@ objs = findEnemies(sys.obj, null, Creeps.mask); 198 + for(uint i = 0, cnt = objs.length; i < cnt; ++i) { 199 + Ship@ ship = cast<Ship>(objs[i]); 200 + if(ship !is null) 201 + remnants.insertLast(ship); 202 + } 203 + } 204 + 205 + double getStrength() { 206 + double str = 0.0; 207 + for(uint i = 0, cnt = remnants.length; i < cnt; ++i) { 208 + if(remnants[i].valid) 209 + str += sqrt(remnants[i].getFleetStrength()); 210 + } 211 + return str * str; 212 + } 213 +}; 214 + 215 +class Creeping : AIComponent { 216 + Systems@ systems; 217 + Fleets@ fleets; 218 + 219 + array<SystemAI@> requested; 220 + array<CreepPenalty@> penalties; 221 + array<CreepingMission@> active; 222 + 223 + array<ClearSystem@> quarantined; 224 + 225 + void create() { 226 + @systems = cast<Systems>(ai.systems); 227 + @fleets = cast<Fleets>(ai.fleets); 228 + } 229 + 230 + void save(SaveFile& file) { 231 + uint cnt = requested.length; 232 + file << cnt; 233 + for(uint i = 0; i < cnt; ++i) 234 + systems.saveAI(file, requested[i]); 235 + 236 + cnt = penalties.length; 237 + file << cnt; 238 + for(uint i = 0; i < cnt; ++i) 239 + file << penalties[i]; 240 + 241 + cnt = active.length; 242 + file << cnt; 243 + for(uint i = 0; i < cnt; ++i) 244 + fleets.saveMission(file, active[i]); 245 + 246 + cnt = quarantined.length; 247 + file << cnt; 248 + for(uint i = 0; i < cnt; ++i) 249 + quarantined[i].save(this, file); 250 + } 251 + 252 + void load(SaveFile& file) { 253 + uint cnt = 0; 254 + file >> cnt; 255 + for(uint i = 0; i < cnt; ++i) { 256 + auto@ sys = systems.loadAI(file); 257 + if(sys !is null) 258 + requested.insertLast(sys); 259 + } 260 + 261 + file >> cnt; 262 + for(uint i = 0; i < cnt; ++i) { 263 + CreepPenalty pen; 264 + file >> pen; 265 + if(pen.obj !is null) 266 + penalties.insertLast(pen); 267 + } 268 + 269 + file >> cnt; 270 + for(uint i = 0; i < cnt; ++i) { 271 + auto@ miss = cast<CreepingMission>(fleets.loadMission(file)); 272 + if(miss !is null) 273 + active.insertLast(miss); 274 + } 275 + 276 + if(file >= SV_0151) { 277 + file >> cnt; 278 + for(uint i = 0; i < cnt; ++i) { 279 + ClearSystem qsys; 280 + qsys.load(this, file); 281 + quarantined.insertLast(qsys); 282 + } 283 + } 284 + } 285 + 286 + void requestClear(SystemAI@ system) { 287 + if(system is null) 288 + return; 289 + if(log) 290 + ai.print("Requested creep camp clear", system.obj); 291 + if(requested.find(system) == -1) 292 + requested.insertLast(system); 293 + } 294 + 295 + CreepingMission@ creepWithFleet(FleetAI@ fleet, Pickup@ pickup, Object@ protector = null) { 296 + if(protector is null) 297 + @protector = pickup.getProtector(); 298 + 299 + if(log) 300 + ai.print("Clearing creep camp in "+pickup.region.name, fleet.obj); 301 + 302 + CreepingMission mission; 303 + @mission.pickup = pickup; 304 + @mission.protector = protector; 305 + 306 + fleets.performMission(fleet, mission); 307 + active.insertLast(mission); 308 + return mission; 309 + } 310 + 311 + Pickup@ best; 312 + Object@ bestProtector; 313 + vec3d ourPosition; 314 + double bestWeight; 315 + double ourStrength; 316 + 317 + void check(SystemAI@ sys, double weight = 1.0) { 318 + for(uint n = 0, ncnt = sys.pickups.length; n < ncnt; ++n) { 319 + Pickup@ pickup = sys.pickups[n]; 320 + Object@ protector = sys.pickupProtectors[n]; 321 + 322 + if(!pickup.valid) 323 + continue; 324 + 325 + double protStrength; 326 + if(protector !is null && protector.valid) { 327 + protStrength = protector.getFleetStrength(); 328 + 329 + if(protStrength * ai.behavior.remnantOverkillFactor > ourStrength) 330 + continue; 331 + } 332 + else 333 + protStrength = 1.0; 334 + 335 + if(isCreeping(pickup)) 336 + continue; 337 + 338 + double w = weight; 339 + w /= protStrength / 1000.0; 340 + w /= pickup.position.distanceTo(ourPosition); 341 + 342 + if(w > bestWeight) { 343 + bestWeight = w; 344 + @best = pickup; 345 + @bestProtector = protector; 346 + } 347 + } 348 + } 349 + 350 + void penalize(Object@ obj, double time) { 351 + for(uint i = 0, cnt = penalties.length; i < cnt; ++i) { 352 + if(penalties[i].obj is obj) { 353 + penalties[i].until = max(penalties[i].until, gameTime + time); 354 + return; 355 + } 356 + } 357 + 358 + CreepPenalty p; 359 + @p.obj = obj; 360 + p.until = gameTime + time; 361 + penalties.insertLast(p); 362 + } 363 + 364 + bool isPenalized(Object@ obj) { 365 + for(uint i = 0, cnt = penalties.length; i < cnt; ++i) { 366 + if(penalties[i].obj is obj) 367 + return true; 368 + } 369 + return false; 370 + } 371 + 372 + bool isCreeping(Pickup@ pickup) { 373 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 374 + if(active[i].pickup is pickup) 375 + return true; 376 + } 377 + return false; 378 + } 379 + 380 + CreepingMission@ creepWithFleet(FleetAI@ fleet) { 381 + @best = null; 382 + @bestProtector = null; 383 + bestWeight = 0.0; 384 + ourStrength = fleet.strength; 385 + ourPosition = fleet.obj.position; 386 + 387 + //Check requested systems first 388 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 389 + auto@ sys = requested[i]; 390 + 391 + if(sys.pickups.length == 0) { 392 + requested.removeAt(i); 393 + --i; --cnt; 394 + continue; 395 + } 396 + 397 + if(haveQuarantinedSystem(sys)) 398 + continue; 399 + 400 + check(sys); 401 + } 402 + 403 + if(best !is null) 404 + return creepWithFleet(fleet, best, bestProtector); 405 + 406 + if(!ai.behavior.remnantAllowArbitraryClear) 407 + return null; 408 + 409 + if(log) 410 + ai.print("Attempted to find creep camp to clear", fleet.obj); 411 + 412 + //Check systems in our territory 413 + for(uint i = 0, cnt = systems.owned.length; i < cnt; ++i) { 414 + SystemAI@ sys = systems.owned[i]; 415 + if(sys.pickups.length != 0) 416 + check(sys); 417 + } 418 + 419 + if(best !is null) 420 + return creepWithFleet(fleet, best, bestProtector); 421 + 422 + //Check systems just outside our border 423 + for(uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) { 424 + SystemAI@ sys = systems.outsideBorder[i]; 425 + if(sys.seenPresent & ai.otherMask != 0) 426 + continue; 427 + if(haveQuarantinedSystem(sys)) 428 + continue; 429 + if(sys.pickups.length != 0) 430 + check(sys, 1.0 / double(1.0 + sys.hopDistance)); 431 + } 432 + 433 + if(best !is null) 434 + return creepWithFleet(fleet, best, bestProtector); 435 + 436 + penalize(fleet.obj, 90.0); 437 + return null; 438 + } 439 + 440 + Object@ findRemnants(Region@ reg) { 441 + for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) { 442 + auto@ qsys = quarantined[i]; 443 + if(qsys.sys.obj !is reg) 444 + continue; 445 + 446 + for(uint n = 0, ncnt = qsys.remnants.length; n < ncnt; ++n) { 447 + auto@ remn = qsys.remnants[n]; 448 + if(remn is null || !remn.valid) 449 + continue; 450 + return remn; 451 + } 452 + } 453 + return null; 454 + } 455 + 456 + ClearMission@ sendToClear(FleetAI@ fleet, ClearSystem@ system) { 457 + ClearMission miss; 458 + @miss.region = system.sys.obj; 459 + 460 + fleets.performMission(fleet, miss); 461 + if(log) 462 + ai.print("Clear remnant defenders in "+miss.region.name, fleet.obj); 463 + return miss; 464 + } 465 + 466 + bool isQuarantined(SystemAI@ sys) { 467 + if(sys.planets.length == 0) 468 + return false; 469 + for(uint i = 0, cnt = sys.planets.length; i < cnt; ++i) { 470 + if(!sys.planets[i].quarantined) 471 + return false; 472 + } 473 + return true; 474 + } 475 + 476 + bool isQuarantined(Region@ region) { 477 + for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) { 478 + if(quarantined[i].sys.obj is region) 479 + return true; 480 + } 481 + return false; 482 + } 483 + 484 + bool haveQuarantinedSystem(SystemAI@ sys) { 485 + for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) { 486 + if(quarantined[i].sys is sys) 487 + return true; 488 + } 489 + return false; 490 + } 491 + 492 + void recordQuarantinedSystem(SystemAI@ sys) { 493 + ClearSystem qsys; 494 + @qsys.sys = sys; 495 + quarantined.insertLast(qsys); 496 + 497 + qsys.record(); 498 + } 499 + 500 + uint ownedCheck = 0; 501 + uint outsideCheck = 0; 502 + void focusTick(double time) { 503 + //Manage creeping check penalties 504 + for(uint i = 0, cnt = penalties.length; i < cnt; ++i) { 505 + if(penalties[i].until < gameTime) { 506 + penalties.removeAt(i); 507 + --i; --cnt; 508 + } 509 + } 510 + 511 + //Manage current creeping missions 512 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 513 + if(active[i].completed || active[i].canceled) { 514 + active.removeAt(i); 515 + --i; --cnt; 516 + } 517 + } 518 + 519 + //Find new systems that are quarantined 520 + if(systems.owned.length != 0) { 521 + ownedCheck = (ownedCheck+1) % systems.owned.length; 522 + auto@ sys = systems.owned[ownedCheck]; 523 + if(sys.explored && isQuarantined(sys)) { 524 + if(!haveQuarantinedSystem(sys)) 525 + recordQuarantinedSystem(sys); 526 + } 527 + } 528 + if(systems.outsideBorder.length != 0) { 529 + outsideCheck = (outsideCheck+1) % systems.outsideBorder.length; 530 + auto@ sys = systems.outsideBorder[outsideCheck]; 531 + if(sys.explored && isQuarantined(sys)) { 532 + if(!haveQuarantinedSystem(sys)) 533 + recordQuarantinedSystem(sys); 534 + } 535 + } 536 + 537 + //Update existing quarantined systems list 538 + for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) { 539 + auto@ qsys = quarantined[i]; 540 + if(!isQuarantined(qsys.sys)) { 541 + quarantined.removeAt(i); 542 + --i; --cnt; 543 + continue; 544 + } 545 + for(uint n = 0, ncnt = qsys.remnants.length; n < ncnt; ++n) { 546 + auto@ remn = qsys.remnants[n]; 547 + if(remn is null || !remn.valid || remn.region !is qsys.sys.obj) { 548 + qsys.remnants.removeAt(n); 549 + --n; --ncnt; 550 + } 551 + } 552 + } 553 + 554 + //See if we should try to clear a quarantined system 555 + bool waitingForGather = false; 556 + if(ai.behavior.remnantAllowArbitraryClear) { 557 + ClearSystem@ best; 558 + double bestStr = INFINITY; 559 + 560 + for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) { 561 + double str = quarantined[i].getStrength(); 562 + if(quarantined[i].remnants.length == 0) 563 + continue; 564 + if(str < bestStr) { 565 + bestStr = str; 566 + @best = quarantined[i]; 567 + } 568 + } 569 + 570 + if(best !is null) { 571 + double needStr = bestStr * ai.behavior.remnantOverkillFactor; 572 + if(fleets.getTotalStrength(FC_Combat) > needStr) { 573 + waitingForGather = true; 574 + if(fleets.getTotalStrength(FC_Combat, readyOnly=true) > needStr) { 575 + //Order sufficient fleets to go clear this system 576 + double takeStr = sqrt(needStr); 577 + double haveStr = 0.0; 578 + 579 + uint offset = randomi(0, fleets.fleets.length-1); 580 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 581 + FleetAI@ fleet = fleets.fleets[(i+offset)%cnt]; 582 + if(fleet.fleetClass != FC_Combat) 583 + continue; 584 + if(!fleet.readyForAction) 585 + continue; 586 + 587 + haveStr += sqrt(fleet.strength); 588 + sendToClear(fleet, best); 589 + 590 + if(haveStr > takeStr) 591 + break; 592 + } 593 + } 594 + } 595 + } 596 + } 597 + 598 + //Find new fleets to creep with 599 + if(!waitingForGather) { 600 + uint offset = randomi(0, fleets.fleets.length-1); 601 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 602 + FleetAI@ fleet = fleets.fleets[(i+offset)%cnt]; 603 + if(fleet.fleetClass != FC_Combat) 604 + continue; 605 + if(!fleet.readyForAction) 606 + continue; 607 + if(isPenalized(fleet.obj)) 608 + continue; 609 + 610 + creepWithFleet(fleet); 611 + break; 612 + } 613 + } 614 + } 615 +}; 616 + 617 +AIComponent@ createCreeping() { 618 + return Creeping(); 619 +}
Added scripts/server/empire_ai/weasel/Designs.as.
1 +import empire_ai.weasel.WeaselAI; 2 + 3 +import util.design_export; 4 +import util.random_designs; 5 + 6 +interface RaceDesigns { 7 + bool preCompose(DesignTarget@ target); 8 + bool postCompose(DesignTarget@ target); 9 + bool design(DesignTarget@ target, int size, const Design@& output); 10 +}; 11 + 12 +enum DesignPurpose { 13 + DP_Scout, 14 + DP_Combat, 15 + DP_Defense, 16 + DP_Support, 17 + DP_Gate, 18 + DP_Slipstream, 19 + DP_Mothership, 20 + DP_Miner, 21 + 22 + DP_COUNT, 23 + DP_Unknown, 24 +}; 25 + 26 +tidy final class DesignTarget { 27 + int id = -1; 28 + const Design@ active; 29 + string customName; 30 + 31 + array<const Design@> potential; 32 + array<double> scores; 33 + 34 + uint purpose; 35 + double targetBuildCost = 0; 36 + double targetMaintenance = 0; 37 + double targetLaborCost = 0; 38 + 39 + double dps = 0.0; 40 + double hp = 0.0; 41 + double supplyDrain = 0.0; 42 + 43 + double targetSize = 0; 44 + bool findSize = false; 45 + 46 + Designer@ designer; 47 + 48 + DesignTarget() { 49 + } 50 + 51 + DesignTarget(uint type, double targetSize) { 52 + this.purpose = type; 53 + this.targetSize = targetSize; 54 + } 55 + 56 + uint get_designType() { 57 + switch(purpose) { 58 + case DP_Scout: return DT_Flagship; 59 + case DP_Combat: return DT_Flagship; 60 + case DP_Defense: return DT_Station; 61 + case DP_Support: return DT_Support; 62 + case DP_Gate: return DT_Station; 63 + case DP_Slipstream: return DT_Flagship; 64 + case DP_Mothership: return DT_Flagship; 65 + } 66 + return DT_Flagship; 67 + } 68 + 69 + void save(Designs& designs, SaveFile& file) { 70 + if(active !is null) { 71 + file.write1(); 72 + file << active; 73 + file << dps; 74 + file << supplyDrain; 75 + file << hp; 76 + } 77 + else { 78 + file.write0(); 79 + } 80 + 81 + file << purpose; 82 + file << targetBuildCost; 83 + file << targetMaintenance; 84 + file << targetLaborCost; 85 + file << targetSize; 86 + file << findSize; 87 + file << customName; 88 + } 89 + 90 + void load(Designs& designs, SaveFile& file) { 91 + if(file.readBit()) { 92 + file >> active; 93 + file >> dps; 94 + file >> supplyDrain; 95 + file >> hp; 96 + } 97 + 98 + file >> purpose; 99 + file >> targetBuildCost; 100 + file >> targetMaintenance; 101 + file >> targetLaborCost; 102 + file >> targetSize; 103 + file >> findSize; 104 + file >> customName; 105 + } 106 + 107 + void prepare(AI& ai) { 108 + @designer = Designer(designType, targetSize, ai.empire, compose=false); 109 + designer.randomHull = true; 110 + 111 + switch(purpose) { 112 + case DP_Scout: 113 + designer.composeScout(); 114 + break; 115 + case DP_Combat: 116 + designer.composeFlagship(); 117 + break; 118 + case DP_Defense: 119 + designer.composeStation(); 120 + break; 121 + case DP_Support: 122 + designer.composeSupport(); 123 + break; 124 + case DP_Gate: 125 + designer.composeGate(); 126 + break; 127 + case DP_Slipstream: 128 + designer.composeSlipstream(); 129 + break; 130 + case DP_Mothership: 131 + designer.composeMothership(); 132 + break; 133 + } 134 + } 135 + 136 + double weight(double value, double goal) { 137 + if(value < goal) 138 + return sqr(value / goal); 139 + else if(value > goal * 1.5) 140 + return goal / value; 141 + return 1.0; 142 + } 143 + 144 + double costWeight(double value, double goal) { 145 + if(findSize) { 146 + if(value < goal) 147 + return 1.0; 148 + else 149 + return 0.000001; 150 + } 151 + else { 152 + if(value < goal) 153 + return goal / value; 154 + else 155 + return pow(0.2, ((value / goal) - 1.0) * 10.0); 156 + } 157 + } 158 + 159 + double evaluate(AI& ai, const Design& dsg) { 160 + double w = 1.0; 161 + 162 + //Try to stick as close to our target as we can 163 + if(targetBuildCost != 0) 164 + w *= costWeight(dsg.total(HV_BuildCost), targetBuildCost); 165 + if(targetLaborCost != 0) 166 + w *= costWeight(dsg.total(HV_LaborCost), targetLaborCost); 167 + if(targetMaintenance != 0) 168 + w *= costWeight(dsg.total(HV_MaintainCost), targetMaintenance); 169 + 170 + double predictHP = 0.0; 171 + double predictDPS = 0.0; 172 + double predictDrain = 0.0; 173 + 174 + //Value support capacity where appropriate 175 + if(purpose == DP_Combat) { 176 + double supCap = dsg.total(SV_SupportCapacity); 177 + double avgHP = 0, avgDPS = 0, avgDrain = 0.0; 178 + cast<Designs>(ai.designs).getSupportAverages(avgHP, avgDPS, avgDrain); 179 + 180 + predictHP += supCap * avgHP; 181 + predictDPS += supCap * avgDPS; 182 + predictDrain += supCap * avgDrain; 183 + } 184 + 185 + //Value combat strength where appropriate 186 + if(purpose != DP_Scout && purpose != DP_Slipstream && purpose != DP_Mothership) { 187 + predictDPS += dsg.total(SV_DPS); 188 + predictHP += dsg.totalHP + dsg.total(SV_ShieldCapacity); 189 + predictDrain += dsg.total(SV_SupplyDrain); 190 + 191 + if(purpose != DP_Support) { 192 + w *= (predictHP * predictDPS) * 0.001; 193 + 194 + double supplyStores = dsg.total(SV_SupplyCapacity); 195 + double actionTime = supplyStores / predictDrain; 196 + w *= weight(actionTime, ai.behavior.fleetAimSupplyDuration); 197 + } 198 + } 199 + 200 + //Value acceleration on a target 201 + if(purpose != DP_Defense && purpose != DP_Gate) { 202 + double targetAccel = 2.0; 203 + if(purpose == DP_Support) 204 + targetAccel *= 1.5; 205 + else if(purpose == DP_Scout) 206 + targetAccel *= 3.0; 207 + 208 + w *= weight(dsg.total(SV_Thrust) / max(dsg.total(HV_Mass), 0.01), targetAccel); 209 + } 210 + 211 + //Penalties for having important systems easy to shoot down 212 + uint holes = 0; 213 + for(uint i = 0, cnt = dsg.subsystemCount; i < cnt; ++i) { 214 + auto@ sys = dsg.subsystem(i); 215 + if(!sys.type.hasTag(ST_Important)) 216 + continue; 217 + //TODO: We should be able to penalize for exposed supply storage 218 + if(sys.type.hasTag(ST_NoCore)) 219 + continue; 220 + 221 + vec2u core = sys.core; 222 + for(uint d = 0; d < 6; ++d) { 223 + if(!traceContainsArmor(dsg, core, d)) 224 + holes += 1; 225 + } 226 + } 227 + 228 + if(holes != 0) 229 + w /= pow(0.9, double(holes)); 230 + 231 + //TODO: Check FTL 232 + 233 + return w; 234 + } 235 + 236 + bool traceContainsArmor(const Design@ dsg, const vec2u& startPos, uint direction) { 237 + vec2u pos = startPos; 238 + while(dsg.hull.active.valid(pos)) { 239 + if(!dsg.hull.active.advance(pos, HexGridAdjacency(direction))) 240 + break; 241 + 242 + auto@ sys = dsg.subsystem(pos.x, pos.y); 243 + if(sys is null) 244 + continue; 245 + if(sys.type.hasTag(ST_IsArmor)) 246 + return true; 247 + } 248 + return false; 249 + } 250 + 251 + bool contains(const Design& dsg) { 252 + if(active is null) 253 + return false; 254 + if(dsg.mostUpdated() is active.mostUpdated()) 255 + return true; 256 + return false; 257 + } 258 + 259 + const Design@ design(AI& ai, Designs& designs) { 260 + int trySize = targetSize; 261 + if(findSize) { 262 + trySize = randomd(0.75, 1.25) * targetSize; 263 + trySize = 5 * round(double(designer.size) / 5.0); 264 + } 265 + if(designs.race !is null) { 266 + const Design@ fromRace; 267 + if(designs.race.design(this, trySize, fromRace)) 268 + return fromRace; 269 + } 270 + if(designer !is null) { 271 + designer.size = trySize; 272 + return designer.design(1); 273 + } 274 + return null; 275 + } 276 + 277 + void choose(AI& ai, const Design@ dsg, bool randomizeName=true) { 278 + set(dsg); 279 + @designer = null; 280 + findSize = false; 281 + 282 + string baseName = dsg.name; 283 + if(customName.length != 0) { 284 + baseName = customName; 285 + } 286 + else if(randomizeName) { 287 + if(dsg.hasTag(ST_IsSupport)) 288 + baseName = autoSupportNames[randomi(0,autoSupportNames.length-1)]; 289 + else 290 + baseName = autoFlagNames[randomi(0,autoFlagNames.length-1)]; 291 + } 292 + 293 + string name = baseName; 294 + uint try = 0; 295 + while(ai.empire.getDesign(name) !is null) { 296 + name = baseName + " "; 297 + appendRoman(++try, name); 298 + } 299 + if(name != dsg.name) 300 + dsg.rename(name); 301 + 302 + //Set design settings/support behavior 303 + if(purpose == DP_Support) { 304 + if(dsg.total(SV_SupportSupplyCapacity) > 0.01) { 305 + DesignSettings settings; 306 + settings.behavior = SG_Brawler; 307 + dsg.setSettings(settings); 308 + } 309 + else if(dsg.totalHP > 50 * dsg.size) { 310 + DesignSettings settings; 311 + settings.behavior = SG_Shield; 312 + dsg.setSettings(settings); 313 + } 314 + else { 315 + DesignSettings settings; 316 + settings.behavior = SG_Cannon; 317 + dsg.setSettings(settings); 318 + } 319 + } 320 + 321 + 322 + ai.empire.addDesign(ai.empire.getDesignClass("AI", true), dsg); 323 + 324 + if(cast<Designs>(ai.designs).log) 325 + ai.print("Chose design for purpose "+uint(purpose)+" at size "+dsg.size); 326 + } 327 + 328 + void step(AI& ai, Designs& designs) { 329 + if(active is null) { 330 + if(designer is null) { 331 + if(designs.race is null || !designs.race.preCompose(this)) 332 + prepare(ai); 333 + if(designs.race !is null && designs.race.postCompose(this)) 334 + return; 335 + } 336 + if(potential.length >= ai.behavior.designEvaluateCount) { 337 + //Find the best design out of all our potentials 338 + const Design@ best; 339 + double bestScore = 0.0; 340 + 341 + for(uint i = 0, cnt = potential.length; i < cnt; ++i) { 342 + double w = scores[i]; 343 + if(w > bestScore) { 344 + bestScore = w; 345 + @best = potential[i]; 346 + } 347 + } 348 + potential.length = 0; 349 + scores.length = 0; 350 + 351 + if(best !is null) 352 + choose(ai, best); 353 + } 354 + else if(designer !is null && active is null) { 355 + //Add a new design onto the list to be evaluated 356 + const Design@ dsg = design(ai, designs); 357 + if(dsg !is null && !dsg.hasFatalErrors()) { 358 + potential.insertLast(dsg); 359 + scores.insertLast(evaluate(ai, dsg)); 360 + 361 + /*if(designs.log)*/ 362 + /* ai.print("Designed for purpose "+uint(purpose)+" at size "+dsg.size+", weight "+evaluate(ai, dsg));*/ 363 + } 364 + } 365 + } 366 + else { 367 + set(active.mostUpdated()); 368 + } 369 + } 370 + 371 + void set(const Design@ dsg) { 372 + if(active is dsg) 373 + return; 374 + 375 + @active = dsg; 376 + targetBuildCost = dsg.total(HV_BuildCost); 377 + targetMaintenance = dsg.total(HV_MaintainCost); 378 + targetLaborCost = dsg.total(HV_LaborCost); 379 + targetSize = dsg.size; 380 + 381 + dps = dsg.total(SV_DPS); 382 + hp = dsg.totalHP + dsg.total(SV_ShieldCapacity); 383 + supplyDrain = dsg.total(SV_SupplyDrain); 384 + } 385 +}; 386 + 387 +const Design@ scaleDesign(const Design@ orig, int newSize) { 388 + DesignDescriptor desc; 389 + resizeDesign(orig, newSize, desc); 390 + 391 + return makeDesign(desc); 392 +} 393 + 394 +final class Designs : AIComponent { 395 + RaceDesigns@ race; 396 + 397 + int nextTargetId = 0; 398 + array<DesignTarget@> designing; 399 + array<DesignTarget@> completed; 400 + array<DesignTarget@> automatic; 401 + 402 + void create() { 403 + @race = cast<RaceDesigns>(ai.race); 404 + } 405 + 406 + void start() { 407 + //Design some basic support sizes 408 + design(DP_Support, 1); 409 + design(DP_Support, 2); 410 + design(DP_Support, 4); 411 + design(DP_Support, 8); 412 + design(DP_Support, 16); 413 + } 414 + 415 + void save(SaveFile& file) { 416 + file << nextTargetId; 417 + 418 + uint cnt = designing.length; 419 + file << cnt; 420 + for(uint i = 0; i < cnt; ++i) { 421 + saveDesign(file, designing[i]); 422 + designing[i].save(this, file); 423 + } 424 + 425 + cnt = automatic.length; 426 + file << cnt; 427 + for(uint i = 0; i < cnt; ++i) { 428 + saveDesign(file, automatic[i]); 429 + if(!isDesigning(automatic[i])) { 430 + file.write1(); 431 + automatic[i].save(this, file); 432 + } 433 + else { 434 + file.write0(); 435 + } 436 + } 437 + 438 + cnt = completed.length; 439 + file << cnt; 440 + for(uint i = 0; i < cnt; ++i) { 441 + saveDesign(file, completed[i]); 442 + if(!isDesigning(completed[i])) { 443 + file.write1(); 444 + completed[i].save(this, file); 445 + } 446 + else { 447 + file.write0(); 448 + } 449 + } 450 + } 451 + 452 + bool isDesigning(DesignTarget@ targ) { 453 + for(uint i = 0, cnt = designing.length; i < cnt; ++i) { 454 + if(designing[i] is targ) 455 + return true; 456 + } 457 + return false; 458 + } 459 + 460 + void load(SaveFile& file) { 461 + file >> nextTargetId; 462 + 463 + uint cnt = 0; 464 + file >> cnt; 465 + for(uint i = 0; i < cnt; ++i) { 466 + auto@ targ = loadDesign(file); 467 + targ.load(this, file); 468 + designing.insertLast(targ); 469 + } 470 + 471 + file >> cnt; 472 + for(uint i = 0; i < cnt; ++i) { 473 + auto@ targ = loadDesign(file); 474 + if(file.readBit()) 475 + targ.load(this, file); 476 + automatic.insertLast(targ); 477 + } 478 + 479 + file >> cnt; 480 + for(uint i = 0; i < cnt; ++i) { 481 + auto@ targ = loadDesign(file); 482 + if(file.readBit()) 483 + targ.load(this, file); 484 + completed.insertLast(targ); 485 + } 486 + } 487 + 488 + array<DesignTarget@> loadIds; 489 + DesignTarget@ loadDesign(int id) { 490 + if(id == -1) 491 + return null; 492 + for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) { 493 + if(loadIds[i].id == id) 494 + return loadIds[i]; 495 + } 496 + DesignTarget data; 497 + data.id = id; 498 + loadIds.insertLast(data); 499 + return data; 500 + } 501 + DesignTarget@ loadDesign(SaveFile& file) { 502 + int id = -1; 503 + file >> id; 504 + if(id == -1) 505 + return null; 506 + else 507 + return loadDesign(id); 508 + } 509 + void saveDesign(SaveFile& file, DesignTarget@ data) { 510 + int id = -1; 511 + if(data !is null) 512 + id = data.id; 513 + file << id; 514 + } 515 + void postLoad(AI& ai) { 516 + loadIds.length = 0; 517 + } 518 + 519 + const Design@ get_currentSupport() { 520 + for(int i = automatic.length - 1; i >= 0; --i) { 521 + if(automatic[i].purpose == DP_Support && automatic[i].active !is null) 522 + return automatic[i].active; 523 + } 524 + return null; 525 + } 526 + 527 + void getSupportAverages(double& hp, double& dps, double& supDrain) { 528 + hp = 0; 529 + dps = 0; 530 + supDrain = 0; 531 + uint count = 0; 532 + for(uint i = 0, cnt = automatic.length; i < cnt; ++i) { 533 + auto@ targ = automatic[i]; 534 + if(targ.purpose != DP_Support) 535 + continue; 536 + if(targ.active is null) 537 + continue; 538 + 539 + hp += targ.hp / double(targ.targetSize); 540 + dps += targ.dps / double(targ.targetSize); 541 + supDrain += targ.supplyDrain / double(targ.targetSize); 542 + count += 1; 543 + } 544 + 545 + if(count == 0) { 546 + hp = 40.0; 547 + dps = 0.30; 548 + supDrain = 1.0; 549 + } 550 + else { 551 + hp /= double(count); 552 + dps /= double(count); 553 + supDrain /= double(count); 554 + } 555 + } 556 + 557 + DesignPurpose classify(Object@ obj) { 558 + if(obj is null || !obj.isShip) 559 + return DP_Combat; 560 + Ship@ ship = cast<Ship>(obj); 561 + return classify(ship.blueprint.design); 562 + } 563 + 564 + DesignPurpose classify(const Design@ dsg, DesignPurpose defaultPurpose = DP_Combat) { 565 + if(dsg is null) 566 + return defaultPurpose; 567 + 568 + for(uint i = 0, cnt = automatic.length; i < cnt; ++i) { 569 + if(automatic[i].contains(dsg)) 570 + return DesignPurpose(automatic[i].purpose); 571 + } 572 + 573 + for(uint i = 0, cnt = completed.length; i < cnt; ++i) { 574 + if(completed[i].contains(dsg)) 575 + return DesignPurpose(completed[i].purpose); 576 + } 577 + 578 + if(dsg.hasTag(ST_Mothership)) 579 + return DP_Mothership; 580 + if(dsg.hasTag(ST_Gate)) 581 + return DP_Gate; 582 + if(dsg.hasTag(ST_Slipstream)) 583 + return DP_Slipstream; 584 + if(dsg.hasTag(ST_Support)) 585 + return DP_Support; 586 + if(dsg.hasTag(ST_Station)) 587 + return DP_Defense; 588 + 589 + double dps = dsg.total(SV_DPS); 590 + if(dsg.total(SV_MiningRate) > 0) 591 + return DP_Miner; 592 + if(dsg.size == 16.0 && dsg.total(SV_DPS) < 2.0) 593 + return DP_Scout; 594 + if(dps > 0.1 * dsg.size || dsg.total(SV_SupportCapacity) > 0) 595 + return DP_Combat; 596 + return defaultPurpose; 597 + } 598 + 599 + DesignTarget@ design(uint purpose, int size, int targetCost = 0, int targetMaint = 0, double targetLabor = 0, bool findSize = false) { 600 + for(uint i = 0, cnt = automatic.length; i < cnt; ++i) { 601 + auto@ target = automatic[i]; 602 + if(target.purpose != purpose) 603 + continue; 604 + if(target.targetSize != size) 605 + continue; 606 + if(targetCost != 0 && target.targetBuildCost > targetCost) 607 + continue; 608 + if(targetMaint != 0 && target.targetMaintenance > targetMaint) 609 + continue; 610 + if(targetLabor != 0 && target.targetLaborCost > targetLabor) 611 + continue; 612 + if(target.findSize != findSize) 613 + continue; 614 + return target; 615 + } 616 + 617 + DesignTarget targ(purpose, size); 618 + targ.findSize = findSize; 619 + targ.targetBuildCost = targetCost; 620 + targ.targetMaintenance = targetMaint; 621 + targ.targetLaborCost = targetLabor; 622 + 623 + automatic.insertLast(targ); 624 + return design(targ); 625 + } 626 + 627 + DesignTarget@ design(DesignTarget@ target) { 628 + target.id = nextTargetId++; 629 + designing.insertLast(target); 630 + return target; 631 + } 632 + 633 + DesignTarget@ get(const Design@ dsg) { 634 + for(uint i = 0, cnt = automatic.length; i < cnt; ++i) { 635 + if(automatic[i].contains(dsg)) 636 + return automatic[i]; 637 + } 638 + return null; 639 + } 640 + 641 + DesignTarget@ scale(const Design@ dsg, int newSize) { 642 + if(dsg.newer !is null) { 643 + auto@ newTarg = get(dsg.newest()); 644 + if(newTarg.targetSize == newSize) 645 + return newTarg; 646 + @dsg = dsg.newest(); 647 + } 648 + 649 + DesignTarget@ previous = get(dsg); 650 + 651 + uint purpose = DP_Combat; 652 + if(previous !is null) 653 + purpose = previous.purpose; 654 + else 655 + purpose = classify(dsg); 656 + 657 + DesignTarget target(purpose, newSize); 658 + target.id = nextTargetId++; 659 + @target.active = scaleDesign(dsg, newSize); 660 + 661 + ai.empire.changeDesign(dsg, target.active, ai.empire.getDesignClass(dsg.cls.name, true)); 662 + 663 + if(previous !is null) 664 + automatic.remove(previous); 665 + automatic.insertLast(target); 666 + 667 + return target; 668 + } 669 + 670 + uint chkInd = 0; 671 + void tick(double time) { 672 + if(designing.length != 0) { 673 + //chkInd = (chkInd+1) % designing.length; 674 + // Getting 1 design first is better than getting all of them later 675 + chkInd = 0; 676 + auto@ target = designing[chkInd]; 677 + target.step(ai, this); 678 + 679 + if(target.active !is null) { 680 + designing.removeAt(chkInd); 681 + if(automatic.find(target) == -1) 682 + completed.insertLast(target); 683 + } 684 + } 685 + } 686 +}; 687 + 688 +AIComponent@ createDesigns() { 689 + return Designs(); 690 +}
Added scripts/server/empire_ai/weasel/Development.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.Planets; 3 +import empire_ai.weasel.Resources; 4 +import empire_ai.weasel.Colonization; 5 +import empire_ai.weasel.Systems; 6 + 7 +import planet_levels; 8 +import buildings; 9 + 10 +import ai.consider; 11 +from ai.buildings import Buildings, BuildingAI, RegisterForLaborUse, AsCreatedResource, BuildingUse; 12 +from ai.resources import AIResources, ResourceAI; 13 + 14 +interface RaceDevelopment { 15 + bool shouldBeFocus(Planet& pl, const ResourceType@ resource); 16 +}; 17 + 18 +class DevelopmentFocus { 19 + Object@ obj; 20 + PlanetAI@ plAI; 21 + int targetLevel = 0; 22 + int requestedLevel = 0; 23 + int maximumLevel = INT_MAX; 24 + array<ExportData@> managedPressure; 25 + double weight = 1.0; 26 + 27 + void tick(AI& ai, Development& dev, double time) { 28 + if(targetLevel != requestedLevel) { 29 + if(targetLevel > requestedLevel) { 30 + int nextLevel = min(targetLevel, min(obj.resourceLevel, requestedLevel)+1); 31 + if(nextLevel != requestedLevel) { 32 + for(int i = requestedLevel+1; i <= nextLevel; ++i) 33 + dev.resources.organizeImports(obj, i); 34 + requestedLevel = nextLevel; 35 + } 36 + } 37 + else { 38 + dev.resources.organizeImports(obj, targetLevel); 39 + requestedLevel = targetLevel; 40 + } 41 + } 42 + 43 + //Remove managed pressure resources that are no longer valid 44 + for(uint i = 0, cnt = managedPressure.length; i < cnt; ++i) { 45 + ExportData@ res = managedPressure[i]; 46 + if(res.request !is null || res.obj is null || !res.obj.valid || res.obj.owner !is ai.empire || !res.usable || res.developUse !is obj) { 47 + if(res.developUse is obj) 48 + @res.developUse = null; 49 + managedPressure.removeAt(i); 50 + --i; --cnt; 51 + } 52 + } 53 + 54 + //Make sure we're not exporting our resource 55 + if(plAI !is null && plAI.resources !is null && plAI.resources.length != 0) { 56 + auto@ res = plAI.resources[0]; 57 + res.localOnly = true; 58 + if(res.request !is null && res.request.obj !is res.obj) 59 + dev.resources.breakImport(res); 60 + } 61 + 62 + //TODO: We should be able to bump managed pressure resources back to Development for 63 + //redistribution if we run out of pressure capacity. 64 + } 65 + 66 + void save(Development& development, SaveFile& file) { 67 + file << obj; 68 + development.planets.saveAI(file, plAI); 69 + file << targetLevel; 70 + file << requestedLevel; 71 + file << maximumLevel; 72 + file << weight; 73 + 74 + uint cnt = managedPressure.length; 75 + file << cnt; 76 + for(uint i = 0; i < cnt; ++i) 77 + development.resources.saveExport(file, managedPressure[i]); 78 + } 79 + 80 + void load(Development& development, SaveFile& file) { 81 + file >> obj; 82 + @plAI = development.planets.loadAI(file); 83 + file >> targetLevel; 84 + file >> requestedLevel; 85 + file >> maximumLevel; 86 + file >> weight; 87 + 88 + uint cnt = 0; 89 + file >> cnt; 90 + for(uint i = 0; i < cnt; ++i) { 91 + auto@ data = development.resources.loadExport(file); 92 + managedPressure.insertLast(data); 93 + } 94 + } 95 +}; 96 + 97 +class Development : AIComponent, Buildings, ConsiderFilter, AIResources { 98 + RaceDevelopment@ race; 99 + Planets@ planets; 100 + Resources@ resources; 101 + Colonization@ colonization; 102 + Systems@ systems; 103 + 104 + array<DevelopmentFocus@> focuses; 105 + array<ExportData@> managedPressure; 106 + 107 + array<ColonizeData@> pendingFocuses; 108 + array<ColonizeData@> pendingResources; 109 + 110 + array<BuildingRequest@> genericBuilds; 111 + array<ExportData@> aiResources; 112 + 113 + double aimFTLStorage = 0.0; 114 + double aimResearchRate = 0.0; 115 + 116 + bool managePlanetPressure = true; 117 + bool manageAsteroidPressure = true; 118 + bool buildBuildings = true; 119 + bool colonizeResources = true; 120 + 121 + void create() { 122 + @planets = cast<Planets>(ai.planets); 123 + @resources = cast<Resources>(ai.resources); 124 + @colonization = cast<Colonization>(ai.colonization); 125 + @systems = cast<Systems>(ai.systems); 126 + @race = cast<RaceDevelopment>(ai.race); 127 + 128 + //Register specialized building types 129 + for(uint i = 0, cnt = getBuildingTypeCount(); i < cnt; ++i) { 130 + auto@ type = getBuildingType(i); 131 + for(uint n = 0, ncnt = type.ai.length; n < ncnt; ++n) { 132 + auto@ hook = cast<BuildingAI>(type.ai[n]); 133 + if(hook !is null) 134 + hook.register(this, type); 135 + } 136 + } 137 + } 138 + 139 + Empire@ get_empire() { 140 + return ai.empire; 141 + } 142 + 143 + Considerer@ get_consider() { 144 + return cast<Considerer>(ai.consider); 145 + } 146 + 147 + void registerUse(BuildingUse use, const BuildingType& type) { 148 + switch(use) { 149 + case BU_Factory: 150 + @ai.defs.Factory = type; 151 + break; 152 + case BU_LaborStorage: 153 + @ai.defs.LaborStorage = type; 154 + break; 155 + } 156 + } 157 + 158 + void save(SaveFile& file) { 159 + file << aimFTLStorage; 160 + 161 + uint cnt = focuses.length; 162 + file << cnt; 163 + for(uint i = 0; i < cnt; ++i) { 164 + auto@ focus = focuses[i]; 165 + focus.save(this, file); 166 + } 167 + 168 + cnt = managedPressure.length; 169 + file << cnt; 170 + for(uint i = 0; i < cnt; ++i) 171 + resources.saveExport(file, managedPressure[i]); 172 + 173 + cnt = pendingFocuses.length; 174 + file << cnt; 175 + for(uint i = 0; i < cnt; ++i) 176 + colonization.saveColonize(file, pendingFocuses[i]); 177 + 178 + cnt = pendingResources.length; 179 + file << cnt; 180 + for(uint i = 0; i < cnt; ++i) 181 + colonization.saveColonize(file, pendingResources[i]); 182 + 183 + cnt = genericBuilds.length; 184 + file << cnt; 185 + for(uint i = 0; i < cnt; ++i) 186 + planets.saveBuildingRequest(file, genericBuilds[i]); 187 + 188 + cnt = aiResources.length; 189 + file << cnt; 190 + for(uint i = 0; i < cnt; ++i) 191 + resources.saveExport(file, aiResources[i]); 192 + } 193 + 194 + void load(SaveFile& file) { 195 + file >> aimFTLStorage; 196 + 197 + uint cnt = 0; 198 + file >> cnt; 199 + for(uint i = 0; i < cnt; ++i) { 200 + auto@ focus = DevelopmentFocus(); 201 + focus.load(this, file); 202 + 203 + if(focus.obj !is null) 204 + focuses.insertLast(focus); 205 + } 206 + 207 + file >> cnt; 208 + for(uint i = 0; i < cnt; ++i) { 209 + auto@ data = resources.loadExport(file); 210 + if(data !is null) 211 + managedPressure.insertLast(data); 212 + } 213 + 214 + file >> cnt; 215 + for(uint i = 0; i < cnt; ++i) { 216 + auto@ data = colonization.loadColonize(file); 217 + if(data !is null) 218 + pendingFocuses.insertLast(data); 219 + } 220 + 221 + file >> cnt; 222 + for(uint i = 0; i < cnt; ++i) { 223 + auto@ data = colonization.loadColonize(file); 224 + if(data !is null) 225 + pendingResources.insertLast(data); 226 + } 227 + 228 + file >> cnt; 229 + for(uint i = 0; i < cnt; ++i) { 230 + auto@ data = planets.loadBuildingRequest(file); 231 + if(data !is null) 232 + genericBuilds.insertLast(data); 233 + } 234 + 235 + file >> cnt; 236 + for(uint i = 0; i < cnt; ++i) { 237 + auto@ data = resources.loadExport(file); 238 + if(data !is null) 239 + aiResources.insertLast(data); 240 + } 241 + } 242 + 243 + bool requestsFTLStorage() { 244 + double capacity = ai.empire.FTLCapacity; 245 + if(aimFTLStorage <= capacity) 246 + return false; 247 + if(ai.empire.FTLStored < capacity * 0.5) 248 + return false; 249 + return true; 250 + } 251 + 252 + bool requestsResearchGeneration() { 253 + double rate = ai.empire.ResearchRate; 254 + if (aimResearchRate <= rate) 255 + return false; 256 + return true; 257 + } 258 + 259 + bool isBuilding(const BuildingType& type) { 260 + for(uint i = 0, cnt = genericBuilds.length; i < cnt; ++i) { 261 + if(genericBuilds[i].type is type) 262 + return true; 263 + } 264 + return false; 265 + } 266 + 267 + bool isLeveling() { 268 + for(uint i = 0, cnt = focuses.length; i < cnt; ++i) { 269 + if(focuses[i].obj.resourceLevel < uint(focuses[i].targetLevel)) { 270 + auto@ focus = focuses[i].obj; 271 + 272 + //If all our requirements are resolved, then we can safely assume it will be leveled up 273 + bool allResolved = true; 274 + for(uint n = 0, ncnt = resources.requested.length; n < ncnt; ++n) { 275 + auto@ req = resources.requested[n]; 276 + if(req.obj !is focus) 277 + continue; 278 + if(req.beingMet) 279 + continue; 280 + 281 + if(!req.isColonizing) { 282 + allResolved = false; 283 + break; 284 + } 285 + 286 + if(!colonization.isResolved(req)) { 287 + allResolved = false; 288 + break; 289 + } 290 + } 291 + 292 + if(!allResolved) 293 + return true; 294 + } 295 + } 296 + return false; 297 + } 298 + 299 + bool isBusy() { 300 + if(pendingFocuses.length != 0) 301 + return true; 302 + if(pendingResources.length != 0) 303 + return true; 304 + if(isLeveling()) 305 + return true; 306 + return false; 307 + } 308 + 309 + bool isFocus(Object@ obj) { 310 + for(uint i = 0, cnt = focuses.length; i < cnt; ++i) { 311 + if(focuses[i].obj is obj) 312 + return true; 313 + } 314 + return false; 315 + } 316 + 317 + bool isManaging(ExportData@ res) { 318 + for(uint i = 0, cnt = managedPressure.length; i < cnt; ++i) { 319 + if(managedPressure[i] is res) 320 + return true; 321 + } 322 + for(uint i = 0, cnt = aiResources.length; i < cnt; ++i) { 323 + if(aiResources[i] is res) 324 + return true; 325 + } 326 + for(uint n = 0, ncnt = focuses.length; n < ncnt; ++n) { 327 + auto@ f = focuses[n]; 328 + if(f.obj is res.obj) 329 + return true; 330 + for(uint i = 0, cnt = f.managedPressure.length; i < cnt; ++i) { 331 + if(f.managedPressure[i] is res) 332 + return true; 333 + } 334 + } 335 + return false; 336 + } 337 + 338 + bool isDevelopingIn(Region@ reg) { 339 + if(reg is null) 340 + return false; 341 + for(uint i = 0, cnt = focuses.length; i < cnt; ++i) { 342 + if(focuses[i].obj.region is reg) 343 + return true; 344 + } 345 + return false; 346 + } 347 + 348 + void start() { 349 + //Level up the homeworld to level 3 to start with 350 + for(uint i = 0, cnt = ai.empire.planetCount; i < cnt; ++i) { 351 + Planet@ homeworld = ai.empire.planetList[i]; 352 + if(homeworld !is null && homeworld.valid) { 353 + auto@ hwFocus = addFocus(planets.register(homeworld)); 354 + if(homeworld.nativeResourceCount >= 2 || homeworld.primaryResourceLimitLevel >= 3 || cnt == 1) 355 + hwFocus.targetLevel = 3; 356 + } 357 + } 358 + } 359 + 360 + double idlePenalty = 0; 361 + void findSomethingToDo() { 362 + if(idlePenalty > gameTime) 363 + return; 364 + 365 + double totalChance = 366 + ai.behavior.focusDevelopWeight 367 + + ai.behavior.focusColonizeNewWeight * sqr(1.0 / double(focuses.length)) 368 + + ai.behavior.focusColonizeHighTierWeight; 369 + double roll = randomd(0.0, totalChance); 370 + 371 + //Level up one of our existing focuses 372 + roll -= ai.behavior.focusDevelopWeight; 373 + if(roll <= 0) { 374 + DevelopmentFocus@ levelup; 375 + double totalWeight = 0.0; 376 + for(uint i = 0, cnt = focuses.length; i < cnt; ++i) { 377 + auto@ f = focuses[i]; 378 + if(f.weight == 0) 379 + continue; 380 + if(f.targetLevel >= f.maximumLevel) 381 + continue; 382 + totalWeight += f.weight; 383 + if(randomd() < f.weight / totalWeight) 384 + @levelup = f; 385 + } 386 + 387 + if(levelup !is null) { 388 + levelup.targetLevel += 1; 389 + if(log) 390 + ai.print("Develop chose to level this up to "+levelup.targetLevel, levelup.obj); 391 + return; 392 + } 393 + else { 394 + if(log) 395 + ai.print("Develop ran out of things to level up."); 396 + } 397 + } 398 + 399 + if(!colonizeResources) 400 + return; 401 + 402 + //Find a scalable or high tier resource to colonize and turn into a focus 403 + roll -= ai.behavior.focusColonizeNewWeight * sqr(1.0 / double(focuses.length)); 404 + if(roll <= 0) { 405 + Planet@ newFocus; 406 + double w; 407 + double bestWeight = 0.0; 408 + 409 + for(uint i = 0, cnt = colonization.potentials.length; i < cnt; ++i) { 410 + auto@ p = colonization.potentials[i]; 411 + 412 + if(p.resource.level < 3 && p.resource.cls !is colonization.scalableClass) 413 + continue; 414 + 415 + Region@ reg = p.pl.region; 416 + if(reg is null) 417 + continue; 418 + 419 + if(colonization.isColonizing(p.pl)) 420 + continue; 421 + 422 + vec2i surfaceSize = p.pl.surfaceGridSize; 423 + int tiles = surfaceSize.width * surfaceSize.height; 424 + if(tiles < 144) 425 + continue; 426 + 427 + auto@ sys = systems.getAI(reg); 428 + w = 1.0; 429 + if(sys.border) 430 + w *= 0.25; 431 + if (!sys.owned && !sys.border) 432 + w /= 0.25; 433 + if(sys.obj.PlanetsMask & ~ai.mask != 0) 434 + w *= 0.25; 435 + if(p.resource.cls is colonization.scalableClass) 436 + w *= 10.0; 437 + 438 + if (w > bestWeight) { 439 + @newFocus = p.pl; 440 + bestWeight = w; 441 + } 442 + } 443 + 444 + if(newFocus !is null) { 445 + auto@ data = colonization.colonize(newFocus); 446 + if(data !is null) 447 + pendingFocuses.insertLast(data); 448 + if(log) 449 + ai.print("Colonize to become develop focus", data.target); 450 + return; 451 + } 452 + else { 453 + if(log) 454 + ai.print("Develop could not find a scalable or high tier resource to make a focus."); 455 + } 456 + } 457 + 458 + if(focuses.length == 0) 459 + return; 460 + 461 + //Find a high tier resource to import to one of our focuses 462 + roll -= ai.behavior.focusColonizeHighTierWeight; 463 + if(roll <= 0) { 464 + ResourceSpec spec; 465 + spec.type = RST_Level_Minimum; 466 + spec.level = 3; 467 + spec.isLevelRequirement = false; 468 + 469 + auto@ data = colonization.colonize(spec); 470 + if(data !is null) { 471 + if(log) 472 + ai.print("Colonize as free resource", data.target); 473 + pendingResources.insertLast(data); 474 + return; 475 + } 476 + else { 477 + if(log) 478 + ai.print("Develop could not find a high tier resource to colonize as free resource."); 479 + } 480 + } 481 + 482 + //Try to find a level 2 resource if everything else failed 483 + { 484 + ResourceSpec spec; 485 + spec.type = RST_Level_Minimum; 486 + spec.level = 2; 487 + spec.isLevelRequirement = false; 488 + 489 + if(colonization.shouldQueueFor(spec)) { 490 + auto@ data = colonization.colonize(spec); 491 + if(data !is null) { 492 + if(log) 493 + ai.print("Colonize as free resource", data.target); 494 + pendingResources.insertLast(data); 495 + return; 496 + } 497 + else { 498 + if(log) 499 + ai.print("Develop could not find a level 2 resource to colonize as free resource."); 500 + } 501 + } 502 + } 503 + 504 + idlePenalty = gameTime + randomd(10.0, 40.0); 505 + } 506 + 507 + uint bldIndex = 0; 508 + uint aiInd = 0; 509 + uint presInd = 0; 510 + uint chkInd = 0; 511 + void focusTick(double time) override { 512 + //Remove any resources we're managing that got used 513 + for(uint i = 0, cnt = managedPressure.length; i < cnt; ++i) { 514 + ExportData@ res = managedPressure[i]; 515 + if(res.request !is null || res.obj is null || !res.obj.valid || res.obj.owner !is ai.empire || !res.usable) { 516 + managedPressure.removeAt(i); 517 + --i; --cnt; 518 + } 519 + } 520 + 521 + //Find new resources that we can put in our pressure manager 522 + uint avCnt = resources.available.length; 523 + if(avCnt != 0) { 524 + uint index = randomi(0, avCnt-1); 525 + for(uint i = 0, cnt = min(avCnt, 3); i < cnt; ++i) { 526 + uint resInd = (index+i) % avCnt; 527 + ExportData@ res = resources.available[resInd]; 528 + if(res.usable && res.request is null && res.obj !is null && res.obj.valid && res.obj.owner is ai.empire && res.developUse is null) { 529 + if(res.resource.ai.length != 0) { 530 + if(!isManaging(res)) 531 + aiResources.insertLast(res); 532 + } 533 + else if(res.resource.totalPressure > 0 && res.resource.exportable) { 534 + if(!managePlanetPressure && res.obj.isPlanet) 535 + continue; 536 + if(!manageAsteroidPressure && res.obj.isAsteroid) 537 + continue; 538 + if(!isManaging(res)) 539 + managedPressure.insertLast(res); 540 + } 541 + } 542 + } 543 + } 544 + 545 + //Distribute managed pressure resources 546 + if(managedPressure.length != 0) { 547 + presInd = (presInd+1) % managedPressure.length; 548 + ExportData@ res = managedPressure[presInd]; 549 + 550 + int pressure = res.resource.totalPressure; 551 + 552 + DevelopmentFocus@ onFocus; 553 + double bestWeight = 0; 554 + bool havePressure = ai.empire.HasPressure != 0.0; 555 + 556 + for(uint i = 0, cnt = focuses.length; i < cnt; ++i) { 557 + auto@ f = focuses[i]; 558 + 559 + int cap = f.obj.pressureCap; 560 + if(!havePressure) 561 + cap = 10000; 562 + int cur = f.obj.totalPressure; 563 + 564 + if(cur + pressure > 2 * cap) 565 + continue; 566 + 567 + double w = 1.0; 568 + if(cur + pressure > cap) 569 + w *= 0.1; 570 + 571 + if(w > bestWeight) { 572 + bestWeight = w; 573 + @onFocus = f; 574 + } 575 + } 576 + 577 + if(onFocus !is null) { 578 + if(res.obj !is onFocus.obj) 579 + res.obj.exportResourceByID(res.resourceId, onFocus.obj); 580 + else 581 + res.obj.exportResourceByID(res.resourceId, null); 582 + @res.developUse = onFocus.obj; 583 + 584 + onFocus.managedPressure.insertLast(res); 585 + managedPressure.removeAt(presInd); 586 + 587 + if(log) 588 + ai.print("Take "+res.resource.name+" from "+res.obj.name+" for pressure", onFocus.obj); 589 + } 590 + } 591 + 592 + //Use generic AI distribution hooks 593 + if(aiResources.length != 0) { 594 + aiInd = (aiInd+1) % aiResources.length; 595 + ExportData@ res = aiResources[aiInd]; 596 + if(res.request !is null || res.obj is null || !res.obj.valid || res.obj.owner !is ai.empire || !res.usable) { 597 + aiResources.removeAt(aiInd); 598 + } 599 + else { 600 + Object@ newTarget = res.developUse; 601 + if(newTarget !is null) { 602 + if(!newTarget.valid || newTarget.owner !is ai.empire) 603 + @newTarget = null; 604 + } 605 + 606 + for(uint i = 0, cnt = res.resource.ai.length; i < cnt; ++i) { 607 + auto@ hook = cast<ResourceAI>(res.resource.ai[i]); 608 + if(hook !is null) 609 + @newTarget = hook.distribute(this, res.resource, newTarget); 610 + } 611 + 612 + if(newTarget !is res.developUse) { 613 + if(res.obj !is newTarget) 614 + res.obj.exportResourceByID(res.resourceId, newTarget); 615 + else 616 + res.obj.exportResourceByID(res.resourceId, null); 617 + @res.developUse = newTarget; 618 + } 619 + } 620 + } 621 + 622 + //Deal with focuses we're colonizing 623 + for(uint i = 0, cnt = pendingFocuses.length; i < cnt; ++i) { 624 + auto@ data = pendingFocuses[i]; 625 + if(data.completed) { 626 + auto@ focus = addFocus(planets.register(data.target)); 627 + focus.targetLevel = 3; 628 + 629 + pendingFocuses.removeAt(i); 630 + --i; --cnt; 631 + } 632 + else if(data.canceled) { 633 + pendingFocuses.removeAt(i); 634 + --i; --cnt; 635 + } 636 + } 637 + 638 + for(uint i = 0, cnt = pendingResources.length; i < cnt; ++i) { 639 + auto@ data = pendingResources[i]; 640 + if(data.completed) { 641 + planets.requestLevel(planets.register(data.target), data.target.primaryResourceLevel); 642 + pendingResources.removeAt(i); 643 + --i; --cnt; 644 + } 645 + else if(data.canceled) { 646 + pendingResources.removeAt(i); 647 + --i; --cnt; 648 + } 649 + } 650 + 651 + //If we're not currently leveling something up, find something else to do 652 + if(!isBusy()) 653 + findSomethingToDo(); 654 + 655 + //Deal with building AI hints 656 + for(uint i = 0, cnt = genericBuilds.length; i < cnt; ++i) { 657 + auto@ build = genericBuilds[i]; 658 + if(build.canceled) { 659 + genericBuilds.removeAt(i); 660 + --i; --cnt; 661 + } 662 + else if(build.built) { 663 + if(build.getProgress() >= 1.f) { 664 + if(build.expires < gameTime) { 665 + genericBuilds.removeAt(i); 666 + --i; --cnt; 667 + } 668 + } 669 + else 670 + build.expires = gameTime + 60.0; 671 + } 672 + } 673 + if(buildBuildings) { 674 + for(uint i = 0, cnt = getBuildingTypeCount(); i < cnt; ++i) { 675 + bldIndex = (bldIndex+1) % cnt; 676 + 677 + auto@ type = getBuildingType(bldIndex); 678 + if(type.ai.length == 0) 679 + continue; 680 + 681 + //If we're already generically building something of this type, wait 682 + bool existing = false; 683 + for(uint n = 0, ncnt = genericBuilds.length; n < ncnt; ++n) { 684 + auto@ build = genericBuilds[n]; 685 + if(build.type is type && !build.built) { 686 + existing = true; 687 + break; 688 + } 689 + } 690 + 691 + if(existing) 692 + break; 693 + 694 + @filterType = type; 695 + @consider.filter = this; 696 + 697 + //See if we should generically build something of this type 698 + for(uint n = 0, ncnt = type.ai.length; n < ncnt; ++n) { 699 + auto@ hook = cast<BuildingAI>(type.ai[n]); 700 + if(hook !is null) { 701 + Object@ buildOn = hook.considerBuild(this, type); 702 + if(buildOn !is null && buildOn.isPlanet) { 703 + auto@ plAI = planets.getAI(cast<Planet>(buildOn)); 704 + if(plAI !is null) { 705 + if(log) 706 + ai.print("AI hook generically requested building of type "+type.name, buildOn); 707 + 708 + double priority = 1.0; 709 + //Resource buildings should be built as soon as possible 710 + if (cast<AsCreatedResource>(hook) !is null) 711 + priority = 2.0; 712 + 713 + auto@ req = planets.requestBuilding(plAI, type, priority, expire=ai.behavior.genericBuildExpire); 714 + if(req !is null) 715 + genericBuilds.insertLast(req); 716 + break; 717 + } 718 + } 719 + } 720 + } 721 + break; 722 + } 723 + } 724 + 725 + //Find planets we've acquired 'somehow' that have scalable resources and should be development focuses 726 + if(planets.planets.length != 0) { 727 + chkInd = (chkInd+1) % planets.planets.length; 728 + auto@ plAI = planets.planets[chkInd]; 729 + 730 + if(plAI.resources.length != 0) { 731 + auto@ res = plAI.resources[0]; 732 + if(res.resource.cls is colonization.scalableClass 733 + || focuses.length == 0 && res.resource.level >= 2 734 + || (race !is null && race.shouldBeFocus(plAI.obj, res.resource))) { 735 + if(!isFocus(plAI.obj)) { 736 + auto@ focus = addFocus(plAI); 737 + focus.targetLevel = max(1, res.resource.level); 738 + } 739 + } 740 + } 741 + } 742 + } 743 + 744 + DevelopmentFocus@ addFocus(PlanetAI@ plAI) { 745 + DevelopmentFocus focus; 746 + @focus.obj = plAI.obj; 747 + @focus.plAI = plAI; 748 + focus.maximumLevel = getMaxPlanetLevel(plAI.obj); 749 + 750 + focuses.insertLast(focus); 751 + return focus; 752 + } 753 + 754 + DevelopmentFocus@ getFocus(Planet& pl) { 755 + for(uint i = 0, cnt = focuses.length; i < cnt; ++i) { 756 + if(focuses[i].obj is pl) 757 + return focuses[i]; 758 + } 759 + return null; 760 + } 761 + 762 + void tick(double time) override { 763 + for(uint i = 0, cnt = focuses.length; i < cnt; ++i) 764 + focuses[i].tick(ai, this, time); 765 + } 766 + 767 + const BuildingType@ filterType; 768 + bool filter(Object@ obj) { 769 + for(uint i = 0, cnt = genericBuilds.length; i < cnt; ++i) { 770 + auto@ build = genericBuilds[i]; 771 + if(build.type is filterType && build.plAI.obj is obj) 772 + return false; 773 + } 774 + return true; 775 + } 776 + 777 + Planet@ getLaborAt(Territory@ territory, double&out expires) { 778 + if (territory is null) { 779 + if (log) 780 + ai.print("invalid territory to get labor at"); 781 + return null; 782 + } 783 + expires = 600.0; 784 + const BuildingType@ type = ai.defs.Factory; 785 + BuildingRequest@ request = null; 786 + Planet@ pl = null; 787 + for (uint i = 0, cnt = type.ai.length; i < cnt; ++i) { 788 + auto@ hook = cast<RegisterForLaborUse>(type.ai[i]); 789 + if (hook !is null) { 790 + Object@ obj = hook.considerBuild(this, type, territory); 791 + if (obj !is null) { 792 + @pl = cast<Planet>(obj); 793 + if (pl !is null) { 794 + planets.requestBuilding(planets.getAI(pl), type, 2.0, expires); 795 + if (log) 796 + ai.print("requesting building " + type.name + " at " + pl.name + " to get labor at " + addrstr(territory)); 797 + break; 798 + } 799 + } 800 + } 801 + } 802 + return pl; 803 + } 804 +}; 805 + 806 +AIComponent@ createDevelopment() { 807 + return Development(); 808 +}
Added scripts/server/empire_ai/weasel/Diplomacy.as.
1 +// Diplomacy 2 +// --------- 3 +// Acts as an adaptor for using the generically developed DiplomacyAI. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 +import empire_ai.weasel.Systems; 8 +import empire_ai.weasel.Planets; 9 +import empire_ai.weasel.Development; 10 +import empire_ai.weasel.Construction; 11 +import empire_ai.weasel.Fleets; 12 +import empire_ai.weasel.Resources; 13 +import empire_ai.weasel.War; 14 +import empire_ai.weasel.Intelligence; 15 + 16 +import influence; 17 +from empire_ai.DiplomacyAI import DiplomacyAI, VoteData, CardAI, VoteState; 18 + 19 +import systems; 20 + 21 +class Diplomacy : DiplomacyAI, IAIComponent { 22 + Systems@ systems; 23 + Fleets@ fleets; 24 + Planets@ planets; 25 + Construction@ construction; 26 + Development@ development; 27 + Resources@ resources; 28 + War@ war; 29 + Intelligence@ intelligence; 30 + 31 + //Adapt to AI component 32 + AI@ ai; 33 + double prevFocus = 0; 34 + bool logCritical = false; 35 + bool logErrors = true; 36 + 37 + double getPrevFocus() { return prevFocus; } 38 + void setPrevFocus(double value) { prevFocus = value; } 39 + 40 + void setLog() { log = true; } 41 + void setLogCritical() { logCritical = true; } 42 + 43 + void set(AI& ai) { @this.ai = ai; } 44 + void start() {} 45 + 46 + void tick(double time) {} 47 + void turn() {} 48 + 49 + void postLoad(AI& ai) {} 50 + void postSave(AI& ai) {} 51 + void loadFinalize(AI& ai) {} 52 + 53 + //Actual AI component implementations 54 + void create() { 55 + @systems = cast<Systems>(ai.systems); 56 + @fleets = cast<Fleets>(ai.fleets); 57 + @planets = cast<Planets>(ai.planets); 58 + @development = cast<Development>(ai.development); 59 + @construction = cast<Construction>(ai.construction); 60 + @resources = cast<Resources>(ai.resources); 61 + @war = cast<War>(ai.war); 62 + @intelligence = cast<Intelligence>(ai.intelligence); 63 + } 64 + 65 + //IMPLEMENTED BY DiplomacyAI 66 + /*void save(SaveFile& file) {}*/ 67 + /*void load(SaveFile& file) {}*/ 68 + 69 + uint nextStep = 0; 70 + void focusTick(double time) { 71 + summarize(); 72 + 73 + if (ai.behavior.forbidDiplomacy) return; 74 + switch(nextStep++ % 3) { 75 + case 0: 76 + buyCards(); 77 + break; 78 + case 1: 79 + considerActions(); 80 + break; 81 + case 2: 82 + considerVotes(); 83 + break; 84 + } 85 + } 86 + 87 + //Adapt to diplomacy AI 88 + Empire@ get_empire() { 89 + return ai.empire; 90 + } 91 + 92 + uint get_allyMask() { 93 + return ai.allyMask; 94 + } 95 + 96 + int getStanding(Empire@ emp) { 97 + //TODO: Use relations module for this generically 98 + if(emp.isHostile(ai.empire)) 99 + return -50; 100 + if(ai.allyMask & emp.mask != 0) 101 + return 100; 102 + return 0; 103 + } 104 + 105 + void print(const string& str) { 106 + ai.print(str); 107 + } 108 + 109 + Object@ considerImportantPlanets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 110 + double bestWeight = 0.0; 111 + Object@ best; 112 + 113 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 114 + Object@ obj = development.focuses[i].obj; 115 + double w = hook.consider(this, targets, vote, card, obj); 116 + if(w > bestWeight) { 117 + @best = obj; 118 + bestWeight = w; 119 + } 120 + } 121 + 122 + return best; 123 + } 124 + 125 + Object@ considerOwnedPlanets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 126 + //Consider our important ones first 127 + double bestWeight = 0.0; 128 + Object@ best; 129 + 130 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 131 + Object@ obj = development.focuses[i].obj; 132 + double w = hook.consider(this, targets, vote, card, obj); 133 + if(w > bestWeight) { 134 + @best = obj; 135 + bestWeight = w; 136 + } 137 + } 138 + 139 + //Consider some random other ones 140 + uint planetCount = planets.planets.length; 141 + if(planetCount != 0) { 142 + uint offset = randomi(0, planetCount-1); 143 + for(uint n = 0; n < 5; ++n) { 144 + Object@ obj = planets.planets[(offset+n) % planetCount].obj; 145 + double w = hook.consider(this, targets, vote, card, obj); 146 + if(w > bestWeight) { 147 + @best = obj; 148 + bestWeight = w; 149 + } 150 + } 151 + } 152 + 153 + return best; 154 + } 155 + 156 + Object@ considerImportantSystems(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 157 + double bestWeight = 0.0; 158 + Object@ best; 159 + 160 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 161 + Object@ obj = development.focuses[i].obj.region; 162 + if(obj is null) 163 + continue; 164 + double w = hook.consider(this, targets, vote, card, obj); 165 + if(w > bestWeight) { 166 + @best = obj; 167 + bestWeight = w; 168 + } 169 + } 170 + 171 + return best; 172 + } 173 + 174 + Object@ considerOwnedSystems(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 175 + //Consider our important ones first 176 + double bestWeight = 0.0; 177 + Object@ best; 178 + 179 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 180 + Object@ obj = development.focuses[i].obj.region; 181 + if(obj is null) 182 + continue; 183 + double w = hook.consider(this, targets, vote, card, obj); 184 + if(w > bestWeight) { 185 + @best = obj; 186 + bestWeight = w; 187 + } 188 + } 189 + 190 + //Consider some random other ones 191 + uint sysCount = systems.owned.length; 192 + if(sysCount != 0) { 193 + uint offset = randomi(0, sysCount-1); 194 + for(uint n = 0; n < 5; ++n) { 195 + Object@ obj = systems.owned[(offset+n) % sysCount].obj; 196 + double w = hook.consider(this, targets, vote, card, obj); 197 + if(w > bestWeight) { 198 + @best = obj; 199 + bestWeight = w; 200 + } 201 + } 202 + } 203 + 204 + return best; 205 + } 206 + 207 + Object@ considerDefendingSystems(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 208 + double bestWeight = 0.0; 209 + Object@ best; 210 + 211 + for(uint i = 0, cnt = war.battles.length; i < cnt; ++i) { 212 + auto@ battle = war.battles[i]; 213 + Region@ sys = battle.system.obj; 214 + if(sys.SiegedMask & empire.mask == 0) 215 + continue; 216 + 217 + double w = hook.consider(this, targets, vote, card, sys); 218 + if(w > bestWeight) { 219 + @best = sys; 220 + bestWeight = w; 221 + } 222 + } 223 + 224 + return best; 225 + } 226 + 227 + Object@ considerDefendingPlanets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 228 + double bestWeight = 0.0; 229 + Object@ best; 230 + 231 + for(uint i = 0, cnt = war.battles.length; i < cnt; ++i) { 232 + auto@ battle = war.battles[i]; 233 + Region@ sys = battle.system.obj; 234 + if(sys.SiegedMask & empire.mask == 0) 235 + continue; 236 + 237 + for(uint n = 0, ncnt = battle.system.planets.length; n < ncnt; ++n) { 238 + Object@ pl = battle.system.planets[n]; 239 + if(pl.owner !is empire) 240 + continue; 241 + 242 + double w = hook.consider(this, targets, vote, card, pl); 243 + if(w > bestWeight) { 244 + @best = pl; 245 + bestWeight = w; 246 + } 247 + } 248 + } 249 + 250 + return best; 251 + } 252 + 253 + Object@ considerEnemySystems(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 254 + double bestWeight = 0.0; 255 + Object@ best; 256 + 257 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 258 + Empire@ emp = getEmpire(i); 259 + if(!emp.major) 260 + continue; 261 + if(!empire.isHostile(emp)) 262 + continue; 263 + 264 + auto@ intel = intelligence.get(emp); 265 + if(intel is null) 266 + continue; 267 + 268 + for(uint n = 0, ncnt = intel.theirBorder.length; n < ncnt; ++n) { 269 + auto@ sysIntel = intel.theirBorder[n]; 270 + 271 + double w = hook.consider(this, targets, vote, card, sysIntel.obj); 272 + if(w > bestWeight) { 273 + @best = sysIntel.obj; 274 + bestWeight = w; 275 + } 276 + } 277 + } 278 + 279 + return best; 280 + } 281 + 282 + Object@ considerEnemyPlanets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 283 + double bestWeight = 0.0; 284 + Object@ best; 285 + 286 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 287 + Empire@ emp = getEmpire(i); 288 + if(!emp.major) 289 + continue; 290 + if(!empire.isHostile(emp)) 291 + continue; 292 + 293 + auto@ intel = intelligence.get(emp); 294 + if(intel is null) 295 + continue; 296 + 297 + for(uint n = 0, ncnt = intel.theirBorder.length; n < ncnt; ++n) { 298 + auto@ sysIntel = intel.theirBorder[n]; 299 + 300 + for(uint j = 0, jcnt = sysIntel.planets.length; j < jcnt; ++j) { 301 + Planet@ pl = sysIntel.planets[j]; 302 + if(pl.visibleOwnerToEmp(empire) !is emp) 303 + continue; 304 + 305 + double w = hook.consider(this, targets, vote, card, pl); 306 + if(w > bestWeight) { 307 + @best = pl; 308 + bestWeight = w; 309 + } 310 + } 311 + } 312 + } 313 + 314 + return best; 315 + } 316 + 317 + Object@ considerFleets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 318 + Object@ best; 319 + double bestWeight = 0.0; 320 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 321 + Object@ fleet = fleets.fleets[i].obj; 322 + if(fleet !is null) { 323 + double w = hook.consider(this, targets, vote, card, fleet); 324 + if(w > bestWeight) { 325 + @best = fleet; 326 + bestWeight = w; 327 + } 328 + } 329 + } 330 + return best; 331 + } 332 + 333 + Object@ considerEnemyFleets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) { 334 + double bestWeight = 0.0; 335 + Object@ best; 336 + 337 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 338 + Empire@ emp = getEmpire(i); 339 + if(!emp.major) 340 + continue; 341 + if(!empire.isHostile(emp)) 342 + continue; 343 + 344 + auto@ intel = intelligence.get(emp); 345 + if(intel is null) 346 + continue; 347 + 348 + for(uint n = 0, ncnt = intel.fleets.length; n < ncnt; ++n) { 349 + auto@ flIntel = intel.fleets[n]; 350 + if(!flIntel.known) 351 + continue; 352 + 353 + double w = hook.consider(this, targets, vote, card, flIntel.obj); 354 + if(w > bestWeight) { 355 + @best = flIntel.obj; 356 + bestWeight = w; 357 + } 358 + } 359 + } 360 + 361 + return best; 362 + } 363 + 364 + Object@ considerMatchingImportRequests(const CardAI& hook, Targets& targets, VoteState@ vote, const InfluenceCard@ card, const ResourceType@ type, bool considerExisting) { 365 + Object@ best; 366 + double bestWeight = 0.0; 367 + for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) { 368 + ImportData@ req = resources.requested[i]; 369 + if(req.spec.meets(type)) { 370 + double w = hook.consider(this, targets, vote, card, req.obj, null); 371 + if(w > bestWeight) { 372 + bestWeight = w; 373 + @best = req.obj; 374 + } 375 + } 376 + } 377 + if(considerExisting) { 378 + for(uint i = 0, cnt = resources.used.length; i < cnt; ++i) { 379 + ExportData@ res = resources.used[i]; 380 + ImportData@ req = res.request; 381 + if(req !is null && req.spec.meets(type)) { 382 + double w = hook.consider(this, targets, vote, card, req.obj, res.obj); 383 + if(w > bestWeight) { 384 + bestWeight = w; 385 + @best = req.obj; 386 + } 387 + } 388 + } 389 + } 390 + return best; 391 + } 392 +}; 393 + 394 +IAIComponent@ createDiplomacy() { 395 + return Diplomacy(); 396 +}
Added scripts/server/empire_ai/weasel/Energy.as.
1 +// Energy 2 +// ------ 3 +// Manage the use of energy on artifacts and other things. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 +import empire_ai.weasel.Systems; 8 + 9 +import ai.consider; 10 + 11 +import artifacts; 12 +import abilities; 13 +import systems; 14 + 15 +from ai.artifacts import Artifacts, ArtifactConsider, ArtifactAI; 16 + 17 +double effCostEstimate(double cost, double freeStorage) { 18 + double free = min(cost, freeStorage); 19 + cost -= free; 20 + 21 + double effStep = config::ENERGY_EFFICIENCY_STEP; 22 + double eff = 0.0; 23 + double step = 1.0; 24 + while(cost > 0) { 25 + eff += (cost / effStep) * step; 26 + cost -= effStep; 27 + step *= 2.0; 28 + } 29 + return eff * effStep + free; 30 +} 31 + 32 +class ConsiderEnergy : ArtifactConsider { 33 + int id = -1; 34 + const ArtifactType@ type; 35 + Artifact@ artifact; 36 + Ability@ ability; 37 + Object@ target; 38 + vec3d pointTarget; 39 + double cost = 0.0; 40 + double value = 0.0; 41 + 42 + void save(AI& ai, SaveFile& file) { 43 + file << id; 44 + file << artifact; 45 + file << target; 46 + file << cost; 47 + file << value; 48 + file << pointTarget; 49 + } 50 + 51 + void load(AI& ai, SaveFile& file) { 52 + file >> id; 53 + file >> artifact; 54 + file >> target; 55 + file >> cost; 56 + file >> value; 57 + file >> pointTarget; 58 + 59 + if(artifact !is null) 60 + init(ai, artifact); 61 + } 62 + 63 + void setTarget(Object@ obj) { 64 + @target = obj; 65 + } 66 + 67 + Object@ getTarget() { 68 + return target; 69 + } 70 + 71 + bool canTarget(Object@ obj) { 72 + if(ability.targets.length != 0) { 73 + auto@ targ = ability.targets[0]; 74 + @targ.obj = obj; 75 + targ.filled = true; 76 + return ability.isValidTarget(0, targ); 77 + } 78 + else 79 + return false; 80 + } 81 + 82 + void setTargetPosition(const vec3d& point) { 83 + pointTarget = point; 84 + } 85 + 86 + vec3d getTargetPosition() { 87 + return pointTarget; 88 + } 89 + 90 + bool canTargetPosition(const vec3d& point) { 91 + if(ability.targets.length != 0) { 92 + auto@ targ = ability.targets[0]; 93 + targ.point = point; 94 + targ.filled = true; 95 + return ability.isValidTarget(0, targ); 96 + } 97 + else 98 + return false; 99 + } 100 + 101 + void init(AI& ai, Artifact@ artifact) { 102 + @this.artifact = artifact; 103 + @type = getArtifactType(artifact.ArtifactType); 104 + 105 + if(ability is null) 106 + @ability = Ability(); 107 + if(type.secondaryChance > 0 && type.abilities.length >= 2 108 + && randomd() < type.secondaryChance) { 109 + ability.id = 1; 110 + @ability.type = type.abilities[1]; 111 + } 112 + else { 113 + ability.id = 0; 114 + @ability.type = type.abilities[0]; 115 + } 116 + ability.targets = Targets(ability.type.targets); 117 + @ability.obj = artifact; 118 + @ability.emp = ai.empire; 119 + } 120 + 121 + bool isValid(AI& ai, Energy& energy) { 122 + return energy.canUse(artifact); 123 + } 124 + 125 + void considerEnergy(AI& ai, Energy& energy) { 126 + if(type !is null && type.abilities.length != 0) { 127 + value = 1.0; 128 + 129 + for(uint i = 0, cnt = type.ai.length; i < cnt; ++i) { 130 + ArtifactAI@ ai; 131 + if(ability.id == 0) 132 + @ai = cast<ArtifactAI>(type.ai[i]); 133 + else 134 + @ai = cast<ArtifactAI>(type.secondaryAI[i]); 135 + if(ai !is null) { 136 + if(!ai.consider(energy, this, value)) { 137 + value = 0.0; 138 + break; 139 + } 140 + } 141 + } 142 + if(type.ai.length == 0) 143 + value = 0.0; 144 + 145 + if(ability.targets.length != 0) { 146 + if(ability.targets[0].type == TT_Object) { 147 + @ability.targets[0].obj = target; 148 + ability.targets[0].filled = true; 149 + 150 + if(target is null) 151 + value = 0.0; 152 + } 153 + else if(ability.targets[0].type == TT_Point) { 154 + ability.targets[0].point = pointTarget; 155 + ability.targets[0].filled = true; 156 + } 157 + } 158 + 159 + if(value > 0.0) { 160 + if(!ability.canActivate(ability.targets, ignoreCost=true)) { 161 + value = 0.0; 162 + } 163 + else { 164 + cost = ability.getEnergyCost(ability.targets); 165 + if(cost != 0.0) { 166 + //Estimate the amount of turns it would take to trigger this, 167 + //and devalue it based on that. This is ceiled in order to allow 168 + //for artifacts of similar cost to not be affected by cost differences. 169 + double effCost = effCostEstimate(cost, energy.freeStorage); 170 + double estTime = effCost / max(energy.baseIncome, 0.01); 171 + double turns = ceil(estTime / (3.0 * 60.0)); 172 + value /= turns; 173 + } 174 + else { 175 + value *= 1000.0; 176 + } 177 + } 178 + } 179 + } 180 + else { 181 + value = 0.0; 182 + } 183 + } 184 + 185 + void execute(AI& ai, Energy& energy) { 186 + if(artifact !is null && type.abilities.length != 0) { 187 + if(energy.log) 188 + ai.print("Activate artifact "+artifact.name, artifact.region); 189 + 190 + if(ability.type.targets.length != 0) { 191 + if(ability.type.targets[0].type == TT_Object) 192 + artifact.activateAbilityTypeFor(ai.empire, ability.type.id, target); 193 + else if(ability.type.targets[0].type == TT_Point) 194 + artifact.activateAbilityTypeFor(ai.empire, ability.type.id, pointTarget); 195 + } 196 + else { 197 + artifact.activateAbilityTypeFor(ai.empire, ability.type.id); 198 + } 199 + } 200 + } 201 + 202 + int opCmp(const ConsiderEnergy@ other) const { 203 + if(value < other.value) 204 + return -1; 205 + if(value > other.value) 206 + return 1; 207 + return 0; 208 + } 209 +}; 210 + 211 +class Energy : AIComponent, Artifacts { 212 + Systems@ systems; 213 + 214 + double baseIncome; 215 + double freeStorage; 216 + 217 + array<ConsiderEnergy@> queue; 218 + int nextEnergyId = 0; 219 + 220 + void save(SaveFile& file) { 221 + file << nextEnergyId; 222 + 223 + uint cnt = queue.length; 224 + file << cnt; 225 + for(uint i = 0; i < cnt; ++i) 226 + queue[i].save(ai, file); 227 + } 228 + 229 + void load(SaveFile& file) { 230 + file >> nextEnergyId; 231 + 232 + uint cnt = 0; 233 + file >> cnt; 234 + for(uint i = 0; i < cnt; ++i) { 235 + ConsiderEnergy c; 236 + c.load(ai, file); 237 + 238 + if(c.artifact !is null) 239 + queue.insertLast(c); 240 + } 241 + } 242 + 243 + void create() { 244 + @systems = cast<Systems>(ai.systems); 245 + } 246 + 247 + Considerer@ get_consider() { 248 + return cast<Considerer>(ai.consider); 249 + } 250 + 251 + Empire@ get_empire() { 252 + return ai.empire; 253 + } 254 + 255 + bool canUse(Artifact@ artifact) { 256 + if(artifact is null || !artifact.valid) 257 + return false; 258 + Empire@ owner = artifact.owner; 259 + if(owner.valid && owner !is ai.empire) 260 + return false; 261 + Region@ reg = artifact.region; 262 + if(reg is null) 263 + return false; 264 + if(reg.PlanetsMask != 0) 265 + return reg.PlanetsMask & ai.mask != 0; 266 + else 267 + return hasTradeAdjacent(ai.empire, reg); 268 + } 269 + 270 + ConsiderEnergy@ registerArtifact(Artifact@ artifact) { 271 + if(!canUse(artifact)) 272 + return null; 273 + 274 + for(uint i = 0, cnt = queue.length; i < cnt; ++i) { 275 + if(queue[i].artifact is artifact) 276 + return queue[i]; 277 + } 278 + 279 + ConsiderEnergy c; 280 + c.id = nextEnergyId++; 281 + c.init(ai, artifact); 282 + 283 + if(log) 284 + ai.print("Detect artifact "+artifact.name, artifact.region); 285 + 286 + queue.insertLast(c); 287 + return c; 288 + } 289 + 290 + uint updateIdx = 0; 291 + bool update() { 292 + if(queue.length == 0) 293 + return false; 294 + updateIdx = (updateIdx+1) % queue.length; 295 + auto@ c = queue[updateIdx]; 296 + double prevValue = c.value; 297 + 298 + //Make sure this is still valid 299 + if(!c.isValid(ai, this)) { 300 + queue.removeAt(updateIdx); 301 + return false; 302 + } 303 + 304 + //Update the current target and value 305 + c.considerEnergy(ai, this); 306 + 307 + /*if(log)*/ 308 + /* ai.print(c.artifact.name+": consider "+c.value+" for cost "+c.cost, c.target);*/ 309 + 310 + //Only re-sort when needed 311 + bool changed = false; 312 + if(prevValue != c.value) { 313 + if(updateIdx > 0) { 314 + if(c.value > queue[updateIdx-1].value) 315 + changed = true; 316 + } 317 + if(updateIdx < queue.length-1) { 318 + if(c.value < queue[updateIdx+1].value) 319 + changed = true; 320 + } 321 + } 322 + 323 + return changed; 324 + } 325 + 326 + uint sysIdx = 0; 327 + void updateSystem() { 328 + uint totCnt = systems.owned.length + systems.outsideBorder.length; 329 + if(totCnt == 0) 330 + return; 331 + 332 + sysIdx = (sysIdx+1) % totCnt; 333 + SystemAI@ sys; 334 + if(sysIdx < systems.owned.length) 335 + @sys = systems.owned[sysIdx]; 336 + else 337 + @sys = systems.outsideBorder[sysIdx - systems.owned.length]; 338 + 339 + for(uint i = 0, cnt = sys.artifacts.length; i < cnt; ++i) 340 + registerArtifact(sys.artifacts[i]); 341 + } 342 + 343 + void tick(double time) { 344 + if(ai.behavior.forbidArtifact) return; 345 + //Update current income 346 + baseIncome = empire.EnergyIncome; 347 + freeStorage = empire.FreeEnergyStorage; 348 + 349 + //See if we can use anything right now 350 + if(queue.length != 0) { 351 + auto@ c = queue[0]; 352 + if(!c.isValid(ai, this)) { 353 + queue.removeAt(0); 354 + } 355 + else if(c.value > 0.0 && ai.empire.EnergyStored >= c.cost) { 356 + c.execute(ai, this); 357 + queue.removeAt(0); 358 + } 359 + } 360 + } 361 + 362 + void focusTick(double time) { 363 + if(ai.behavior.forbidArtifact) return; 364 + //Consider artifact usage 365 + bool changed = false; 366 + for(uint n = 0; n < min(queue.length, max(ai.behavior.artifactFocusConsiderCount, queue.length/20)); ++n) { 367 + if(update()) 368 + changed = true; 369 + } 370 + 371 + //Re-sort consideration 372 + if(changed) 373 + queue.sortDesc(); 374 + 375 + //Try to find new artifacts 376 + updateSystem(); 377 + } 378 +}; 379 + 380 +AIComponent@ createEnergy() { 381 + return Energy(); 382 +}
Added scripts/server/empire_ai/weasel/Events.as.
1 +// Events 2 +// ------ 3 +// Notifies subscribed components of events raised by other components. 4 +// 5 +import empire_ai.weasel.WeaselAI; 6 + 7 +import ai.events; 8 + 9 +final class Events : AIComponent { 10 + //Event callbacks 11 + 12 + private array<EventHandler@> _onOwnedSystemAdded; 13 + private array<EventHandler@> _onOwnedSystemRemoved; 14 + private array<EventHandler@> _onBorderSystemAdded; 15 + private array<EventHandler@> _onBorderSystemRemoved; 16 + private array<EventHandler@> _onOutsideBorderSystemAdded; 17 + private array<EventHandler@> _onOutsideBorderSystemRemoved; 18 + private array<EventHandler@> _onPlanetAdded; 19 + private array<EventHandler@> _onPlanetRemoved; 20 + private array<EventHandler@> _onTradeRouteNeeded; 21 + private array<EventHandler@> _onOrbitalRequested; 22 + 23 + void create() { 24 + } 25 + 26 + //Event delegate registration 27 + 28 + Events@ opAddAssign(IOwnedSystemEvents& events) { 29 + _onOwnedSystemAdded.insertLast(EventHandler(events.onOwnedSystemAdded)); 30 + _onOwnedSystemRemoved.insertLast(EventHandler(events.onOwnedSystemRemoved)); 31 + return this; 32 + } 33 + 34 + Events@ opAddAssign(IBorderSystemEvents& events) { 35 + _onBorderSystemAdded.insertLast(EventHandler(events.onBorderSystemAdded)); 36 + _onBorderSystemRemoved.insertLast(EventHandler(events.onBorderSystemRemoved)); 37 + return this; 38 + } 39 + 40 + Events@ opAddAssign(IOutsideBorderSystemEvents& events) { 41 + _onOutsideBorderSystemAdded.insertLast(EventHandler(events.onOutsideBorderSystemAdded)); 42 + _onOutsideBorderSystemRemoved.insertLast(EventHandler(events.onOutsideBorderSystemRemoved)); 43 + return this; 44 + } 45 + 46 + Events@ opAddAssign(IPlanetEvents& events) { 47 + _onPlanetAdded.insertLast(EventHandler(events.onPlanetAdded)); 48 + _onPlanetRemoved.insertLast(EventHandler(events.onPlanetRemoved)); 49 + return this; 50 + } 51 + 52 + Events@ opAddAssign(ITradeRouteEvents& events) { 53 + _onTradeRouteNeeded.insertLast(EventHandler(events.onTradeRouteNeeded)); 54 + return this; 55 + } 56 + 57 + Events@ opAddAssign(IOrbitalRequestEvents& events) { 58 + _onOrbitalRequested.insertLast(EventHandler(events.onOrbitalRequested)); 59 + return this; 60 + } 61 + 62 + //Event notifications 63 + 64 + private void raiseEvent(array<EventHandler@>& subscribed, ref@ sender, EventArgs& args) { 65 + for (uint i = 0, cnt = subscribed.length; i < cnt; ++i) 66 + subscribed[i](sender, args); 67 + } 68 + 69 + void notifyOwnedSystemAdded(ref@ sender, EventArgs& args) { 70 + raiseEvent(_onOwnedSystemAdded, sender, args); 71 + } 72 + 73 + void notifyOwnedSystemRemoved(ref@ sender, EventArgs& args) { 74 + raiseEvent(_onOwnedSystemRemoved, sender, args); 75 + } 76 + 77 + void notifyBorderSystemAdded(ref@ sender, EventArgs& args) { 78 + raiseEvent(_onBorderSystemAdded, sender, args); 79 + } 80 + 81 + void notifyBorderSystemRemoved(ref@ sender, EventArgs& args) { 82 + raiseEvent(_onBorderSystemRemoved, sender, args); 83 + } 84 + 85 + void notifyOutsideBorderSystemAdded(ref@ sender, EventArgs& args) { 86 + raiseEvent(_onOutsideBorderSystemAdded, sender, args); 87 + } 88 + 89 + void notifyOutsideBorderSystemRemoved(ref@ sender, EventArgs& args) { 90 + raiseEvent(_onOutsideBorderSystemRemoved, sender, args); 91 + } 92 + 93 + void notifyPlanetAdded(ref@ sender, EventArgs& args) { 94 + raiseEvent(_onPlanetAdded, sender, args); 95 + } 96 + 97 + void notifyPlanetRemoved(ref@ sender, EventArgs& args) { 98 + raiseEvent(_onPlanetRemoved, sender, args); 99 + } 100 + 101 + void notifyTradeRouteNeeded(ref@ sender, EventArgs& args) { 102 + raiseEvent(_onTradeRouteNeeded, sender, args); 103 + } 104 + 105 + void notifyOrbitalRequested(ref@ sender, EventArgs& args) { 106 + raiseEvent(_onOrbitalRequested, sender, args); 107 + } 108 + 109 + void save(SaveFile& file) { 110 + } 111 + 112 + void load(SaveFile& file) { 113 + } 114 +}; 115 + 116 +AIComponent@ createEvents() { 117 + return Events(); 118 +}
Added scripts/server/empire_ai/weasel/Fleets.as.
1 +// Fleets 2 +// ------ 3 +// Manages data about fleets and missions, as well as making sure fleets 4 +// return to their station after a mission. 5 +// 6 + 7 +import empire_ai.weasel.WeaselAI; 8 + 9 +import empire_ai.weasel.Systems; 10 +import empire_ai.weasel.Designs; 11 +import empire_ai.weasel.Movement; 12 + 13 +enum FleetClass { 14 + FC_Scout, 15 + FC_Combat, 16 + FC_Slipstream, 17 + FC_Mothership, 18 + FC_Defense, 19 + 20 + FC_ALL 21 +}; 22 + 23 +enum MissionPriority { 24 + MiP_Background, 25 + MiP_Normal, 26 + MiP_High, 27 + MiP_Critical, 28 +} 29 + 30 +class Mission { 31 + int id = -1; 32 + bool completed = false; 33 + bool canceled = false; 34 + uint priority = MiP_Normal; 35 + 36 + void _save(Fleets& fleets, SaveFile& file) { 37 + file << completed; 38 + file << canceled; 39 + file << priority; 40 + save(fleets, file); 41 + } 42 + 43 + void _load(Fleets& fleets, SaveFile& file) { 44 + file >> completed; 45 + file >> canceled; 46 + file >> priority; 47 + load(fleets, file); 48 + } 49 + 50 + void save(Fleets& fleets, SaveFile& file) { 51 + } 52 + 53 + void load(Fleets& fleets, SaveFile& file) { 54 + } 55 + 56 + bool get_isActive() { 57 + return true; 58 + } 59 + 60 + double getPerformWeight(AI& ai, FleetAI& fleet) { 61 + return 1.0; 62 + } 63 + 64 + void start(AI& ai, FleetAI& fleet) { 65 + } 66 + 67 + void cancel(AI& ai, FleetAI& fleet) { 68 + } 69 + 70 + void tick(AI& ai, FleetAI& fleet, double time) { 71 + } 72 +}; 73 + 74 +final class FleetAI { 75 + uint fleetClass; 76 + Object@ obj; 77 + Mission@ mission; 78 + 79 + Region@ stationed; 80 + bool stationedFactory = true; 81 + 82 + double filled = 0.0; 83 + double idleSince = 0.0; 84 + double fillStaticSince = 0.0; 85 + 86 + void save(Fleets& fleets, SaveFile& file) { 87 + file << fleetClass; 88 + file << stationed; 89 + file << filled; 90 + file << idleSince; 91 + file << fillStaticSince; 92 + file << stationedFactory; 93 + 94 + fleets.saveMission(file, mission); 95 + } 96 + 97 + void load(Fleets& fleets, SaveFile& file) { 98 + file >> fleetClass; 99 + file >> stationed; 100 + file >> filled; 101 + file >> idleSince; 102 + file >> fillStaticSince; 103 + file >> stationedFactory; 104 + 105 + @mission = fleets.loadMission(file); 106 + } 107 + 108 + bool get_isHome() { 109 + if(stationed is null) 110 + return true; 111 + return obj.region is stationed; 112 + } 113 + 114 + bool get_busy() { 115 + return mission !is null; 116 + } 117 + 118 + double get_strength() { 119 + return obj.getFleetStrength(); 120 + } 121 + 122 + double get_supplies() { 123 + Ship@ ship = cast<Ship>(obj); 124 + if(ship is null) 125 + return 1.0; 126 + double maxSupply = ship.MaxSupply; 127 + if(maxSupply <= 0) 128 + return 1.0; 129 + return ship.Supply / maxSupply; 130 + } 131 + 132 + double get_remainingSupplies() { 133 + Ship@ ship = cast<Ship>(obj); 134 + if(ship is null) 135 + return 0.0; 136 + return ship.Supply; 137 + } 138 + 139 + double get_radius() { 140 + return obj.getFormationRadius(); 141 + } 142 + 143 + double get_fleetHealth() { 144 + return obj.getFleetStrength() / obj.getFleetMaxStrength(); 145 + } 146 + 147 + double get_flagshipHealth() { 148 + Ship@ ship = cast<Ship>(obj); 149 + if(ship is null) 150 + return 1.0; 151 + return ship.blueprint.currentHP / ship.blueprint.design.totalHP; 152 + } 153 + 154 + bool get_actionableState() { 155 + if(isHome && obj.hasOrderedSupports && stationedFactory) 156 + return false; 157 + if(supplies < 0.75) 158 + return false; 159 + if(filled < 0.5) 160 + return false; 161 + if(filled < 1.0 && gameTime < fillStaticSince + 90.0) 162 + return false; 163 + return true; 164 + } 165 + 166 + bool get_readyForAction() { 167 + if(mission !is null) 168 + return false; 169 + if(isHome && obj.hasOrderedSupports && stationedFactory) 170 + return false; 171 + if(supplies < 0.75) 172 + return false; 173 + if(filled < 0.5) 174 + return false; 175 + if(filled < 1.0 && gameTime < fillStaticSince + 90.0) 176 + return false; 177 + if(obj.isMoving) { 178 + if(obj.velocity.length / obj.maxAcceleration > 16.0) 179 + return false; 180 + } 181 + //DOF - Do not send badly damaged flagships 182 + Ship@ flagship = cast<Ship>(obj); 183 + auto@ bp = flagship.blueprint; 184 + if(bp.currentHP / bp.design.totalHP < 0.75) { 185 + return false; 186 + } 187 + return true; 188 + } 189 + 190 + bool tick(AI& ai, Fleets& fleets, double time) { 191 + //Make sure we still exist 192 + if(!obj.valid || obj.owner !is ai.empire) { 193 + if(mission !is null) { 194 + mission.canceled = true; 195 + @mission = null; 196 + } 197 + return false; 198 + } 199 + 200 + //Record data 201 + int supUsed = obj.SupplyUsed; 202 + int supCap = obj.SupplyCapacity; 203 + int supGhost = obj.SupplyGhost; 204 + int supOrdered = obj.SupplyOrdered; 205 + 206 + double newFill = 1.0; 207 + if(supCap > 0.0) 208 + newFill = double(supUsed - supGhost - supOrdered) / double(supCap); 209 + if(newFill != filled) { 210 + fillStaticSince = gameTime; 211 + filled = newFill; 212 + } 213 + 214 + //Perform our mission 215 + if(mission !is null) { 216 + if(!mission.completed && !mission.canceled) 217 + mission.tick(ai, this, time); 218 + if(mission.completed || mission.canceled) { 219 + @mission = null; 220 + idleSince = gameTime; 221 + } 222 + } 223 + 224 + //Return to where we're stationed if we're not doing anything 225 + if(mission is null && stationed !is null && fleetClass != FC_Scout) { 226 + if(gameTime >= idleSince + ai.behavior.fleetIdleReturnStationedTime) { 227 + if(obj.region !is stationed && !obj.hasOrders) { 228 + if(fleets.log) 229 + ai.print("Returning to station in "+stationed.name, obj); 230 + fleets.movement.move(obj, stationed, spread=true); 231 + } 232 + } 233 + } 234 + return true; 235 + } 236 +}; 237 + 238 +class Fleets : AIComponent { 239 + Systems@ systems; 240 + Designs@ designs; 241 + Movement@ movement; 242 + 243 + array<FleetAI@> fleets; 244 + 245 + int nextMissionId = 0; 246 + double totalStrength = 0; 247 + double totalMaxStrength = 0; 248 + 249 + void create() { 250 + @systems = cast<Systems>(ai.systems); 251 + @designs = cast<Designs>(ai.designs); 252 + @movement = cast<Movement>(ai.movement); 253 + } 254 + 255 + void save(SaveFile& file) { 256 + file << nextMissionId; 257 + file << totalStrength; 258 + file << totalMaxStrength; 259 + 260 + uint cnt = fleets.length; 261 + file << cnt; 262 + for(uint i = 0; i < cnt; ++i) { 263 + saveAI(file, fleets[i]); 264 + fleets[i].save(this, file); 265 + } 266 + } 267 + 268 + void load(SaveFile& file) { 269 + file >> nextMissionId; 270 + file >> totalStrength; 271 + file >> totalMaxStrength; 272 + 273 + uint cnt = 0; 274 + file >> cnt; 275 + for(uint i = 0; i < cnt; ++i) { 276 + FleetAI@ flAI = loadAI(file); 277 + if(flAI !is null) 278 + flAI.load(this, file); 279 + else 280 + FleetAI().load(this, file); 281 + } 282 + } 283 + 284 + void saveAI(SaveFile& file, FleetAI@ flAI) { 285 + if(flAI is null) { 286 + file.write0(); 287 + return; 288 + } 289 + file.write1(); 290 + file << flAI.obj; 291 + } 292 + 293 + FleetAI@ loadAI(SaveFile& file) { 294 + if(!file.readBit()) 295 + return null; 296 + 297 + Object@ obj; 298 + file >> obj; 299 + 300 + if(obj is null) 301 + return null; 302 + 303 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 304 + if(fleets[i].obj is obj) 305 + return fleets[i]; 306 + } 307 + 308 + FleetAI flAI; 309 + @flAI.obj = obj; 310 + fleets.insertLast(flAI); 311 + return flAI; 312 + } 313 + 314 + array<Mission@> savedMissions; 315 + array<Mission@> loadedMissions; 316 + void postSave(AI& ai) { 317 + savedMissions.length = 0; 318 + } 319 + void postLoad(AI& ai) { 320 + loadedMissions.length = 0; 321 + } 322 + 323 + void saveMission(SaveFile& file, Mission@ mission) { 324 + if(mission is null) { 325 + file.write0(); 326 + return; 327 + } 328 + 329 + file.write1(); 330 + file << mission.id; 331 + if(mission.id == -1) { 332 + storeMission(file, mission); 333 + } 334 + else { 335 + bool found = false; 336 + for(uint i = 0, cnt = savedMissions.length; i < cnt; ++i) { 337 + if(savedMissions[i] is mission) { 338 + found = true; 339 + break; 340 + } 341 + } 342 + 343 + if(!found) { 344 + storeMission(file, mission); 345 + savedMissions.insertLast(mission); 346 + } 347 + } 348 + } 349 + 350 + Mission@ loadMission(SaveFile& file) { 351 + if(!file.readBit()) 352 + return null; 353 + 354 + int id = 0; 355 + file >> id; 356 + if(id == -1) { 357 + Mission@ miss = createMission(file); 358 + miss.id = id; 359 + return miss; 360 + } 361 + else { 362 + for(uint i = 0, cnt = loadedMissions.length; i < cnt; ++i) { 363 + if(loadedMissions[i].id == id) 364 + return loadedMissions[i]; 365 + } 366 + 367 + Mission@ miss = createMission(file); 368 + miss.id = id; 369 + loadedMissions.insertLast(miss); 370 + return miss; 371 + } 372 + } 373 + 374 + void storeMission(SaveFile& file, Mission@ mission) { 375 + auto@ cls = getClass(mission); 376 + auto@ mod = cls.module; 377 + 378 + file << mod.name; 379 + file << cls.name; 380 + mission._save(this, file); 381 + } 382 + 383 + Mission@ createMission(SaveFile& file) { 384 + string modName; 385 + string clsName; 386 + 387 + file >> modName; 388 + file >> clsName; 389 + 390 + auto@ mod = getScriptModule(modName); 391 + if(mod is null) { 392 + error("ERROR: AI Load could not find module for mission "+modName+"::"+clsName); 393 + return null; 394 + } 395 + 396 + auto@ cls = mod.getClass(clsName); 397 + if(cls is null) { 398 + error("ERROR: AI Load could not find class for mission "+modName+"::"+clsName); 399 + return null; 400 + } 401 + 402 + auto@ miss = cast<Mission>(cls.create()); 403 + if(miss is null) { 404 + error("ERROR: AI Load could not create class instance for mission "+modName+"::"+clsName); 405 + return null; 406 + } 407 + 408 + miss._load(this, file); 409 + return miss; 410 + } 411 + 412 + void checkForFleets() { 413 + auto@ data = ai.empire.getFlagships(); 414 + Object@ obj; 415 + while(receive(data, obj)) { 416 + if(obj !is null) 417 + register(obj); 418 + } 419 + @data = ai.empire.getStations(); 420 + while(receive(data, obj)) { 421 + if(obj !is null) 422 + register(obj); 423 + } 424 + } 425 + 426 + bool haveCombatReadyFleets() { 427 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 428 + auto@ flAI = fleets[i]; 429 + if(flAI.fleetClass != FC_Combat) 430 + continue; 431 + if(!flAI.readyForAction) 432 + continue; 433 + return true; 434 + } 435 + return false; 436 + } 437 + 438 + uint countCombatReadyFleets() { 439 + uint count = 0; 440 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 441 + auto@ flAI = fleets[i]; 442 + if(flAI.fleetClass != FC_Combat) 443 + continue; 444 + if(!flAI.readyForAction) 445 + continue; 446 + count += 1; 447 + } 448 + return count; 449 + } 450 + 451 + bool allFleetsCombatReady() { 452 + bool have = false; 453 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 454 + auto@ flAI = fleets[i]; 455 + if(flAI.fleetClass != FC_Combat) 456 + continue; 457 + if(!flAI.readyForAction) 458 + return false; 459 + have = true; 460 + } 461 + return have; 462 + } 463 + 464 + uint prevFleetCount = 0; 465 + double checkTimer = 0; 466 + void focusTick(double time) override { 467 + //Check for any newly obtained fleets 468 + uint curFleetCount = ai.empire.fleetCount; 469 + checkTimer += time; 470 + if(curFleetCount != prevFleetCount || checkTimer > 60.0) { 471 + checkForFleets(); 472 + prevFleetCount = curFleetCount; 473 + checkTimer = 0; 474 + } 475 + 476 + //Calculate our current strengths 477 + totalStrength = 0; 478 + totalMaxStrength = 0; 479 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 480 + totalStrength += sqrt(fleets[i].obj.getFleetStrength()); 481 + totalMaxStrength += sqrt(fleets[i].obj.getFleetMaxStrength()); 482 + } 483 + totalStrength = sqr(totalStrength); 484 + totalMaxStrength = sqr(totalMaxStrength); 485 + } 486 + 487 + double getTotalStrength(uint checkClass, bool idleOnly = false, bool readyOnly = false) { 488 + double str = 0.0; 489 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 490 + auto@ flAI = fleets[i]; 491 + if((flAI.fleetClass == checkClass || checkClass == FC_ALL) 492 + && (!idleOnly || flAI.mission is null) 493 + && (!readyOnly || flAI.readyForAction)) 494 + str += sqrt(fleets[i].obj.getFleetStrength()); 495 + } 496 + return str*str; 497 + } 498 + 499 + void tick(double time) override { 500 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 501 + if(!fleets[i].tick(ai, this, time)) { 502 + fleets.removeAt(i); 503 + --i; --cnt; 504 + continue; 505 + } 506 + 507 + Region@ reg = fleets[i].obj.region; 508 + if(reg !is null) 509 + systems.focus(reg); 510 + } 511 + } 512 + 513 + MoveOrder@ returnToBase(FleetAI@ fleet, uint priority = MP_Normal) { 514 + if(fleet.stationed !is null) 515 + return movement.move(fleet.obj, fleet.stationed, priority, spread=true); 516 + return null; 517 + } 518 + 519 + FleetAI@ register(Object@ obj) { 520 + FleetAI@ flAI = getAI(obj); 521 + 522 + if(flAI is null) { 523 + @flAI = FleetAI(); 524 + @flAI.obj = obj; 525 + @flAI.stationed = obj.region; 526 + obj.setHoldPosition(true); 527 + 528 + uint designClass = designs.classify(obj); 529 + 530 + if(designClass == DP_Scout) 531 + flAI.fleetClass = FC_Scout; 532 + else if(designClass == DP_Slipstream) 533 + flAI.fleetClass = FC_Slipstream; 534 + else if(designClass == DP_Mothership) 535 + flAI.fleetClass = FC_Mothership; 536 + else if(designClass == DP_Defense) 537 + flAI.fleetClass = FC_Defense; 538 + else 539 + flAI.fleetClass = FC_Combat; 540 + 541 + fleets.insertLast(flAI); 542 + } 543 + 544 + return flAI; 545 + } 546 + 547 + void register(Mission@ mission) { 548 + if(mission.id == -1) 549 + mission.id = nextMissionId++; 550 + } 551 + 552 + FleetAI@ getAI(Object@ obj) { 553 + if(obj is null) 554 + return null; 555 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 556 + if(fleets[i].obj is obj) 557 + return fleets[i]; 558 + } 559 + return null; 560 + } 561 + 562 + uint count(uint checkClass) { 563 + uint amount = 0; 564 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 565 + auto@ flAI = fleets[i]; 566 + if(flAI.fleetClass == checkClass || checkClass == FC_ALL) 567 + amount += 1; 568 + } 569 + return amount; 570 + } 571 + 572 + bool haveIdle(uint checkClass) { 573 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 574 + auto@ flAI = fleets[i]; 575 + if((flAI.fleetClass == checkClass || checkClass == FC_ALL) && flAI.mission is null) 576 + return true; 577 + } 578 + return false; 579 + } 580 + 581 + double closestIdleTo(uint checkClass, const vec3d& position) { 582 + double closest = INFINITY; 583 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 584 + auto@ flAI = fleets[i]; 585 + if((flAI.fleetClass != checkClass && checkClass != FC_ALL) || flAI.mission !is null) 586 + continue; 587 + 588 + double d = flAI.obj.position.distanceTo(position); 589 + if(d < closest) 590 + closest = d; 591 + } 592 + return closest; 593 + } 594 + 595 + FleetAI@ performMission(Mission@ mission) { 596 + FleetAI@ perform; 597 + double bestWeight = 0.0; 598 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 599 + auto@ flAI = fleets[i]; 600 + if(flAI.mission !is null) 601 + continue; 602 + double w = mission.getPerformWeight(ai, flAI); 603 + if(w > bestWeight) { 604 + bestWeight = w; 605 + @perform = flAI; 606 + } 607 + } 608 + 609 + if(perform !is null) { 610 + @perform.mission = mission; 611 + register(mission); 612 + mission.start(ai, perform); 613 + } 614 + return perform; 615 + } 616 + 617 + FleetAI@ performMission(FleetAI@ fleet, Mission@ mission) { 618 + if(fleet.mission !is null) { 619 + fleet.mission.cancel(ai, fleet); 620 + fleet.mission.canceled = true; 621 + } 622 + @fleet.mission = mission; 623 + register(mission); 624 + mission.start(ai, fleet); 625 + return fleet; 626 + } 627 +}; 628 + 629 +AIComponent@ createFleets() { 630 + return Fleets(); 631 +}
Added scripts/server/empire_ai/weasel/ImportData.as.
1 +import resources; 2 +import tile_resources; 3 +import saving; 4 + 5 +export ResourceSpecType; 6 +export ResourceSpec; 7 +export implementSpec; 8 + 9 +export ImportData; 10 +export ExportData; 11 + 12 +enum ResourceSpecType { 13 + RST_Specific, 14 + RST_Level_Specific, 15 + RST_Level_Minimum, 16 + RST_Pressure_Type, 17 + RST_Pressure_Level0, 18 + RST_Class, 19 +}; 20 + 21 +tidy final class ResourceSpec : Savable { 22 + uint type = RST_Specific; 23 + const ResourceType@ resource; 24 + const ResourceClass@ cls; 25 + uint level = 0; 26 + uint pressureType = 0; 27 + bool isLevelRequirement = false; 28 + bool isForImport = true; 29 + bool allowUniversal = true; 30 + 31 + void save(SaveFile& file) { 32 + file << type; 33 + if(resource !is null) { 34 + file.write1(); 35 + file.writeIdentifier(SI_Resource, resource.id); 36 + } 37 + else { 38 + file.write0(); 39 + } 40 + if(cls !is null) { 41 + file.write1(); 42 + file << cls.ident; 43 + } 44 + else { 45 + file.write0(); 46 + } 47 + file << level; 48 + file << pressureType; 49 + file << isLevelRequirement; 50 + file << isForImport; 51 + file << allowUniversal; 52 + } 53 + 54 + void load(SaveFile& file) { 55 + file >> type; 56 + if(file.readBit()) 57 + @resource = getResource(file.readIdentifier(SI_Resource)); 58 + if(file.readBit()) { 59 + string clsName; 60 + file >> clsName; 61 + @cls = getResourceClass(clsName); 62 + } 63 + file >> level; 64 + file >> pressureType; 65 + file >> isLevelRequirement; 66 + file >> isForImport; 67 + file >> allowUniversal; 68 + } 69 + 70 + bool opEquals(const ResourceSpec& other) const { 71 + if(type != other.type) 72 + return false; 73 + if(isLevelRequirement != other.isLevelRequirement) 74 + return false; 75 + switch(type) { 76 + case RST_Specific: 77 + return other.resource is resource; 78 + case RST_Level_Specific: 79 + case RST_Level_Minimum: 80 + return other.level == level; 81 + case RST_Pressure_Type: 82 + case RST_Pressure_Level0: 83 + return other.pressureType == pressureType; 84 + case RST_Class: 85 + return other.cls is cls; 86 + } 87 + return true; 88 + } 89 + 90 + bool meets(const ResourceType@ check, Object@ fromObj = null, Object@ toObj = null) const { 91 + if(check is null) 92 + return false; 93 + if(allowUniversal && isLevelRequirement) { 94 + if(check.mode == RM_UniversalUnique || check.mode == RM_Universal) { 95 + //HACK: The AI shouldn't use drugs for food and water 96 + switch(type) { 97 + case RST_Level_Specific: 98 + case RST_Level_Minimum: 99 + return level >= 2; 100 + } 101 + return false; 102 + } 103 + } 104 + if(isForImport && !check.exportable && (fromObj is null || fromObj !is toObj)) 105 + return false; 106 + if(isLevelRequirement && check.mode == RM_NonRequirement) 107 + return false; 108 + switch(type) { 109 + case RST_Specific: 110 + return check is resource; 111 + case RST_Level_Specific: 112 + return check.level == level; 113 + case RST_Level_Minimum: 114 + return check.level >= level; 115 + case RST_Pressure_Type: 116 + return check.tilePressure[pressureType] >= max(check.totalPressure * 0.4, 1.0); 117 + case RST_Pressure_Level0: 118 + return check.level == 0 && check.tilePressure[pressureType] >= max(check.totalPressure * 0.4, 1.0); 119 + case RST_Class: 120 + return check.cls is cls; 121 + } 122 + return false; 123 + } 124 + 125 + bool implements(const ResourceRequirement& req) const { 126 + if(!isLevelRequirement) 127 + return false; 128 + switch(req.type) { 129 + case RRT_Resource: 130 + return this.type == RST_Specific && this.resource is req.resource; 131 + case RRT_Class: 132 + case RRT_Class_Types: 133 + return this.type == RST_Class && this.cls is req.cls; 134 + case RRT_Level: 135 + case RRT_Level_Types: 136 + return this.type == RST_Level_Specific && this.level == req.level; 137 + } 138 + return false; 139 + } 140 + 141 + string dump() { 142 + switch(type) { 143 + case RST_Specific: 144 + return resource.name; 145 + case RST_Level_Specific: 146 + return "Tier "+level; 147 + case RST_Level_Minimum: 148 + return "Tier "+level+"+"; 149 + case RST_Pressure_Type: 150 + return "Any "+getTileResourceIdent(pressureType); 151 + case RST_Pressure_Level0: 152 + return "Level 0 "+getTileResourceIdent(pressureType); 153 + case RST_Class: 154 + return "Of "+cls.ident; 155 + } 156 + return "??"; 157 + } 158 + 159 + int get_resourceLevel() const { 160 + switch(type) { 161 + case RST_Specific: 162 + return 0; 163 + case RST_Level_Specific: 164 + return level; 165 + case RST_Level_Minimum: 166 + return level; 167 + case RST_Pressure_Type: 168 + return 0; 169 + case RST_Pressure_Level0: 170 + return 0; 171 + case RST_Class: 172 + return 0; 173 + } 174 + return 0; 175 + } 176 + 177 + int opCmp(const ResourceSpec@ other) const { 178 + int level = this.resourceLevel; 179 + int otherLevel = other.resourceLevel; 180 + if(level > otherLevel) 181 + return 1; 182 + if(level < otherLevel) 183 + return -1; 184 + return 0; 185 + } 186 +}; 187 + 188 +ResourceSpec@ implementSpec(const ResourceRequirement& req) { 189 + ResourceSpec spec; 190 + spec.isLevelRequirement = true; 191 + 192 + switch(req.type) { 193 + case RRT_Resource: 194 + spec.type = RST_Specific; 195 + @spec.resource = req.resource; 196 + break; 197 + case RRT_Class: 198 + case RRT_Class_Types: 199 + spec.type = RST_Class; 200 + @spec.cls = req.cls; 201 + break; 202 + case RRT_Level: 203 + case RRT_Level_Types: 204 + spec.type = RST_Level_Specific; 205 + spec.level = req.level; 206 + break; 207 + } 208 + return spec; 209 +} 210 + 211 +tidy final class ImportData : Savable { 212 + int id = -1; 213 + Object@ obj; 214 + ResourceSpec@ spec; 215 + const ResourceType@ resource; 216 + Object@ fromObject; 217 + int resourceId = -1; 218 + bool beingMet = false; 219 + bool forLevel = false; 220 + bool cycled = false; 221 + bool isColonizing = false; 222 + bool claimedFor = false; 223 + double idleSince = 0.0; 224 + 225 + void save(SaveFile& file) { 226 + file << obj; 227 + file << spec; 228 + if(resource !is null) { 229 + file.write1(); 230 + file.writeIdentifier(SI_Resource, resource.id); 231 + } 232 + else { 233 + file.write0(); 234 + } 235 + file << fromObject; 236 + file << resourceId; 237 + file << beingMet; 238 + file << forLevel; 239 + file << cycled; 240 + file << isColonizing; 241 + file << claimedFor; 242 + file << idleSince; 243 + } 244 + 245 + void load(SaveFile& file) { 246 + file >> obj; 247 + @spec = ResourceSpec(); 248 + file >> spec; 249 + if(file.readBit()) 250 + @resource = getResource(file.readIdentifier(SI_Resource)); 251 + file >> fromObject; 252 + file >> resourceId; 253 + file >> beingMet; 254 + file >> forLevel; 255 + file >> cycled; 256 + file >> isColonizing; 257 + file >> claimedFor; 258 + file >> idleSince; 259 + } 260 + 261 + void set(ExportData@ source) { 262 + @fromObject = source.obj; 263 + resourceId = source.resourceId; 264 + @resource = source.resource; 265 + } 266 + 267 + int opCmp(const ImportData@ other) const { 268 + return spec.opCmp(other.spec); 269 + } 270 + 271 + bool get_isOpen() const { 272 + return !beingMet; 273 + } 274 +}; 275 + 276 +tidy final class ExportData : Savable { 277 + int id = -1; 278 + Object@ obj; 279 + const ResourceType@ resource; 280 + int resourceId = -1; 281 + ImportData@ request; 282 + Object@ developUse; 283 + bool localOnly = false; 284 + 285 + bool get_usable() const { 286 + if(obj is null) 287 + return false; 288 + if(resourceId == obj.primaryResourceId) 289 + return obj.primaryResourceUsable; 290 + else 291 + return obj.getNativeResourceUsableByID(resourceId); 292 + } 293 + 294 + bool get_isPrimary() const { 295 + return resourceId == obj.primaryResourceId; 296 + } 297 + 298 + bool isExportedTo(Object@ check) const { 299 + if(check is obj) 300 + return true; 301 + if(resourceId == obj.primaryResourceId) 302 + return obj.isPrimaryDestination(check); 303 + else 304 + return obj.getNativeResourceDestinationByID(obj.owner, resourceId) is check; 305 + } 306 + 307 + void save(SaveFile& file) { 308 + //Does not save the request link, this is done by Resources 309 + file << obj; 310 + if(resource !is null) { 311 + file.write1(); 312 + file.writeIdentifier(SI_Resource, resource.id); 313 + } 314 + else { 315 + file.write0(); 316 + } 317 + file << resourceId; 318 + file << developUse; 319 + file << localOnly; 320 + } 321 + 322 + void load(SaveFile& file) { 323 + file >> obj; 324 + if(file.readBit()) 325 + @resource = getResource(file.readIdentifier(SI_Resource)); 326 + file >> resourceId; 327 + file >> developUse; 328 + file >> localOnly; 329 + } 330 +};
Added scripts/server/empire_ai/weasel/Infrastructure.as.
1 +// Infrastructure 2 +// ------ 3 +// Manages building basic structures in newly colonized or weakened systems 4 +// to support the Military or Colonization components. 5 +// 6 +import empire_ai.weasel.WeaselAI; 7 + 8 +import empire_ai.weasel.Events; 9 +import empire_ai.weasel.Colonization; 10 +import empire_ai.weasel.Development; 11 +import empire_ai.weasel.Construction; 12 +import empire_ai.weasel.Budget; 13 +import empire_ai.weasel.Orbitals; 14 +import empire_ai.weasel.Systems; 15 +import empire_ai.weasel.Planets; 16 +import empire_ai.weasel.Resources; 17 + 18 +import ai.construction; 19 +import ai.events; 20 + 21 +from ai.orbitals import RegisterForTradeUse; 22 + 23 +from statuses import getStatusID; 24 +from traits import getTraitID; 25 + 26 +enum ResourcePreference { 27 + RP_None, 28 + RP_FoodWater, 29 + RP_Level0, 30 + RP_Level1, 31 + RP_Level2, 32 + RP_Level3, 33 + RP_Scalable, 34 +}; 35 + 36 +enum SystemArea { 37 + SA_Core, 38 + SA_Tradable, 39 +}; 40 + 41 +enum SystemBuildAction { 42 + BA_BuildOutpost, 43 +}; 44 + 45 +enum PlanetBuildAction { 46 + BA_BuildMoonBase, 47 +}; 48 + 49 +enum SystemBuildLocation { 50 + BL_InSystem, 51 + BL_AtSystemEdge, 52 + BL_AtBestPlanet, 53 +}; 54 + 55 +enum FocusType { 56 + FT_None, 57 + FT_Outpost, 58 +} 59 + 60 +int moonBaseStatusId = -1; 61 + 62 +final class OwnedSystemEvents : IOwnedSystemEvents { 63 + Infrastructure@ infrastructure; 64 + 65 + OwnedSystemEvents(Infrastructure& infrastructure) { 66 + @this.infrastructure = infrastructure; 67 + } 68 + 69 + void onOwnedSystemAdded(ref& sender, EventArgs& args) { 70 + SystemAI@ ai = cast<SystemAI>(sender); 71 + if (ai !is null) 72 + infrastructure.registerOwnedSystemAdded(ai); 73 + } 74 + 75 + void onOwnedSystemRemoved(ref& sender, EventArgs& args) { 76 + SystemAI@ ai = cast<SystemAI>(sender); 77 + if (ai !is null) 78 + infrastructure.registerOwnedSystemRemoved(ai); 79 + } 80 +}; 81 + 82 +final class OutsideBorderSystemEvents : IOutsideBorderSystemEvents { 83 + Infrastructure@ infrastructure; 84 + 85 + OutsideBorderSystemEvents(Infrastructure& infrastructure) { 86 + @this.infrastructure = infrastructure; 87 + } 88 + 89 + void onOutsideBorderSystemAdded(ref& sender, EventArgs& args) { 90 + SystemAI@ ai = cast<SystemAI>(sender); 91 + if (ai !is null) 92 + infrastructure.registerOutsideBorderSystemAdded(ai); 93 + } 94 + 95 + void onOutsideBorderSystemRemoved(ref& sender, EventArgs& args) { 96 + SystemAI@ ai = cast<SystemAI>(sender); 97 + if (ai !is null) 98 + infrastructure.registerOutsideBorderSystemRemoved(ai); 99 + } 100 +}; 101 + 102 +final class PlanetEvents : IPlanetEvents { 103 + Infrastructure@ infrastructure; 104 + 105 + PlanetEvents(Infrastructure& infrastructure) { 106 + @this.infrastructure = infrastructure; 107 + } 108 + 109 + void onPlanetAdded(ref& sender, EventArgs& args) { 110 + PlanetAI@ ai = cast<PlanetAI>(sender); 111 + if (ai !is null) 112 + infrastructure.registerPlanetAdded(ai); 113 + } 114 + 115 + void onPlanetRemoved(ref& sender, EventArgs& args) { 116 + PlanetAI@ ai = cast<PlanetAI>(sender); 117 + if (ai !is null) 118 + infrastructure.registerPlanetRemoved(ai); 119 + } 120 +}; 121 + 122 +final class TradeRouteEvents : ITradeRouteEvents { 123 + Infrastructure@ infrastructure; 124 + 125 + TradeRouteEvents(Infrastructure& infrastructure) { 126 + @this.infrastructure = infrastructure; 127 + } 128 + 129 + void onTradeRouteNeeded(ref& sender, EventArgs& args) { 130 + TradeRouteNeededEventArgs@ specs = cast<TradeRouteNeededEventArgs>(args); 131 + if (specs !is null) 132 + infrastructure.establishTradeRoute(specs.territoryA, specs.territoryB); 133 + } 134 +} 135 + 136 +final class OrbitalRequestEvents : IOrbitalRequestEvents { 137 + Infrastructure@ infrastructure; 138 + 139 + OrbitalRequestEvents(Infrastructure& infrastructure) { 140 + @this.infrastructure = infrastructure; 141 + } 142 + 143 + void onOrbitalRequested(ref& sender, EventArgs& args) { 144 + OrbitalRequestedEventArgs@ specs = cast<OrbitalRequestedEventArgs>(args); 145 + if (specs !is null) 146 + infrastructure.requestOrbital(specs.region, specs.module, specs.priority, specs.expires, specs.moneyType); 147 + } 148 +} 149 + 150 +final class SystemOrder { 151 + private IConstruction@ _construction; 152 + 153 + double expires = INFINITY; 154 + 155 + SystemOrder() {} 156 + 157 + SystemOrder(IConstruction@ construction) { 158 + @_construction = (construction); 159 + } 160 + 161 + bool get_isValid() const { return _construction !is null; } 162 + 163 + bool get_isInProgress() const { return _construction.started; } 164 + 165 + bool get_isComplete() const { return _construction.completed; } 166 + 167 + IConstruction@ get_info() const { return _construction; } 168 + 169 + void save(Infrastructure& infrastructure, SaveFile& file) { 170 + file << _construction.id; 171 + file << expires; 172 + } 173 + 174 + void load(Infrastructure& infrastructure, SaveFile& file) { 175 + int id = - 1; 176 + file >> id; 177 + if (id != -1) { 178 + for (uint i = 0, cnt = infrastructure.construction.allocations.length; i < cnt; ++i) { 179 + if (infrastructure.construction.allocations[i].id == id) { 180 + @_construction = infrastructure.construction.allocations[i]; 181 + } 182 + } 183 + } 184 + file >> expires; 185 + } 186 +}; 187 + 188 +final class PlanetOrder { 189 + private IConstruction@ _construction; 190 + 191 + double expires = INFINITY; 192 + 193 + PlanetOrder() {} 194 + 195 + PlanetOrder(IConstruction@ construction) { 196 + @_construction = construction; 197 + } 198 + 199 + bool get_isValid() const { return _construction !is null; } 200 + 201 + bool get_isInProgress() const { return _construction.started; } 202 + 203 + bool get_isComplete() const { return _construction.completed; } 204 + 205 + IConstruction@ get_info() const { return _construction; } 206 + 207 + void save(Infrastructure& infrastructure, SaveFile& file) { 208 + file << _construction.id; 209 + file << expires; 210 + } 211 + 212 + void load(Infrastructure& infrastructure, SaveFile& file) { 213 + int id = - 1; 214 + file >> id; 215 + if (id != -1) { 216 + for (uint i = 0, cnt = infrastructure.planets.constructionRequests.length; i < cnt; ++i) { 217 + if (infrastructure.planets.constructionRequests[i].id == id) { 218 + @_construction = infrastructure.planets.constructionRequests[i]; 219 + } 220 + } 221 + } 222 + file >> expires; 223 + } 224 +}; 225 + 226 +abstract class NextAction { 227 + double priority = 1.0; 228 + bool force = false; 229 + bool critical = false; 230 +}; 231 + 232 +final class SystemAction : NextAction { 233 + private SystemCheck@ _sys; 234 + private SystemBuildAction _action; 235 + private SystemBuildLocation _loc; 236 + 237 + SystemAction(SystemCheck& sys, SystemBuildAction action, SystemBuildLocation loc) { 238 + @_sys = sys; 239 + _action = action; 240 + _loc = loc; 241 + } 242 + 243 + SystemCheck@ get_sys() const { return _sys; } 244 + SystemBuildAction get_action() const { return _action; } 245 + SystemBuildLocation get_loc() const { return _loc; } 246 +}; 247 + 248 +final class PlanetAction : NextAction { 249 + private PlanetCheck@ _pl; 250 + private PlanetBuildAction _action; 251 + 252 + PlanetAction(PlanetCheck& pl, PlanetBuildAction action) { 253 + @_pl = pl; 254 + _action = action; 255 + } 256 + 257 + PlanetCheck@ get_pl() const { return _pl; } 258 + PlanetBuildAction get_action() const { return _action; } 259 +}; 260 + 261 +abstract class Check { 262 + protected double _checkInTime = 0.0; 263 + 264 + Check() { 265 + _checkInTime = gameTime; 266 + } 267 + 268 + double get_checkInTime() const { return _checkInTime; } 269 +} 270 + 271 +namespace SystemCheck { 272 + array<SystemOrder@> allOrders; 273 +} 274 + 275 +final class SystemCheck : Check { 276 + SystemAI@ ai; 277 + 278 + array<SystemOrder@> orders; 279 + 280 + private double _weight = 0.0; 281 + private bool _isUnderAttack = false; 282 + 283 + SystemCheck() {} 284 + 285 + SystemCheck(Infrastructure& infrastructure, SystemAI& ai) { 286 + super(); 287 + @this.ai = ai; 288 + } 289 + 290 + double get_weight() const { return _weight; } 291 + bool get_isUnderAttack() const { return _isUnderAttack; } 292 + bool get_isBuilding() const { return orders.length > 0; } 293 + 294 + void save(Infrastructure& infrastructure, SaveFile& file) { 295 + infrastructure.systems.saveAI(file, ai); 296 + 297 + uint cnt = orders.length; 298 + file << cnt; 299 + for(uint i = 0; i < cnt; ++i) 300 + orders[i].save(infrastructure, file); 301 + 302 + file << _checkInTime; 303 + file << _weight; 304 + file << _isUnderAttack; 305 + } 306 + 307 + void load(Infrastructure& infrastructure, SaveFile& file) { 308 + @ai = infrastructure.systems.loadAI(file); 309 + 310 + uint cnt = 0; 311 + file >> cnt; 312 + for(uint i = 0; i < cnt; ++i) { 313 + auto@ order = SystemOrder(); 314 + order.load(infrastructure, file); 315 + if (order.isValid) 316 + addOrder(order); 317 + } 318 + file >> _checkInTime; 319 + file >> _weight; 320 + file >> _isUnderAttack; 321 + } 322 + 323 + void tick(AI& ai, Infrastructure& infrastructure, double time) { 324 + OrbitalAI@ orbital; 325 + //Update hostile status 326 + _isUnderAttack = this.ai.obj.ContestedMask & ai.mask != 0; 327 + 328 + //Cancel all orders if attacked 329 + /*if (isUnderAttack && isBuilding) { 330 + for (uint i = 0, cnt = orders.length; i < cnt; ++i) { 331 + auto@ order = orders[i]; 332 + //SoI - TODO: Cancel not fully implemented, see Construction.as 333 + infrastructure.construction.cancel(order.info); 334 + removeOrder(order); 335 + --i; --cnt; 336 + } 337 + }*/ 338 + 339 + if (isBuilding) { 340 + for (uint i = 0, cnt = orders.length; i < cnt; ++i) { 341 + auto@ order = orders[i]; 342 + if (!order.isValid) { 343 + removeOrder(order); 344 + --i; --cnt; 345 + } 346 + else if (order.isComplete) { 347 + if (infrastructure.log) 348 + ai.print("order complete"); 349 + removeOrder(order); 350 + --i; --cnt; 351 + } 352 + else if (!order.isInProgress && order.expires < gameTime) { 353 + if (infrastructure.log) 354 + ai.print("order expired, gameTime = " + gameTime); 355 + removeOrder(order); 356 + --i; --cnt; 357 + } 358 + } 359 + } 360 + } 361 + 362 + void focusTick(AI& ai, Infrastructure& infrastructure, double time) { 363 + } 364 + 365 + double check(AI& ai) { 366 + _weight = 0.0; 367 + //Systems under attack are bottom priority for now 368 + if (isUnderAttack) 369 + return weight; 370 + //Hostile systems are bottom priority until cleared 371 + if (this.ai.seenPresent & ai.enemyMask != 0) 372 + return weight; 373 + //Start weighting 374 + double sysWeight = 1.0; 375 + //Oldest systems come first 376 + sysWeight /= (checkInTime + 60.0) / 60.0; 377 + //The home system is a priority 378 + if (this.ai.obj is ai.empire.HomeSystem) 379 + sysWeight *= 2.0; 380 + 381 + _weight = 1.0 * sysWeight; 382 + return weight; 383 + } 384 + 385 + SystemOrder@ buildInSystem(Infrastructure& infrastructure, const OrbitalModule@ module, double priority = 1.0, bool force = false, double delay = 600.0, uint moneyType = BT_Infrastructure) { 386 + vec3d pos = ai.obj.position; 387 + vec2d offset = random2d(ai.obj.radius * 0.4, ai.obj.radius * 0.7); 388 + pos.x += offset.x; 389 + pos.z += offset.y; 390 + 391 + BuildOrbital@ orbital = infrastructure.construction.buildOrbital(module, pos, priority, force, moneyType); 392 + auto@ order = SystemOrder(orbital); 393 + order.expires = gameTime + delay; 394 + addOrder(order); 395 + 396 + return order; 397 + } 398 + 399 + SystemOrder@ buildAtSystemEdge(Infrastructure& infrastructure, const OrbitalModule@ module, double priority = 1.0, bool force = false, double delay = 600.0, uint moneyType = BT_Infrastructure) { 400 + vec3d pos = ai.obj.position; 401 + vec2d offset = random2d(ai.obj.radius * 0.8, ai.obj.radius * 0.9); 402 + pos.x += offset.x; 403 + pos.z += offset.y; 404 + 405 + BuildOrbital@ orbital = infrastructure.construction.buildOrbital(module, pos, priority, force, moneyType); 406 + auto@ order = SystemOrder(orbital); 407 + order.expires = gameTime + delay; 408 + addOrder(order); 409 + 410 + return order; 411 + } 412 + 413 + SystemOrder@ buildAtPlanet(Infrastructure& infrastructure, Planet& planet, const OrbitalModule@ module, double priority = 1.0, bool force = false, double delay = 600.0, uint moneyType = BT_Infrastructure) { 414 + BuildOrbital@ orbital = infrastructure.construction.buildLocalOrbital(module, planet, priority, force, moneyType); 415 + auto@ order = SystemOrder(orbital); 416 + order.expires = gameTime + delay; 417 + addOrder(order); 418 + 419 + return order; 420 + } 421 + 422 + void addOrder(SystemOrder@ order) { 423 + orders.insertLast(order); 424 + SystemCheck::allOrders.insertLast(order); 425 + } 426 + 427 + void removeOrder(SystemOrder@ order) { 428 + orders.remove(order); 429 + SystemCheck::allOrders.remove(order); 430 + @order = null; 431 + } 432 +}; 433 + 434 +namespace PlanetCheck { 435 + array<PlanetOrder@> allOrders; 436 +} 437 + 438 +final class PlanetCheck : Check { 439 + PlanetAI@ ai; 440 + 441 + array<PlanetOrder@> orders; 442 + 443 + private double _weight = 0.0; 444 + private bool _isSystemUnderAttack = false; 445 + 446 + PlanetCheck() {} 447 + 448 + PlanetCheck(Infrastructure& infrastructure, PlanetAI& ai) { 449 + super(); 450 + @this.ai = ai; 451 + } 452 + 453 + double get_weight() const { return _weight; } 454 + bool get_isSystemUnderAttack() const { return _isSystemUnderAttack; } 455 + bool get_isBuilding() const { return orders.length > 0; } 456 + 457 + void save(Infrastructure& infrastructure, SaveFile& file) { 458 + infrastructure.planets.saveAI(file, ai); 459 + 460 + uint cnt = orders.length; 461 + file << cnt; 462 + for(uint i = 0; i < cnt; ++i) 463 + orders[i].save(infrastructure, file); 464 + 465 + file << _checkInTime; 466 + file << _weight; 467 + file << _isSystemUnderAttack; 468 + } 469 + 470 + void load(Infrastructure& infrastructure, SaveFile& file) { 471 + @ai = infrastructure.planets.loadAI(file); 472 + 473 + uint cnt = 0; 474 + file >> cnt; 475 + for(uint i = 0; i < cnt; ++i) { 476 + auto@ order = PlanetOrder(); 477 + order.load(infrastructure, file); 478 + if (order.isValid) 479 + addOrder(order); 480 + } 481 + file >> _checkInTime; 482 + file >> _weight; 483 + file >> _isSystemUnderAttack; 484 + } 485 + 486 + void tick(AI& ai, Infrastructure& infrastructure, double time) { 487 + auto@ sysAI = infrastructure.systems.getAI(this.ai.obj.region); 488 + if (sysAI !is null) 489 + _isSystemUnderAttack = sysAI.obj.ContestedMask & ai.mask != 0; 490 + 491 + if (isBuilding) { 492 + for (uint i = 0, cnt = orders.length; i < cnt; ++i) { 493 + auto@ order = orders[i]; 494 + if (!order.isValid) { 495 + removeOrder(order); 496 + --i; --cnt; 497 + } 498 + else if (order.isComplete) { 499 + if (infrastructure.log) 500 + ai.print("planet order complete"); 501 + removeOrder(order); 502 + --i; --cnt; 503 + } 504 + else if (!order.isInProgress && order.expires < gameTime) { 505 + if (infrastructure.log) 506 + ai.print("planet order expired, gameTime = " + gameTime); 507 + removeOrder(order); 508 + --i; --cnt; 509 + } 510 + } 511 + } 512 + } 513 + 514 + void focusTick(AI& ai, Infrastructure& infrastructure, double time) { 515 + } 516 + 517 + double check(AI& ai) { 518 + _weight = 0.0; 519 + //Planets in systems under attack are bottom priority for now 520 + if (isSystemUnderAttack) 521 + return _weight; 522 + //Start weighting 523 + double plWeight = 1.0; 524 + //Oldest planets come first 525 + plWeight /= (checkInTime + 60.0) / 60.0; 526 + //The homeworld is a priority 527 + if (this.ai.obj is ai.empire.Homeworld) 528 + plWeight *= 2.0; 529 + 530 + _weight = 1.0 * plWeight; 531 + return _weight; 532 + } 533 + 534 + PlanetOrder@ build(Infrastructure& infrastructure, const ConstructionType@ consType, double priority = 1.0, bool force = false, bool critical = false, double delay = 600.0, uint moneyType = BT_Infrastructure) { 535 + ConstructionRequest@ request = infrastructure.planets.requestConstruction(ai, ai.obj, consType, priority, gameTime + delay, moneyType); 536 + auto@ order = PlanetOrder(request); 537 + order.expires = gameTime + delay; 538 + addOrder(order); 539 + 540 + return order; 541 + } 542 + 543 + void addOrder(PlanetOrder@ order) { 544 + orders.insertLast(order); 545 + PlanetCheck::allOrders.insertLast(order); 546 + } 547 + 548 + void removeOrder(PlanetOrder@ order) { 549 + orders.remove(order); 550 + PlanetCheck::allOrders.remove(order); 551 + @order = null; 552 + } 553 +}; 554 + 555 +final class TradeRoute { 556 + private Territory@ _territoryA; 557 + private Territory@ _territoryB; 558 + private Region@ _endpointA; 559 + private Region@ _endpointB; 560 + private SystemOrder@ _orderA; 561 + private SystemOrder@ _orderB; 562 + private bool _isEstablishing; 563 + private bool _isWaitingForLabor; 564 + private double _delay; 565 + private double _sleep; 566 + 567 + TradeRoute() {} 568 + 569 + TradeRoute(Territory& territoryA, Territory& territoryB) { 570 + @_territoryA = territoryA; 571 + @_territoryB = territoryB; 572 + _isEstablishing = false; 573 + _isWaitingForLabor = false; 574 + _delay = 0.0; 575 + _sleep = 0.0; 576 + } 577 + 578 + Territory@ get_territoryA() const { return _territoryA; } 579 + Territory@ get_territoryB() const { return _territoryB; } 580 + Region@ get_endpointA() const { return _endpointA; } 581 + Region@ get_endpointB() const { return _endpointB; } 582 + SystemOrder@ get_orderA() const { return _orderA; } 583 + SystemOrder@ get_orderB() const { return _orderB; } 584 + bool get_isEstablishing() const { return _isEstablishing; } 585 + bool get_isWaitingForLabor() const { return _isWaitingForLabor; } 586 + 587 + void save(Infrastructure& infrastructure, SaveFile& file) { 588 + } 589 + 590 + void load(Infrastructure& infrastructure, SaveFile& file) { 591 + } 592 + 593 + void tick(AI& ai, Infrastructure& infrastructure, double time) { 594 + if (_delay > 0.0 && _delay < gameTime) { 595 + _isWaitingForLabor = false; 596 + _delay = 0.0; 597 + } 598 + } 599 + 600 + void focusTick(AI& ai, Infrastructure& infrastructure, double time) { 601 + } 602 + 603 + bool canEstablish(Infrastructure& infrastructure, bool&out buildAtA, bool&out canBuildAtA, bool&out buildAtB, bool&out canBuildAtB) { 604 + //We're still sleeping 605 + if (_sleep > gameTime) 606 + return false; 607 + //At least one building order is still pending 608 + if (orderA !is null || orderB !is null) 609 + return false; 610 + 611 + buildAtA = true; 612 + buildAtB = true; 613 + canBuildAtA = false; 614 + canBuildAtB = false; 615 + for (uint i = 0, cnt = infrastructure.checkedPlanets.length; i < cnt; ++i) { 616 + Planet@ pl = infrastructure.checkedPlanets[i].ai.obj; 617 + if (pl.region !is null) { 618 + Territory@ t = pl.region.getTerritory(infrastructure.ai.empire); 619 + if (t is territoryA) { 620 + //Is there a global trade node here already 621 + if (pl.region.GateMask & ~pl.owner.mask != 0) { 622 + buildAtA = false; 623 + @_endpointA = pl.region; 624 + } 625 + if (!canBuildAtA) { 626 + //Is there a labor source in this territory 627 + if (pl.laborIncome > 0 && pl.canBuildOrbitals) 628 + canBuildAtA = true; 629 + } 630 + } 631 + else if (t is territoryB) { 632 + //Is there a global trade node here already 633 + if (pl.region.GateMask & ~pl.owner.mask != 0) { 634 + buildAtB = false; 635 + @_endpointB = pl.region; 636 + } 637 + if (!canBuildAtB) { 638 + //Is there a labor source in this territory 639 + if (pl.laborIncome > 0 && pl.canBuildOrbitals) 640 + canBuildAtB = true; 641 + } 642 + } 643 + if (!buildAtA && !buildAtB) { 644 + //Should not normally happen, except if trade if somehow disrupted despite global trade nodes 645 + return false; 646 + } 647 + if (canBuildAtA && canBuildAtB) { 648 + _isWaitingForLabor = false; 649 + return true; 650 + } 651 + } 652 + } 653 + //These checks are expensive and don't need to be run frequently, so let's sleep for some time 654 + _sleep = gameTime + 10.0; 655 + return false; 656 + } 657 + 658 + void establish(Infrastructure& infrastructure, Region@ regionA, Region@ regionB) { 659 + SystemOrder@ orderA = null; 660 + SystemOrder@ orderB = null; 661 + if (regionA !is null) { 662 + @orderA = infrastructure.requestOrbital(regionA, infrastructure.ai.defs.TradeStation); 663 + @_endpointA = regionA; 664 + } 665 + if (regionB !is null) { 666 + @orderB = infrastructure.requestOrbital(regionB, infrastructure.ai.defs.TradeStation); 667 + @_endpointB = regionB; 668 + } 669 + if (orderA is null || orderB is null) { 670 + infrastructure.ai.print("ERROR: could not establish trade route between " + regionA.name + " and " + regionB.name); 671 + return; 672 + } 673 + @_orderA = orderA; 674 + @_orderB = orderB; 675 + _isEstablishing = true; 676 + } 677 + 678 + void waitForLabor(double expires) { 679 + _isWaitingForLabor = true; 680 + _delay = gameTime + expires; 681 + } 682 +} 683 + 684 +final class Infrastructure : AIComponent { 685 + const ResourceClass@ foodClass, waterClass, scalableClass; 686 + 687 + //Current focus 688 + private uint _focus = FT_None; 689 + 690 + Events@ events; 691 + Colonization@ colonization; 692 + Development@ development; 693 + Construction@ construction; 694 + Orbitals@ orbitals; 695 + Planets@ planets; 696 + Systems@ systems; 697 + Budget@ budget; 698 + Resources@ resources; 699 + 700 + array<SystemCheck@> checkedOwnedSystems; //Includes border systems 701 + array<SystemCheck@> checkedOutsideSystems; 702 + array<PlanetCheck@> checkedPlanets; 703 + 704 + array<TradeRoute@> pendingRoutes; 705 + 706 + SystemCheck@ homeSystem; 707 + NextAction@ nextAction; 708 + 709 + //Unlock tracking 710 + bool canBuildGate = false; 711 + bool canBuildMoonBase = true; 712 + 713 + void create() { 714 + @events = cast<Events>(ai.events); 715 + @colonization = cast<Colonization>(ai.colonization); 716 + @development = cast<Development>(ai.development); 717 + @construction = cast<Construction>(ai.construction); 718 + @orbitals = cast<Orbitals>(ai.orbitals); 719 + @planets = cast<Planets>(ai.planets); 720 + @systems = cast<Systems>(ai.systems); 721 + @budget = cast<Budget>(ai.budget); 722 + @resources = cast<Resources>(ai.resources); 723 + 724 + //Cache expensive lookups 725 + @foodClass = getResourceClass("Food"); 726 + @waterClass = getResourceClass("WaterType"); 727 + @scalableClass = getResourceClass("Scalable"); 728 + moonBaseStatusId = getStatusID("MoonBase"); 729 + 730 + events += OwnedSystemEvents(this); 731 + events += OutsideBorderSystemEvents(this); 732 + events += PlanetEvents(this); 733 + events += TradeRouteEvents(this); 734 + 735 + if (ai.empire.hasTrait(getTraitID("Gate"))) 736 + canBuildGate = true; 737 + if (ai.empire.hasTrait(getTraitID("StarChildren"))) 738 + canBuildMoonBase = false; 739 + } 740 + 741 + void save(SaveFile& file) { 742 + file << _focus; 743 + uint cnt = checkedOwnedSystems.length; 744 + file << cnt; 745 + for(uint i = 0; i < cnt; ++i) 746 + checkedOwnedSystems[i].save(this, file); 747 + cnt = checkedOutsideSystems.length; 748 + file << cnt; 749 + for(uint i = 0; i < cnt; ++i) 750 + checkedOutsideSystems[i].save(this, file); 751 + cnt = checkedPlanets.length; 752 + file << cnt; 753 + for(uint i = 0; i < cnt; ++i) 754 + checkedPlanets[i].save(this, file); 755 + } 756 + 757 + void load(SaveFile& file) { 758 + file >> _focus; 759 + uint cnt = 0; 760 + file >> cnt; 761 + for(uint i = 0; i < cnt; ++i) { 762 + SystemCheck@ sys = SystemCheck(); 763 + sys.load(this, file); 764 + checkedOwnedSystems.insertLast(sys); 765 + } 766 + cnt = 0; 767 + file >> cnt; 768 + for(uint i = 0; i < cnt; ++i) { 769 + SystemCheck@ sys = SystemCheck(); 770 + sys.load(this, file); 771 + checkedOutsideSystems.insertLast(sys); 772 + } 773 + cnt = 0; 774 + file >> cnt; 775 + for(uint i = 0; i < cnt; ++i) { 776 + PlanetCheck@ pl = PlanetCheck(); 777 + pl.load(this, file); 778 + checkedPlanets.insertLast(pl); 779 + } 780 + } 781 + 782 + void start() { 783 + } 784 + 785 + void turn() { 786 + if(log) { 787 + ai.print("=============="); 788 + ai.print("Current owned systems checked: " + checkedOwnedSystems.length); 789 + for (uint i = 0, cnt = checkedOwnedSystems.length; i < cnt; ++i) 790 + ai.print(checkedOwnedSystems[i].ai.obj.name); 791 + ai.print("=============="); 792 + ai.print("Current outside border systems checked: " + checkedOutsideSystems.length); 793 + for (uint i = 0, cnt = checkedOutsideSystems.length; i < cnt; ++i) 794 + ai.print(checkedOutsideSystems[i].ai.obj.name); 795 + ai.print("=============="); 796 + ai.print("Current owned planets checked: " + checkedPlanets.length); 797 + for (uint i = 0, cnt = checkedPlanets.length; i < cnt; ++i) 798 + ai.print(checkedPlanets[i].ai.obj.name); 799 + ai.print("=============="); 800 + } 801 + 802 + //Reset any focus 803 + _focus = FT_None; 804 + //If colonization is somehow blocked, force territory expansion by focusing on building outposts 805 + if (colonization.needsMoreTerritory){ 806 + if (budget.canFocus()) { 807 + budget.focus(BT_Infrastructure); 808 + _focus = FT_Outpost; 809 + } 810 + } 811 + } 812 + 813 + void tick(double time) override { 814 + SystemCheck@ sys; 815 + PlanetCheck@ pl; 816 + TradeRoute@ route; 817 + //Perform routine duties 818 + for (uint i = 0, cnt = checkedOwnedSystems.length; i < cnt; ++i) { 819 + @sys = checkedOwnedSystems[i]; 820 + sys.tick(ai, this, time); 821 + } 822 + for (uint i = 0, cnt = checkedOutsideSystems.length; i < cnt; ++i) { 823 + @sys = checkedOutsideSystems[i]; 824 + sys.tick(ai, this, time); 825 + } 826 + for (uint i = 0, cnt = checkedPlanets.length; i < cnt; ++i) { 827 + @pl = checkedPlanets[i]; 828 + pl.tick(ai, this, time); 829 + } 830 + for (uint i = 0, cnt = pendingRoutes.length; i < cnt; ++i) { 831 + @route = pendingRoutes[i]; 832 + route.tick(ai, this, time); 833 + } 834 + } 835 + 836 + void focusTick(double time) override { 837 + SystemCheck@ sys; 838 + PlanetCheck@ pl; 839 + SystemBuildLocation loc; 840 + 841 + bool critical = false; 842 + double w; 843 + double bestWeight = 0.0; 844 + 845 + if(ai.behavior.forbidConstruction) return; 846 + 847 + //Check if owned systems need anything 848 + for (uint i = 0, cnt = checkedOwnedSystems.length; i < cnt; ++i) { 849 + @sys = checkedOwnedSystems[i]; 850 + //Only consider anything if no critical action is underway 851 + if (!critical) { 852 + //Evaluate current weight 853 + w = sys.check(ai); 854 + if (w > bestWeight) { 855 + if (_focus == FT_None || _focus == FT_Outpost) { 856 + //Check if an outpost is needed 857 + if (shouldHaveOutpost(sys, SA_Core, loc)) { 858 + @nextAction = SystemAction(sys, BA_BuildOutpost, loc); 859 + bestWeight = w; 860 + if (log) 861 + ai.print("outpost considered for owned system with weight: " + w, sys.ai.obj); 862 + } 863 + } 864 + } 865 + } 866 + //Perform routine duties 867 + sys.focusTick(ai, this, time); 868 + } 869 + //Check if systems in tradable area need anything 870 + for (uint i = 0, cnt = checkedOutsideSystems.length; i < cnt; ++i) { 871 + @sys = checkedOutsideSystems[i]; 872 + //Skip unexplored systems 873 + if (sys.ai.explored) { 874 + //Only consider anything if no critical action is underway 875 + if (!critical) { 876 + //Evaluate current weight 877 + w = sys.check(ai); 878 + if (w > bestWeight) { 879 + if (_focus == FT_None || _focus == FT_Outpost) { 880 + //Check if an outpost is needed 881 + if (shouldHaveOutpost(sys, SA_Tradable, loc)) { 882 + @nextAction = SystemAction(sys, BA_BuildOutpost, loc); 883 + bestWeight = w; 884 + if (log) 885 + ai.print("outpost considered for outside system with weight: " + w, sys.ai.obj); 886 + } 887 + } 888 + } 889 + } 890 + } 891 + //Perform routine duties 892 + sys.focusTick(ai, this, time); 893 + } 894 + //Check if owned planets need anything 895 + for (uint i = 0, cnt = checkedPlanets.length; i < cnt; ++i) { 896 + @pl = checkedPlanets[i]; 897 + //Only consider anything if no critical action is underway 898 + if (!critical) { 899 + //Planets are their own 'factory' and can only build one construction at a time 900 + if (!pl.isBuilding) { 901 + //Evaluate current weight 902 + w = pl.check(ai); 903 + if (w > bestWeight) { 904 + //Check if a moon base is needed 905 + if (canBuildMoonBase && shouldHaveMoonBase(pl)) { 906 + @nextAction = PlanetAction(pl, BA_BuildMoonBase); 907 + bestWeight = w; 908 + if (log) 909 + ai.print("moon base considered with weight: " + w, pl.ai.obj); 910 + } 911 + } 912 + } 913 + } 914 + //Perform routine duties 915 + pl.focusTick(ai, this, time); 916 + } 917 + //Execute our next action if there is one 918 + if (nextAction !is null) { 919 + Object@ obj; 920 + auto@ next = cast<SystemAction>(nextAction); 921 + if (next !is null) 922 + { 923 + @sys = next.sys; 924 + switch (next.action) { 925 + case BA_BuildOutpost: 926 + switch (next.loc) { 927 + case BL_InSystem: 928 + sys.buildInSystem(this, ai.defs.TradeOutpost, next.priority, next.force); 929 + break; 930 + case BL_AtSystemEdge: 931 + sys.buildAtSystemEdge(this, ai.defs.TradeOutpost, next.priority, next.force); 932 + break; 933 + case BL_AtBestPlanet: 934 + @obj = getBestPlanet(sys); 935 + if (obj !is null) { 936 + sys.buildAtPlanet(this, cast<Planet>(obj), ai.defs.TradeOutpost, next.priority, next.force); 937 + } 938 + break; 939 + default: 940 + ai.print("ERROR: undefined infrastructure building location for outpost"); 941 + } 942 + if (log) 943 + ai.print("outpost ordered", sys.ai.obj); 944 + break; 945 + default: 946 + ai.print("ERROR: undefined infrastructure building action for system"); 947 + } 948 + } 949 + else { 950 + auto@ next = cast<PlanetAction>(nextAction); 951 + if (next !is null) { 952 + @pl = next.pl; 953 + switch (next.action) { 954 + case BA_BuildMoonBase: 955 + pl.build(this, ai.defs.MoonBase, next.priority, next.force, next.critical); 956 + if (log) 957 + ai.print("moon base ordered", pl.ai.obj); 958 + break; 959 + default: 960 + ai.print("ERROR: undefined infrastructure building action for planet"); 961 + } 962 + } 963 + } 964 + 965 + @nextAction = null; 966 + } 967 + 968 + //Manage any pending trading routes 969 + TradeRoute@ route; 970 + for (uint i = 0, cnt = pendingRoutes.length; i < cnt; ++i) { 971 + @route = pendingRoutes[i]; 972 + 973 + if (route.territoryA is null || route.territoryB is null) { 974 + pendingRoutes.remove(route); 975 + --i; --cnt; 976 + if (log) 977 + ai.print("invalid territory for pending route, route canceled"); 978 + } 979 + 980 + bool buildAtA = true; 981 + bool canBuildAtA = false; 982 + bool buildAtB = true; 983 + bool canBuildAtB = false; 984 + 985 + if (route.canEstablish(this, buildAtA, canBuildAtA, buildAtB, canBuildAtB)) { 986 + Region@ regionA = null; 987 + Region@ regionB = null; 988 + if (buildAtA) 989 + @regionA = getRouteEndpoint(route.territoryA); 990 + if (buildAtB) 991 + @regionB = getRouteEndpoint(route.territoryB); 992 + if (regionA !is null && regionB !is null) { 993 + route.establish(this, regionA, regionB); 994 + if (log) 995 + ai.print("trade route establishing between " + regionA.name + " and " + regionB.name); 996 + } 997 + } 998 + else if (!route.isEstablishing && !route.isWaitingForLabor) { 999 + Region@ regionA = null; 1000 + Region@ regionB = null; 1001 + double expires = 0.0; 1002 + if (!canBuildAtA) 1003 + @regionA = getLaborAt(route.territoryA, expires); 1004 + if (!canBuildAtB) 1005 + @regionB = getLaborAt(route.territoryB, expires); 1006 + route.waitForLabor(expires); 1007 + if (log) { 1008 + string location = ""; 1009 + if (!canBuildAtA && regionA !is null) 1010 + location += " " + regionA.name; 1011 + if (!canBuildAtB && regionB !is null) { 1012 + if (location != "") 1013 + location += ", "; 1014 + location += " " + regionB.name; 1015 + } 1016 + if (location == "") 1017 + ai.print("trade route unable to get labor"); 1018 + else 1019 + ai.print("trade route waiting for labor at:" + location); 1020 + } 1021 + } 1022 + if (route.endpointA !is null && route.endpointB !is null && resources.canTradeBetween(route.endpointA, route.endpointB)) { 1023 + pendingRoutes.remove(route); 1024 + --i; --cnt; 1025 + if (log) 1026 + ai.print("trade route established between " + addrstr(route.territoryA) + " and " + addrstr(route.territoryB)); 1027 + } 1028 + //Perform routine duties 1029 + route.focusTick(ai, this, time); 1030 + } 1031 + } 1032 + 1033 + void registerOwnedSystemAdded(SystemAI& sysAI) { 1034 + auto@ sys = SystemCheck(this, sysAI); 1035 + checkedOwnedSystems.insertLast(sys); 1036 + if (log) 1037 + ai.print("adding owned system: " + sysAI.obj.name); 1038 + } 1039 + 1040 + void registerOwnedSystemRemoved(SystemAI& sysAI) { 1041 + for (uint i = 0, cnt = checkedOwnedSystems.length; i < cnt; ++i) { 1042 + if (sysAI is checkedOwnedSystems[i].ai) { 1043 + checkedOwnedSystems.removeAt(i); 1044 + break; 1045 + } 1046 + } 1047 + if (log) 1048 + ai.print("removing owned system: " + sysAI.obj.name); 1049 + } 1050 + 1051 + void registerOutsideBorderSystemAdded(SystemAI& sysAI) { 1052 + auto@ sys = SystemCheck(this, sysAI); 1053 + checkedOutsideSystems.insertLast(sys); 1054 + if (log) 1055 + ai.print("adding outside system: " + sysAI.obj.name); 1056 + } 1057 + 1058 + void registerOutsideBorderSystemRemoved(SystemAI& sysAI) { 1059 + for (uint i = 0, cnt = checkedOutsideSystems.length; i < cnt; ++i) { 1060 + if (sysAI is checkedOutsideSystems[i].ai) { 1061 + checkedOutsideSystems.removeAt(i); 1062 + break; 1063 + } 1064 + } 1065 + if (log) 1066 + ai.print("removing outside system: " + sysAI.obj.name); 1067 + } 1068 + 1069 + void registerPlanetAdded(PlanetAI& plAI) { 1070 + auto@ pl = PlanetCheck(this, plAI); 1071 + checkedPlanets.insertLast(pl); 1072 + if (log) 1073 + ai.print("adding planet: " + plAI.obj.name); 1074 + } 1075 + 1076 + void registerPlanetRemoved(PlanetAI& plAI) { 1077 + for (uint i = 0, cnt = checkedPlanets.length; i < cnt; ++i) { 1078 + if (plAI is checkedPlanets[i].ai) { 1079 + checkedPlanets.removeAt(i); 1080 + break; 1081 + } 1082 + } 1083 + if (log) 1084 + ai.print("removing planet: " + plAI.obj.name); 1085 + } 1086 + 1087 + void establishTradeRoute(Territory@ territoryA, Territory@ territoryB) { 1088 + if (canBuildGate) 1089 + return; 1090 + if (hasPendingTradeRoute(territoryA, territoryB)) { 1091 + if (log) 1092 + ai.print("pending route detected between " + addrstr(territoryA) + " and " + addrstr(territoryB) + ", establishment canceled"); 1093 + return; 1094 + } 1095 + 1096 + if (territoryA is null || territoryB is null) { 1097 + if (log) 1098 + ai.print("invalid territory for pending route, establishment canceled"); 1099 + return; 1100 + } 1101 + pendingRoutes.insertLast(TradeRoute(territoryA, territoryB)); 1102 + if (log) 1103 + ai.print("establishing trade route between " + addrstr(territoryA) + " and " + addrstr(territoryB)); 1104 + } 1105 + 1106 + SystemOrder@ requestOrbital(Region@ region, const OrbitalModule@ module, double priority = 1.0, double expires = INFINITY, uint moneyType = BT_Infrastructure) { 1107 + SystemAI@ sysAI = systems.getAI(region); 1108 + if (sysAI !is null) { 1109 + for (uint i = 0, cnt = checkedOwnedSystems.length; i < cnt; ++i) { 1110 + if (sysAI is checkedOwnedSystems[i].ai) 1111 + return checkedOwnedSystems[i].buildInSystem(this, module, priority, false, expires, moneyType); 1112 + } 1113 + ai.print("ERROR: requestOrbital: owned system not found: " + region.name); 1114 + return null; 1115 + } 1116 + return null; 1117 + } 1118 + 1119 + bool shouldHaveOutpost(SystemCheck& sys, SystemArea area, SystemBuildLocation&out loc) const { 1120 + loc = BL_InSystem; 1121 + 1122 + uint presentMask = sys.ai.seenPresent; 1123 + //Make sure we did not previously built an outpost here 1124 + if (orbitals.haveInSystem(ai.defs.TradeOutpost, sys.ai.obj)) 1125 + return false; 1126 + //Make sure we are not already building an outpost here 1127 + if (isBuilding(sys, ai.defs.TradeOutpost)) 1128 + return false; 1129 + //Hostile systems should be ignored until cleared 1130 + if (presentMask & ai.enemyMask != 0) 1131 + return false; 1132 + //Inhabited systems should be ignored if we're not aggressively expanding 1133 + if(!ai.behavior.colonizeNeutralOwnedSystems && (presentMask & ai.neutralMask) != 0) 1134 + return false; 1135 + if(!ai.behavior.colonizeAllySystems && (presentMask & ai.allyMask) != 0) 1136 + return false; 1137 + else { 1138 + Planet@ planet; 1139 + ResourceType@ type; 1140 + 1141 + switch(area) { 1142 + //Owned systems should have an outpost 1143 + case SA_Core: 1144 + if (sys.ai.planets.length > 0) 1145 + loc = BL_AtBestPlanet; 1146 + return true; 1147 + //Outside systems might have an outpost if they are of some interest 1148 + case SA_Tradable: 1149 + @planet = getBestPlanet(sys, type); 1150 + if (planet is null) 1151 + break; 1152 + loc = BL_AtBestPlanet; 1153 + //The best planet is barren, the system needs an outpost to allow expansion 1154 + if (int(planet.primaryResourceType) == -1) 1155 + return true; 1156 + //The best planet has either a scalable or level 3 or 2 resource, the system should have an outpost to dissuade other empires from colonizing it 1157 + if (type !is null && (type.cls is scalableClass || type.level == 3 || type.level == 2)) 1158 + return true; 1159 + return false; 1160 + default: 1161 + return false; 1162 + } 1163 + } 1164 + return false; 1165 + } 1166 + 1167 + bool shouldHaveMoonBase(PlanetCheck& pl) const { 1168 + if (pl.ai.obj.moonCount == 0) 1169 + return false; 1170 + //If the planet is at least level 2 and short on empty developed tiles, it should have a moon base 1171 + else if (pl.ai.obj.resourceLevel > 1 && pl.ai.obj.emptyDevelopedTiles < 9) 1172 + return true; 1173 + 1174 + return false; 1175 + } 1176 + 1177 + Region@ getRouteEndpoint(Territory@ territory) { 1178 + const OrbitalModule@ module = ai.defs.TradeStation; 1179 + Region@ region = null; 1180 + for (uint i = 0, cnt = module.ai.length; i < cnt; ++i) { 1181 + auto@ hook = cast<RegisterForTradeUse>(module.ai[i]); 1182 + if (hook !is null) { 1183 + Object@ obj = hook.considerBuild(orbitals, module, territory); 1184 + if (obj !is null) { 1185 + @region = cast<Region>(obj); 1186 + break; 1187 + } 1188 + } 1189 + } 1190 + return region; 1191 + } 1192 + 1193 + Region@ getLaborAt(Territory@ territory, double&out expires) { 1194 + expires = 600.0; 1195 + 1196 + if (territory is null) { 1197 + if (log) 1198 + ai.print("invalid territory to get labor at"); 1199 + return null; 1200 + } 1201 + //SoI - TODO: Handle more complex cases 1202 + 1203 + //Fallback solution: build a labor generation building 1204 + Planet@ pl = development.getLaborAt(territory, expires); 1205 + if (pl !is null) 1206 + return pl.region; 1207 + return null; 1208 + } 1209 + 1210 + bool isBuilding(const OrbitalModule@ module) { 1211 + for (uint i = 0, cnt = SystemCheck::allOrders.length; i < cnt; ++i) { 1212 + auto@ orbital = cast<IOrbitalConstruction>(SystemCheck::allOrders[i].info); 1213 + if (orbital !is null) { 1214 + if (orbital.module is module) 1215 + return true; 1216 + } 1217 + } 1218 + return false; 1219 + } 1220 + 1221 + bool isBuilding(SystemCheck& sys, const OrbitalModule@ module) { 1222 + for (uint i = 0, cnt = sys.orders.length; i < cnt; ++i) { 1223 + auto@ orbital = cast<IOrbitalConstruction>(sys.orders[i].info); 1224 + if (orbital !is null) { 1225 + if (orbital.module is module) 1226 + return true; 1227 + } 1228 + } 1229 + return false; 1230 + } 1231 + 1232 + bool isBuilding(const ConstructionType@ consType) { 1233 + for (uint i = 0, cnt = PlanetCheck::allOrders.length; i < cnt; ++i) { 1234 + auto@ generic = cast<IGenericConstruction>(PlanetCheck::allOrders[i].info); 1235 + if (generic !is null) { 1236 + if (generic.construction is consType) 1237 + return true; 1238 + } 1239 + } 1240 + return false; 1241 + } 1242 + 1243 + bool isBuilding(PlanetCheck& pl, const ConstructionType@ consType) { 1244 + for (uint i = 0, cnt = pl.orders.length; i < cnt; ++i) { 1245 + auto@ generic = cast<IGenericConstruction>(pl.orders[i].info); 1246 + if (generic !is null) { 1247 + if (generic.construction is consType) 1248 + return true; 1249 + } 1250 + } 1251 + return false; 1252 + } 1253 + 1254 + bool hasPendingTradeRoute(Territory@ territoryA, Territory@ territoryB) { 1255 + for (uint i = 0, cnt = pendingRoutes.length; i < cnt; ++i) { 1256 + if (pendingRoutes[i].territoryA is territoryA && pendingRoutes[i].territoryB is territoryB) 1257 + return true; 1258 + } 1259 + return false; 1260 + } 1261 + 1262 + Planet@ getBestPlanet(SystemCheck sys) { 1263 + ResourceType@ type; 1264 + return getBestPlanet(sys, type); 1265 + } 1266 + 1267 + Planet@ getBestPlanet(SystemCheck sys, const ResourceType@ resourceType) { 1268 + Planet@ bestPlanet, planet; 1269 + ResourcePreference bestResource = RP_None; 1270 + 1271 + if (sys.ai.obj is ai.empire.HomeSystem) { 1272 + //The homeworld if there is one 1273 + @planet = ai.empire.Homeworld; 1274 + if (planet !is null) 1275 + return planet; 1276 + } 1277 + 1278 + for (uint i = 0, cnt = sys.ai.planets.length; i < cnt; ++i) { 1279 + @planet = sys.ai.planets[i]; 1280 + int resId = planet.primaryResourceType; 1281 + if (resId == -1) 1282 + continue; 1283 + 1284 + const ResourceType@ type = getResource(resId); 1285 + //The first scalable resource 1286 + if (type.cls is scalableClass) { 1287 + @resourceType = type; 1288 + return planet; 1289 + } 1290 + //The first level 3 resource 1291 + if (type.level == 3) { 1292 + bestResource = RP_Level3; 1293 + @resourceType = type; 1294 + @bestPlanet = planet; 1295 + } 1296 + //The first level 2 resource 1297 + else if (type.level == 2 && RP_Level2 > bestResource) { 1298 + bestResource = RP_Level2; 1299 + @resourceType = type; 1300 + @bestPlanet = planet; 1301 + } 1302 + //The first level 1 resource 1303 + else if (type.level == 1 && RP_Level1 > bestResource) { 1304 + bestResource = RP_Level1; 1305 + @resourceType = type; 1306 + @bestPlanet = planet; 1307 + } 1308 + //The first level 0 resource except food and water 1309 + else if (type.level == 0 && type.cls !is foodClass && type.cls !is waterClass && RP_Level0 > bestResource) { 1310 + bestResource = RP_Level0; 1311 + @resourceType = type; 1312 + @bestPlanet = planet; 1313 + } 1314 + //The first food or water resource 1315 + else if ((type.cls is foodClass || type.cls is waterClass) && RP_Level0 > bestResource) { 1316 + bestResource = RP_FoodWater; 1317 + @resourceType = type; 1318 + @bestPlanet = planet; 1319 + } 1320 + else if (i == sys.ai.planets.length - 1 && bestPlanet is null) { 1321 + @resourceType = type; 1322 + @bestPlanet = planet; 1323 + } 1324 + } 1325 + 1326 + if (bestPlanet is null) 1327 + return planet; 1328 + return bestPlanet; 1329 + } 1330 +}; 1331 + 1332 +AIComponent@ createInfrastructure() { 1333 + return Infrastructure(); 1334 +}
Added scripts/server/empire_ai/weasel/Intelligence.as.
1 +// Intelligence 2 +// ------------ 3 +// Keeps track of the existence and movement of enemy fleets and other assets. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 +import empire_ai.weasel.Fleets; 8 +import empire_ai.weasel.Systems; 9 + 10 +import regions.regions; 11 + 12 +final class FleetIntel { 13 + Object@ obj; 14 + bool known = false; 15 + bool visible = false; 16 + double lastSeen = 0; 17 + double seenStrength = 0; 18 + double predictStrength = 0; 19 + 20 + vec3d seenPosition; 21 + Region@ seenRegion; 22 + vec3d seenDestination; 23 + Region@ seenTarget; 24 + 25 + void save(SaveFile& file) { 26 + file << obj; 27 + file << known; 28 + file << visible; 29 + file << lastSeen; 30 + file << seenStrength; 31 + file << predictStrength; 32 + file << seenPosition; 33 + file << seenRegion; 34 + file << seenDestination; 35 + file << seenTarget; 36 + } 37 + 38 + void load(SaveFile& file) { 39 + file >> obj; 40 + file >> known; 41 + file >> visible; 42 + file >> lastSeen; 43 + file >> seenStrength; 44 + file >> predictStrength; 45 + file >> seenPosition; 46 + file >> seenRegion; 47 + file >> seenDestination; 48 + file >> seenTarget; 49 + } 50 + 51 + bool get_isSignificant() { 52 + return obj.getFleetStrength() > 0.1; 53 + } 54 + 55 + bool tick(AI& ai, Intelligence& intelligence, Intel& intel) { 56 + if(visible) { 57 + if(!obj.valid || obj.owner !is intel.empire) 58 + return false; 59 + } 60 + else { 61 + if(!obj.valid || obj.owner !is intel.empire) { 62 + if(!known || lastSeen < gameTime - 300.0) 63 + return false; 64 + } 65 + } 66 + if(obj.isVisibleTo(ai.empire)) { 67 + known = true; 68 + visible = true; 69 + lastSeen = gameTime; 70 + 71 + seenStrength = obj.getFleetStrength(); 72 + predictStrength = obj.getFleetMaxStrength(); 73 + int supCap = obj.SupplyCapacity; 74 + double fillPct = 1.0; 75 + if(supCap != 0) { 76 + double fillPct = double(obj.SupplyUsed) / double(supCap); 77 + if(fillPct > 0.5) 78 + predictStrength /= fillPct; 79 + else 80 + predictStrength *= 2.0; 81 + } 82 + 83 + seenPosition = obj.position; 84 + @seenRegion = obj.region; 85 + 86 + if(obj.isMoving) { 87 + seenDestination = obj.computedDestination; 88 + if(seenRegion !is null && inRegion(seenRegion, seenDestination)) 89 + @seenTarget = seenRegion; 90 + else if(seenTarget !is null && inRegion(seenTarget, seenDestination)) 91 + @seenTarget = seenTarget; 92 + else 93 + @seenTarget = getRegion(seenDestination); 94 + } 95 + else { 96 + seenDestination = seenPosition; 97 + @seenTarget = seenRegion; 98 + } 99 + } 100 + else { 101 + visible = false; 102 + } 103 + return true; 104 + } 105 +}; 106 + 107 +final class Intel { 108 + Empire@ empire; 109 + uint borderedTo = 0; 110 + 111 + array<FleetIntel@> fleets; 112 + array<SystemAI@> shared; 113 + array<SystemAI@> theirBorder; 114 + array<SystemAI@> theirOwned; 115 + 116 + void save(Intelligence& intelligence, SaveFile& file) { 117 + uint cnt = fleets.length; 118 + file << cnt; 119 + for(uint i = 0; i < cnt; ++i) 120 + fleets[i].save(file); 121 + 122 + cnt = shared.length; 123 + file << cnt; 124 + for(uint i = 0; i < cnt; ++i) 125 + intelligence.systems.saveAI(file, shared[i]); 126 + 127 + cnt = theirBorder.length; 128 + file << cnt; 129 + for(uint i = 0; i < cnt; ++i) 130 + intelligence.systems.saveAI(file, theirBorder[i]); 131 + 132 + cnt = theirOwned.length; 133 + file << cnt; 134 + for(uint i = 0; i < cnt; ++i) 135 + intelligence.systems.saveAI(file, theirOwned[i]); 136 + 137 + file << borderedTo; 138 + } 139 + 140 + void load(Intelligence& intelligence, SaveFile& file) { 141 + uint cnt = 0; 142 + 143 + file >> cnt; 144 + for(uint i = 0; i < cnt; ++i) { 145 + FleetIntel flIntel; 146 + flIntel.load(file); 147 + if(flIntel.obj !is null) 148 + fleets.insertLast(flIntel); 149 + } 150 + 151 + file >> cnt; 152 + for(uint i = 0; i < cnt; ++i) { 153 + auto@ sys = intelligence.systems.loadAI(file); 154 + if(sys !is null) 155 + shared.insertLast(sys); 156 + } 157 + 158 + file >> cnt; 159 + for(uint i = 0; i < cnt; ++i) { 160 + auto@ sys = intelligence.systems.loadAI(file); 161 + if(sys !is null) 162 + theirBorder.insertLast(sys); 163 + } 164 + 165 + file >> cnt; 166 + for(uint i = 0; i < cnt; ++i) { 167 + auto@ sys = intelligence.systems.loadAI(file); 168 + if(sys !is null) 169 + theirOwned.insertLast(sys); 170 + } 171 + 172 + file >> borderedTo; 173 + } 174 + 175 + //TODO: If a fleet is going to drop out of cutoff range soon, 176 + // queue up a scouting mission to its last known position so we 177 + // can try to regain intel on it. 178 + 179 + double getSeenStrength(double cutOff = 600.0) { 180 + double total = 0.0; 181 + cutOff = gameTime - cutOff; 182 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 183 + auto@ flIntel = fleets[i]; 184 + if(!flIntel.known) 185 + continue; 186 + if(flIntel.lastSeen < cutOff) 187 + continue; 188 + total += sqrt(fleets[i].seenStrength); 189 + } 190 + return total * total; 191 + } 192 + 193 + double getPredictiveStrength(double cutOff = 600.0) { 194 + double total = 0.0; 195 + cutOff = gameTime - cutOff; 196 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 197 + auto@ flIntel = fleets[i]; 198 + if(!flIntel.known) 199 + continue; 200 + if(flIntel.lastSeen < cutOff) 201 + continue; 202 + total += sqrt(fleets[i].predictStrength); 203 + } 204 + return total * total; 205 + } 206 + 207 + double accuracy(AI& ai, Intelligence& intelligence, double cutOff = 600.0) { 208 + uint total = 0; 209 + uint known = 0; 210 + 211 + cutOff = gameTime - cutOff; 212 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 213 + auto@ flIntel = fleets[i]; 214 + if(!flIntel.isSignificant) 215 + continue; 216 + 217 + total += 1; 218 + if(flIntel.known && flIntel.lastSeen >= cutOff) 219 + known += 1; 220 + } 221 + 222 + if(total == 0) 223 + return 1.0; 224 + return double(known) / double(total); 225 + } 226 + 227 + double defeatability(AI& ai, Intelligence& intelligence, double cutOff = 600.0) { 228 + double acc = accuracy(ai, intelligence, cutOff); 229 + double ourStrength = 0, theirStrength = 0; 230 + 231 + if(acc < 0.6) { 232 + //In low-accuracy situations, base it on the empire overall strength metric 233 + theirStrength = empire.TotalMilitary; 234 + ourStrength = ai.empire.TotalMilitary; 235 + } 236 + else { 237 + theirStrength = getPredictiveStrength(cutOff * 10.0); 238 + ourStrength = intelligence.fleets.totalStrength; 239 + } 240 + 241 + if(theirStrength == 0) 242 + return 10.0; 243 + return ourStrength / theirStrength; 244 + } 245 + 246 + void tick(AI& ai, Intelligence& intelligence) { 247 + //Keep known fleets updated 248 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 249 + if(!fleets[i].tick(ai, intelligence, this)) { 250 + fleets.removeAt(i); 251 + --i; --cnt; 252 + } 253 + } 254 + } 255 + 256 + bool isShared(AI& ai, SystemAI@ sys) { 257 + return sys.seenPresent & ai.empire.mask != 0 && sys.seenPresent & empire.mask != 0; 258 + } 259 + 260 + bool isBorder(AI& ai, SystemAI@ sys) { 261 + return sys.outsideBorder && sys.seenPresent & empire.mask != 0; 262 + } 263 + 264 + void focusTick(AI& ai, Intelligence& intelligence) { 265 + //Detect newly created fleets 266 + auto@ data = empire.getFlagships(); 267 + Object@ obj; 268 + while(receive(data, obj)) { 269 + if(obj is null) 270 + continue; 271 + 272 + bool found = false; 273 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 274 + if(fleets[i].obj is obj) { 275 + found = true; 276 + break; 277 + } 278 + } 279 + 280 + if(!found) { 281 + FleetIntel flIntel; 282 + @flIntel.obj = obj; 283 + fleets.insertLast(flIntel); 284 + } 285 + } 286 + 287 + //Remove no longer shared and border systems 288 + for(uint i = 0, cnt = shared.length; i < cnt; ++i) { 289 + if(!isShared(ai, shared[i])) { 290 + shared.removeAt(i); 291 + --i; --cnt; 292 + } 293 + } 294 + for(uint i = 0, cnt = theirBorder.length; i < cnt; ++i) { 295 + if(!isBorder(ai, theirBorder[i])) { 296 + theirBorder.removeAt(i); 297 + --i; --cnt; 298 + } 299 + } 300 + 301 + borderedTo = 0; 302 + for(uint i = 0, cnt = theirOwned.length; i < cnt; ++i) { 303 + auto@ sys = theirOwned[i]; 304 + uint seen = sys.seenPresent; 305 + if(seen & empire.mask == 0) { 306 + theirOwned.removeAt(i); 307 + --i; --cnt; 308 + continue; 309 + } 310 + 311 + for(uint n = 0, ncnt = sys.desc.adjacent.length; n < ncnt; ++n) { 312 + auto@ other = intelligence.systems.getAI(sys.desc.adjacent[n]); 313 + if(other !is null) 314 + borderedTo |= other.seenPresent & ~empire.mask; 315 + } 316 + 317 + for(uint n = 0, ncnt = sys.desc.wormholes.length; n < ncnt; ++n) { 318 + auto@ other = intelligence.systems.getAI(sys.desc.wormholes[n]); 319 + if(other !is null) 320 + borderedTo |= other.seenPresent & ~empire.mask; 321 + } 322 + } 323 + 324 + //Detect shared and border systems 325 + for(uint i = 0, cnt = intelligence.systems.owned.length; i < cnt; ++i) { 326 + auto@ sys = intelligence.systems.owned[i]; 327 + if(isShared(ai, sys)) { 328 + if(shared.find(sys) == -1) 329 + shared.insertLast(sys); 330 + } 331 + } 332 + for(uint i = 0, cnt = intelligence.systems.outsideBorder.length; i < cnt; ++i) { 333 + auto@ sys = intelligence.systems.outsideBorder[i]; 334 + if(isBorder(ai, sys)) { 335 + if(theirBorder.find(sys) == -1) 336 + theirBorder.insertLast(sys); 337 + } 338 + } 339 + for(uint i = 0, cnt = intelligence.systems.all.length; i < cnt; ++i) { 340 + auto@ sys = intelligence.systems.all[i]; 341 + if(sys.seenPresent & empire.mask != 0) { 342 + if(theirOwned.find(sys) == -1) 343 + theirOwned.insertLast(sys); 344 + } 345 + } 346 + 347 + //Try to update some stuff 348 + SystemAI@ check; 349 + double lru = 0; 350 + 351 + for(uint i = 0, cnt = shared.length; i < cnt; ++i) { 352 + auto@ sys = shared[i]; 353 + double update = sys.lastStrengthCheck; 354 + if(update < lru && sys.visible) { 355 + @check = sys; 356 + lru = update; 357 + } 358 + } 359 + 360 + for(uint i = 0, cnt = theirBorder.length; i < cnt; ++i) { 361 + auto@ sys = theirBorder[i]; 362 + double update = sys.lastStrengthCheck; 363 + if(update < lru && sys.visible) { 364 + @check = sys; 365 + lru = update; 366 + } 367 + } 368 + 369 + if(check !is null) 370 + check.strengthCheck(ai); 371 + } 372 +}; 373 + 374 +class Intelligence : AIComponent { 375 + Fleets@ fleets; 376 + Systems@ systems; 377 + 378 + array<Intel@> intel; 379 + 380 + void create() { 381 + @fleets = cast<Fleets>(ai.fleets); 382 + @systems = cast<Systems>(ai.systems); 383 + } 384 + 385 + void start() { 386 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 387 + Empire@ emp = getEmpire(i); 388 + if(emp is ai.empire) 389 + continue; 390 + if(!emp.major) 391 + continue; 392 + 393 + Intel empIntel; 394 + @empIntel.empire = emp; 395 + 396 + if(intel.length <= uint(emp.index)) 397 + intel.length = uint(emp.index)+1; 398 + @intel[emp.index] = empIntel; 399 + } 400 + } 401 + 402 + void save(SaveFile& file) { 403 + uint cnt = intel.length; 404 + file << cnt; 405 + for(uint i = 0; i < cnt; ++i) { 406 + if(intel[i] is null) { 407 + file.write0(); 408 + continue; 409 + } 410 + 411 + file.write1(); 412 + intel[i].save(this, file); 413 + } 414 + } 415 + 416 + void load(SaveFile& file) { 417 + uint cnt = 0; 418 + file >> cnt; 419 + intel.length = cnt; 420 + 421 + for(uint i = 0; i < cnt; ++i) { 422 + if(!file.readBit()) 423 + continue; 424 + 425 + @intel[i] = Intel(); 426 + @intel[i].empire = getEmpire(i); 427 + intel[i].load(this, file); 428 + } 429 + } 430 + 431 + Intel@ get(Empire@ emp) { 432 + if(emp is null) 433 + return null; 434 + if(!emp.major) 435 + return null; 436 + if(uint(emp.index) >= intel.length) 437 + return null; 438 + return intel[emp.index]; 439 + } 440 + 441 + uint ind = 0; 442 + void tick(double time) override { 443 + if(intel.length == 0) 444 + return; 445 + ind = (ind+1)%intel.length; 446 + if(intel[ind] !is null) 447 + intel[ind].tick(ai, this); 448 + } 449 + 450 + uint fInd = 0; 451 + void focusTick(double time) override { 452 + if(intel.length == 0) 453 + return; 454 + fInd = (fInd+1)%intel.length; 455 + if(intel[fInd] !is null) 456 + intel[fInd].focusTick(ai, this); 457 + } 458 + 459 + string strdisplay(double str) { 460 + return standardize(str * 0.001, true); 461 + } 462 + 463 + double defeatability(Empire@ emp) { 464 + auto@ empIntel = get(emp); 465 + if(empIntel is null) 466 + return 0.0; 467 + return empIntel.defeatability(ai, this); 468 + } 469 + 470 + double defeatability(uint theirMask, uint myMask = 0, double cutOff = 600.0) { 471 + if(myMask == 0) 472 + myMask = ai.empire.mask; 473 + 474 + double minAcc = 1.0; 475 + for(uint i = 0, cnt = intel.length; i < cnt; ++i) { 476 + auto@ itl = intel[i]; 477 + if(itl is null || itl.empire is null) 478 + continue; 479 + if((theirMask | myMask) & itl.empire.mask == 0) 480 + continue; 481 + minAcc = min(itl.accuracy(ai, this, cutOff), minAcc); 482 + } 483 + 484 + double ourStrength = 0, theirStrength = 0; 485 + for(uint i = 0, cnt = intel.length; i < cnt; ++i) { 486 + auto@ itl = intel[i]; 487 + if(itl is null || itl.empire is null) 488 + continue; 489 + if((theirMask | myMask) & itl.empire.mask == 0) 490 + continue; 491 + 492 + double str = 0.0; 493 + if(minAcc < 0.6) 494 + str = itl.empire.TotalMilitary; 495 + else 496 + str = itl.getPredictiveStrength(cutOff * 10.0); 497 + 498 + if(itl.empire.mask & theirMask != 0) 499 + theirStrength += str; 500 + if(itl.empire.mask & myMask != 0) 501 + ourStrength += str; 502 + } 503 + 504 + if(myMask & ai.empire.mask != 0) { 505 + if(minAcc < 0.6) 506 + ourStrength += ai.empire.TotalMilitary; 507 + else 508 + ourStrength += fleets.totalStrength; 509 + } 510 + if(theirMask & ai.empire.mask != 0) { 511 + if(minAcc < 0.6) 512 + theirStrength += ai.empire.TotalMilitary; 513 + else 514 + theirStrength += fleets.totalStrength; 515 + } 516 + 517 + if(theirStrength == 0) 518 + return 10.0; 519 + return ourStrength / theirStrength; 520 + } 521 + 522 + void turn() override { 523 + if(log) { 524 + ai.print("Intelligence Report on Empires:"); 525 + ai.print(ai.pad(" Our strength: ", 18)+strdisplay(fleets.totalStrength)+" / "+strdisplay(fleets.totalMaxStrength)); 526 + for(uint i = 0, cnt = intel.length; i < cnt; ++i) { 527 + auto@ empIntel = intel[i]; 528 + if(empIntel is null) 529 + continue; 530 + ai.print(" "+ai.pad(empIntel.empire.name, 16) 531 + +ai.pad(" "+strdisplay(empIntel.getSeenStrength()) 532 + +" / "+strdisplay(empIntel.getPredictiveStrength()), 20) 533 + +" defeatability "+toString(empIntel.defeatability(ai, this), 2) 534 + +" accuracy "+toString(empIntel.accuracy(ai, this), 2)); 535 + } 536 + } 537 + } 538 +}; 539 + 540 +AIComponent@ createIntelligence() { 541 + return Intelligence(); 542 +}
Added scripts/server/empire_ai/weasel/Military.as.
1 +// Military 2 +// -------- 3 +// Military construction logic. Builds and restores fleets and defensive stations, 4 +// but does not deal with actually using those fleets to fight - that is the purview of 5 +// the War component. 6 +// 7 + 8 +import empire_ai.weasel.WeaselAI; 9 + 10 +import empire_ai.weasel.Designs; 11 +import empire_ai.weasel.Construction; 12 +import empire_ai.weasel.Budget; 13 +import empire_ai.weasel.Fleets; 14 +import empire_ai.weasel.Development; 15 +import empire_ai.weasel.Movement; 16 +import empire_ai.weasel.Systems; 17 +import empire_ai.weasel.Orbitals; 18 + 19 +import resources; 20 + 21 +class SupportOrder { 22 + DesignTarget@ design; 23 + Object@ onObject; 24 + AllocateBudget@ alloc; 25 + bool isGhostOrder = false; 26 + double expires = INFINITY; 27 + uint count = 0; 28 + 29 + void save(Military& military, SaveFile& file) { 30 + military.designs.saveDesign(file, design); 31 + file << onObject; 32 + military.budget.saveAlloc(file, alloc); 33 + file << isGhostOrder; 34 + file << expires; 35 + file << count; 36 + } 37 + 38 + void load(Military& military, SaveFile& file) { 39 + @design = military.designs.loadDesign(file); 40 + file >> onObject; 41 + @alloc = military.budget.loadAlloc(file); 42 + file >> isGhostOrder; 43 + file >> expires; 44 + file >> count; 45 + } 46 + 47 + bool tick(AI& ai, Military& military, double time) { 48 + if(alloc !is null) { 49 + if(alloc.allocated) { 50 + if(isGhostOrder) 51 + onObject.rebuildAllGhosts(); 52 + else 53 + onObject.orderSupports(design.active.mostUpdated(), count); 54 + if(military.log && design !is null) 55 + ai.print("Support order completed for "+count+"x "+design.active.name+" ("+design.active.size+")", onObject); 56 + return false; 57 + } 58 + } 59 + else if(design !is null) { 60 + if(design.active !is null) 61 + @alloc = military.budget.allocate(BT_Military, getBuildCost(design.active.mostUpdated()) * count); 62 + } 63 + if(expires < gameTime) { 64 + if(alloc !is null && !alloc.allocated) 65 + military.budget.remove(alloc); 66 + if(isGhostOrder) 67 + onObject.clearAllGhosts(); 68 + if(military.log) 69 + ai.print("Support order expired", onObject); 70 + return false; 71 + } 72 + return true; 73 + } 74 +}; 75 + 76 +class StagingBase { 77 + Region@ region; 78 + array<FleetAI@> fleets; 79 + 80 + double idleTime = 0.0; 81 + double occupiedTime = 0.0; 82 + 83 + OrbitalAI@ shipyard; 84 + BuildOrbital@ shipyardBuild; 85 + Factory@ factory; 86 + 87 + bool isUnderAttack = false; 88 + 89 + void save(Military& military, SaveFile& file) { 90 + file << region; 91 + file << idleTime; 92 + file << occupiedTime; 93 + file << isUnderAttack; 94 + 95 + military.orbitals.saveAI(file, shipyard); 96 + military.construction.saveConstruction(file, shipyardBuild); 97 + 98 + uint cnt = fleets.length; 99 + file << cnt; 100 + for(uint i = 0; i < cnt; ++i) 101 + military.fleets.saveAI(file, fleets[i]); 102 + } 103 + 104 + void load(Military& military, SaveFile& file) { 105 + file >> region; 106 + file >> idleTime; 107 + file >> occupiedTime; 108 + file >> isUnderAttack; 109 + 110 + @shipyard = military.orbitals.loadAI(file); 111 + @shipyardBuild = cast<BuildOrbital>(military.construction.loadConstruction(file)); 112 + 113 + uint cnt = 0; 114 + file >> cnt; 115 + for(uint i = 0; i < cnt; ++i) { 116 + if(i > 200 && file < SV_0158) { 117 + //Something went preeeetty wrong in an old save 118 + if(file.readBit()) { 119 + Object@ obj; 120 + file >> obj; 121 + } 122 + } 123 + else { 124 + auto@ fleet = military.fleets.loadAI(file); 125 + if(fleet !is null) 126 + fleets.insertLast(fleet); 127 + } 128 + } 129 + } 130 + 131 + bool tick(AI& ai, Military& military, double time) { 132 + if(fleets.length == 0) { 133 + occupiedTime = 0.0; 134 + idleTime += time; 135 + } 136 + else { 137 + occupiedTime += time; 138 + idleTime = 0.0; 139 + } 140 + 141 + isUnderAttack = region.ContestedMask & ai.mask != 0; 142 + 143 + //Manage building our shipyard 144 + if(shipyardBuild !is null) { 145 + if(shipyardBuild.completed) { 146 + @shipyard = military.orbitals.getInSystem(ai.defs.Shipyard, region); 147 + if(shipyard !is null) 148 + @shipyardBuild = null; 149 + } 150 + } 151 + if(shipyard !is null) { 152 + if(!shipyard.obj.valid) { 153 + @shipyard = null; 154 + @shipyardBuild = null; 155 + } 156 + } 157 + 158 + if(factory !is null && (!factory.valid || factory.obj.region !is region)) 159 + @factory = null; 160 + if(factory is null) 161 + @factory = military.construction.getFactory(region); 162 + 163 + if(factory !is null) { 164 + factory.needsSupportLabor = false; 165 + factory.waitingSupportLabor = 0.0; 166 + if(factory.obj.hasOrderedSupports) { 167 + factory.needsSupportLabor = true; 168 + factory.waitingSupportLabor += double(factory.obj.SupplyOrdered) * ai.behavior.estSizeSupportLabor; 169 + } 170 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 171 + if(fleets[i].isHome && fleets[i].obj.hasOrderedSupports) { 172 + factory.needsSupportLabor = true; 173 + factory.waitingSupportLabor += double(fleets[i].obj.SupplyOrdered) * ai.behavior.estSizeSupportLabor; 174 + break; 175 + } 176 + } 177 + if(factory.waitingSupportLabor > 0) 178 + factory.aimForLabor(factory.waitingSupportLabor / ai.behavior.constructionMaxTime); 179 + } 180 + 181 + bool isFactorySufficient = false; 182 + if(factory !is null) { 183 + if(factory.waitingSupportLabor <= factory.laborIncome * ai.behavior.constructionMaxTime 184 + || factory.obj.canImportLabor || factory !is military.construction.primaryFactory) 185 + isFactorySufficient = true; 186 + } 187 + 188 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 189 + Object@ obj = fleets[i].obj; 190 + if(obj is null || !obj.valid) { 191 + fleets.removeAt(i); 192 + --i; --cnt; 193 + continue; 194 + } 195 + fleets[i].stationedFactory = isFactorySufficient; 196 + } 197 + 198 + if(occupiedTime >= 3.0 * 60.0 && ai.defs.Shipyard !is null && shipyard is null && shipyardBuild is null 199 + && !isUnderAttack && (!isFactorySufficient && factory !is military.construction.primaryFactory)) { 200 + //If any fleets need construction try to queue up a shipyard 201 + bool needYard = false; 202 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 203 + auto@ flt = fleets[i]; 204 + if(flt.obj.hasOrderedSupports || flt.filled < 0.8) { 205 + needYard = true; 206 + break; 207 + } 208 + } 209 + 210 + if(needYard) { 211 + @shipyard = military.orbitals.getInSystem(ai.defs.Shipyard, region); 212 + if(shipyard is null) { 213 + vec3d pos = region.position; 214 + vec2d offset = random2d(region.radius * 0.4, region.radius * 0.8); 215 + pos.x += offset.x; 216 + pos.z += offset.y; 217 + 218 + @shipyardBuild = military.construction.buildOrbital(ai.defs.Shipyard, pos); 219 + } 220 + } 221 + } 222 + 223 + if((idleTime >= 10.0 * 60.0 || region.PlanetsMask & ai.mask == 0) && (shipyardBuild is null || shipyard !is null) && (factory is null || (shipyard !is null && factory.obj is shipyard.obj)) && military.stagingBases.length >= 2) { 224 + if(shipyard !is null && !ai.behavior.forbidScuttle) { 225 + cast<Orbital>(shipyard.obj).scuttle(); 226 + } 227 + else { 228 + if(factory !is null) { 229 + factory.needsSupportLabor = false; 230 + @factory = null; 231 + } 232 + return false; 233 + } 234 + } 235 + return true; 236 + } 237 +}; 238 + 239 +class Military : AIComponent { 240 + Fleets@ fleets; 241 + Development@ development; 242 + Designs@ designs; 243 + Construction@ construction; 244 + Budget@ budget; 245 + Systems@ systems; 246 + Orbitals@ orbitals; 247 + 248 + array<SupportOrder@> supportOrders; 249 + array<StagingBase@> stagingBases; 250 + 251 + AllocateConstruction@ mainWait; 252 + bool spentMoney = true; 253 + 254 + void create() { 255 + @fleets = cast<Fleets>(ai.fleets); 256 + @development = cast<Development>(ai.development); 257 + @designs = cast<Designs>(ai.designs); 258 + @construction = cast<Construction>(ai.construction); 259 + @budget = cast<Budget>(ai.budget); 260 + @systems = cast<Systems>(ai.systems); 261 + @orbitals = cast<Orbitals>(ai.orbitals); 262 + } 263 + 264 + void save(SaveFile& file) { 265 + uint cnt = supportOrders.length; 266 + file << cnt; 267 + for(uint i = 0; i < cnt; ++i) 268 + supportOrders[i].save(this, file); 269 + 270 + construction.saveConstruction(file, mainWait); 271 + file << spentMoney; 272 + 273 + cnt = stagingBases.length; 274 + file << cnt; 275 + for(uint i = 0; i < cnt; ++i) { 276 + saveStaging(file, stagingBases[i]); 277 + stagingBases[i].save(this, file); 278 + } 279 + } 280 + 281 + void load(SaveFile& file) { 282 + uint cnt = 0; 283 + file >> cnt; 284 + for(uint i = 0; i < cnt; ++i) { 285 + SupportOrder ord; 286 + ord.load(this, file); 287 + if(ord.onObject !is null) 288 + supportOrders.insertLast(ord); 289 + } 290 + 291 + @mainWait = construction.loadConstruction(file); 292 + file >> spentMoney; 293 + 294 + file >> cnt; 295 + for(uint i = 0; i < cnt; ++i) { 296 + StagingBase@ base = loadStaging(file); 297 + if(base !is null) { 298 + base.load(this, file); 299 + if(stagingBases.find(base) == -1) 300 + stagingBases.insertLast(base); 301 + } 302 + else { 303 + StagingBase().load(this, file); 304 + } 305 + } 306 + } 307 + 308 + void loadFinalize(AI& ai) override { 309 + for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) { 310 + auto@ base = stagingBases[i]; 311 + for(uint n = 0, ncnt = base.fleets.length; n < ncnt; ++n) { 312 + Object@ obj = base.fleets[n].obj; 313 + if(obj is null || !obj.valid || !obj.initialized) { 314 + base.fleets.removeAt(n); 315 + --n; --ncnt; 316 + } 317 + } 318 + } 319 + } 320 + 321 + StagingBase@ loadStaging(SaveFile& file) { 322 + Region@ reg; 323 + file >> reg; 324 + 325 + if(reg is null) 326 + return null; 327 + 328 + StagingBase@ base = getBase(reg); 329 + if(base is null) { 330 + @base = StagingBase(); 331 + @base.region = reg; 332 + stagingBases.insertLast(base); 333 + } 334 + return base; 335 + } 336 + 337 + void saveStaging(SaveFile& file, StagingBase@ base) { 338 + Region@ reg; 339 + if(base !is null) 340 + @reg = base.region; 341 + file << reg; 342 + } 343 + 344 + Region@ getClosestStaging(Region& targetRegion) { 345 + //Check if we have anything close enough 346 + StagingBase@ best; 347 + int minHops = INT_MAX; 348 + for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) { 349 + int d = systems.hopDistance(stagingBases[i].region, targetRegion); 350 + if(d < minHops) { 351 + minHops = d; 352 + @best = stagingBases[i]; 353 + } 354 + } 355 + if(best !is null) 356 + return best.region; 357 + return null; 358 + } 359 + 360 + Region@ getStagingFor(Region& targetRegion) { 361 + //Check if we have anything close enough 362 + StagingBase@ best; 363 + int minHops = INT_MAX; 364 + for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) { 365 + int d = systems.hopDistance(stagingBases[i].region, targetRegion); 366 + if(d < minHops) { 367 + minHops = d; 368 + @best = stagingBases[i]; 369 + } 370 + } 371 + if(minHops < ai.behavior.stagingMaxHops) 372 + return best.region; 373 + 374 + //Create a new staging base for this 375 + Region@ bestNew; 376 + minHops = INT_MAX; 377 + for(uint i = 0, cnt = systems.border.length; i < cnt; ++i) { 378 + auto@ sys = systems.border[i].obj; 379 + int d = systems.hopDistance(sys, targetRegion); 380 + if(d < minHops) { 381 + minHops = d; 382 + @bestNew = sys; 383 + } 384 + } 385 + 386 + if(minHops > ai.behavior.stagingMaxHops && best !is null) 387 + return best.region; 388 + 389 + auto@ base = getBase(bestNew); 390 + if(base !is null) 391 + return base.region; 392 + else 393 + return createStaging(bestNew).region; 394 + } 395 + 396 + StagingBase@ createStaging(Region@ region) { 397 + if(region is null) 398 + return null; 399 + 400 + if(log) 401 + ai.print("Create new staging base.", region); 402 + 403 + StagingBase newBase; 404 + @newBase.region = region; 405 + 406 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 407 + if(fleets.fleets[i].stationed is region) 408 + newBase.fleets.insertLast(fleets.fleets[i]); 409 + } 410 + 411 + stagingBases.insertLast(newBase); 412 + return newBase; 413 + } 414 + 415 + StagingBase@ getBase(Region@ inRegion) { 416 + if(inRegion is null) 417 + return null; 418 + for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) { 419 + if(stagingBases[i].region is inRegion) 420 + return stagingBases[i]; 421 + } 422 + return null; 423 + } 424 + 425 + vec3d getStationPosition(Region& inRegion, double distance = 100.0) { 426 + auto@ base = getBase(inRegion); 427 + if(base !is null) { 428 + if(base.shipyard !is null) { 429 + vec3d pos = base.shipyard.obj.position; 430 + vec2d offset = random2d(distance * 0.5, distance * 1.5); 431 + pos.x += offset.x; 432 + pos.z += offset.y; 433 + 434 + return pos; 435 + } 436 + } 437 + 438 + vec3d pos = inRegion.position; 439 + vec2d offset = random2d(inRegion.radius * 0.4, inRegion.radius * 0.8); 440 + pos.x += offset.x; 441 + pos.z += offset.y; 442 + return pos; 443 + } 444 + 445 + void stationFleet(FleetAI@ fleet, Region@ inRegion) { 446 + if(inRegion is null || fleet.stationed is inRegion) 447 + return; 448 + 449 + auto@ prevBase = getBase(fleet.stationed); 450 + if(prevBase !is null) 451 + prevBase.fleets.remove(fleet); 452 + 453 + auto@ base = getBase(inRegion); 454 + if(base !is null) 455 + base.fleets.insertLast(fleet); 456 + 457 + @fleet.stationed = inRegion; 458 + fleet.stationedFactory = construction.getFactory(inRegion) !is null; 459 + if(fleet.mission is null) 460 + fleets.returnToBase(fleet); 461 + } 462 + 463 + void orderSupportsOn(Object& obj, double expire = 60.0) { 464 + if(obj.SupplyGhost > 0) { 465 + if(ai.behavior.fleetsRebuildGhosts) { 466 + //Try to rebuild the fleet's ghosts 467 + SupportOrder ord; 468 + @ord.onObject = obj; 469 + @ord.alloc = budget.allocate(BT_Military, obj.rebuildGhostsCost()); 470 + ord.expires = gameTime + expire; 471 + ord.isGhostOrder = true; 472 + 473 + supportOrders.insertLast(ord); 474 + 475 + if(log) 476 + ai.print("Attempting to rebuild ghosts", obj); 477 + return; 478 + } 479 + else { 480 + obj.clearAllGhosts(); 481 + } 482 + } 483 + 484 + int supCap = obj.SupplyCapacity; 485 + int supHave = obj.SupplyUsed - obj.SupplyGhost; 486 + 487 + //Build some supports 488 + int supSize = pow(2, round(::log(double(supCap) * randomd(0.005, 0.03))/::log(2.0))); 489 + supSize = max(min(supSize, supCap - supHave), 1); 490 + 491 + SupportOrder ord; 492 + @ord.onObject = obj; 493 + @ord.design = designs.design(DP_Support, supSize); 494 + ord.expires = gameTime + expire; 495 + ord.count = clamp((supCap - supHave)/supSize, 1, int(ceil((randomd(0.01, 0.1)*supCap)/double(supSize)))); 496 + 497 + if(log) 498 + ai.print("Attempting to build supports: "+ord.count+"x size "+supSize, obj); 499 + 500 + supportOrders.insertLast(ord); 501 + } 502 + 503 + void findSomethingToDo() { 504 + //See if we should retrofit anything 505 + if(mainWait is null && !spentMoney && gameTime > ai.behavior.flagshipBuildMinGameTime) { 506 + int availMoney = budget.spendable(BT_Military); 507 + int moneyTargetSize = floor(double(availMoney) * ai.behavior.shipSizePerMoney); 508 + 509 + //See if one of our fleets is old enough that we can retrofit it 510 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 511 + FleetAI@ fleet = fleets.fleets[i]; 512 + if(fleet.mission !is null && fleet.mission.isActive) 513 + continue; 514 + if(fleet.fleetClass != FC_Combat) 515 + continue; 516 + if(fleet.obj.hasOrders) 517 + continue; 518 + 519 + Ship@ ship = cast<Ship>(fleet.obj); 520 + if(ship is null) 521 + continue; 522 + 523 + //Don't retrofit free fleets 524 + if(ship.isFree && !ai.behavior.retrofitFreeFleets) 525 + continue; 526 + 527 + //Find the factory assigned to this 528 + Factory@ factory; 529 + if(fleet.isHome) { 530 + Region@ reg = fleet.obj.region; 531 + @factory = construction.getFactory(reg); 532 + } 533 + if(factory is null) 534 + continue; 535 + if(factory.busy) 536 + continue; 537 + 538 + //Find how large we can make this flagship 539 + const Design@ dsg = ship.blueprint.design; 540 + int targetSize = min(int(moneyTargetSize * 1.2), int(factory.laborToBear(ai) * 1.3 * ai.behavior.shipSizePerLabor)); 541 + targetSize = 5 * floor(double(targetSize) / 5.0); 542 + 543 + //See if we should retrofit this 544 + int size = ship.blueprint.design.size; 545 + if(size > targetSize) 546 + continue; 547 + 548 + double pctDiff = (double(targetSize) / double(size)) - 1.0; 549 + if(pctDiff < ai.behavior.shipRetrofitThreshold) 550 + continue; 551 + 552 + DesignTarget@ newDesign = designs.scale(dsg, targetSize); 553 + spentMoney = true; 554 + 555 + auto@ retrofit = construction.retrofit(ship); 556 + @mainWait = construction.buildNow(retrofit, factory); 557 + 558 + if(log) 559 + ai.print("Retrofitting to size "+targetSize, fleet.obj); 560 + 561 + //TODO: This should mark the fleet as occupied for missions while we retrofit 562 + 563 + return; 564 + } 565 + 566 + //See if we should build a new fleet 567 + Factory@ factory = construction.primaryFactory; 568 + if(factory !is null && !factory.busy) { 569 + //Figure out how large our flagship would be if we built one 570 + factory.aimForLabor((double(moneyTargetSize) / ai.behavior.shipSizePerLabor) / ai.behavior.constructionMaxTime); 571 + int targetSize = min(moneyTargetSize, int(factory.laborToBear(ai) * ai.behavior.shipSizePerLabor)); 572 + targetSize = 5 * floor(double(targetSize) / 5.0); 573 + 574 + int expMaint = double(targetSize) * ai.behavior.maintenancePerShipSize; 575 + int expCost = double(targetSize) / ai.behavior.shipSizePerMoney; 576 + if(budget.canSpend(BT_Military, expCost, expMaint)) { 577 + //Make sure we're building an adequately sized flagship 578 + uint count = 0; 579 + double avgSize = 0.0; 580 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 581 + FleetAI@ fleet = fleets.fleets[i]; 582 + Ship@ ship = cast<Ship>(fleet.obj); 583 + if(ship !is null && fleet.fleetClass == FC_Combat) { 584 + avgSize += ship.blueprint.design.size; 585 + count += 1; 586 + } 587 + } 588 + if(count != 0) 589 + avgSize /= double(count); 590 + 591 + if(count < ai.behavior.maxActiveFleets && targetSize >= avgSize * ai.behavior.flagshipBuildMinAvgSize) { 592 + //Build the flagship 593 + DesignTarget@ design = designs.design(DP_Combat, targetSize, 594 + availMoney, budget.maintainable(BT_Military), 595 + factory.laborToBear(ai), 596 + findSize=true); 597 + @mainWait = construction.buildFlagship(design); 598 + mainWait.maxTime *= 1.5; 599 + spentMoney = true; 600 + 601 + if(log) 602 + ai.print("Ordering a new fleet at size "+targetSize); 603 + 604 + return; 605 + } 606 + } 607 + } 608 + } 609 + 610 + //See if any of our fleets need refilling 611 + //TODO: Aim for labor on the factory so that the supports are built in reasonable time 612 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 613 + FleetAI@ fleet = fleets.fleets[i]; 614 + if(fleet.mission !is null && fleet.mission.isActive) 615 + continue; 616 + if(fleet.fleetClass != FC_Combat) 617 + continue; 618 + if(fleet.obj.hasOrders) 619 + continue; 620 + if(fleet.filled >= 1.0) 621 + continue; 622 + if(hasSupportOrderFor(fleet.obj)) 623 + continue; 624 + if(!fleet.isHome) 625 + continue; 626 + 627 + //Re-station to our factory if we're idle and need refill without being near a factory 628 + Factory@ f = construction.getFactory(fleet.obj.region); 629 + if(f is null) { 630 + if(fleet.filled < ai.behavior.stagingToFactoryFill && construction.primaryFactory !is null) 631 + stationFleet(fleet, construction.primaryFactory.obj.region); 632 + continue; 633 + } 634 + 635 + //Don't order if the factory has support orders, it'll just make everything take longer 636 + if(f !is null && ai.behavior.supportOrderWaitOnFactory && fleet.filled < 0.9 && fleet.obj.SupplyGhost == 0) { 637 + if(f.obj.hasOrderedSupports && f.obj.SupplyUsed < f.obj.SupplyCapacity) 638 + continue; 639 + } 640 + 641 + int supCap = fleet.obj.SupplyCapacity; 642 + int supHave = fleet.obj.SupplyUsed - fleet.obj.SupplyGhost; 643 + if(supHave < supCap) { 644 + orderSupportsOn(fleet.obj); 645 + spentMoney = true; 646 + return; 647 + } 648 + } 649 + 650 + budget.checkedMilitarySpending = spentMoney; 651 + 652 + //TODO: Build defense stations 653 + } 654 + 655 + bool hasSupportOrderFor(Object& obj) { 656 + for(uint i = 0, cnt = supportOrders.length; i < cnt; ++i) { 657 + if(supportOrders[i].onObject is obj) 658 + return true; 659 + } 660 + return false; 661 + } 662 + 663 + void tick(double time) override { 664 + //Manage our orders for support ships 665 + for(uint i = 0, cnt = supportOrders.length; i < cnt; ++i) { 666 + if(!supportOrders[i].tick(ai, this, time)) { 667 + supportOrders.removeAt(i); 668 + --i; --cnt; 669 + } 670 + } 671 + } 672 + 673 + void focusTick(double time) override { 674 + //Find something for us to do 675 + findSomethingToDo(); 676 + 677 + //If we're far into the budget, spend our money on building supports at our factories 678 + if(budget.Progress > 0.9 && budget.canSpend(BT_Military, 10)) { 679 + for(uint i = 0, cnt = construction.factories.length; i < cnt; ++i) { 680 + //TODO: Build on planets in the system if this is full 681 + auto@ f = construction.factories[i]; 682 + if(f.obj.SupplyUsed < f.obj.SupplyCapacity && !hasSupportOrderFor(f.obj)) { 683 + orderSupportsOn(f.obj, expire=budget.RemainingTime); 684 + break; 685 + } 686 + } 687 + } 688 + 689 + //Check if we should re-station any of our fleets 690 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 691 + auto@ flAI = fleets.fleets[i]; 692 + if(flAI.stationed is null) { 693 + Region@ reg = flAI.obj.region; 694 + if(reg !is null && reg.PlanetsMask & ai.mask != 0) 695 + stationFleet(flAI, reg); 696 + } 697 + } 698 + 699 + //Make sure all our major factories are considered staging bases 700 + for(uint i = 0, cnt = construction.factories.length; i < cnt; ++i) { 701 + auto@ f = construction.factories[i]; 702 + if(f.obj.isShip) 703 + continue; 704 + Region@ reg = f.obj.region; 705 + if(reg is null) 706 + continue; 707 + auto@ base = getBase(reg); 708 + if(base is null) 709 + createStaging(reg); 710 + } 711 + 712 + //If we don't have any staging bases, make one at a focus 713 + if(stagingBases.length == 0 && development.focuses.length != 0) { 714 + Region@ reg = development.focuses[0].obj.region; 715 + if(reg !is null) 716 + createStaging(reg); 717 + } 718 + 719 + //Update our staging bases 720 + for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) { 721 + auto@ base = stagingBases[i]; 722 + if(!base.tick(ai, this, time)) { 723 + stagingBases.removeAt(i); 724 + --i; --cnt; 725 + } 726 + } 727 + } 728 + 729 + void turn() override { 730 + //Fleet construction happens in the beginning of the turn, because we want 731 + //to use our entire military budget on it. 732 + if(mainWait !is null) { 733 + if(mainWait.completed) { 734 + @mainWait = null; 735 + } 736 + else if(!mainWait.started) { 737 + if(log) 738 + ai.print("Failed current main construction wait."); 739 + construction.cancel(mainWait); 740 + @mainWait = null; 741 + } 742 + } 743 + spentMoney = false; 744 + } 745 +}; 746 + 747 +AIComponent@ createMilitary() { 748 + return Military(); 749 +}
Added scripts/server/empire_ai/weasel/Movement.as.
1 +// Movement 2 +// -------- 3 +// Manages FTL travel modes, expenditure of FTL energy, and general movement patterns. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 + 8 +import oddity_navigation; 9 +import ftl; 10 + 11 +enum FTLReturn { 12 + F_Pass, 13 + F_Continue, 14 + F_Done, 15 + F_Kill, 16 +}; 17 + 18 +class FTL : AIComponent { 19 + uint order(MoveOrder& order) { return F_Pass; } 20 + uint tick(MoveOrder& order, double time) { return F_Pass; } 21 +}; 22 + 23 +bool getNearPosition(Object& obj, Object& target, vec3d& pos, bool spread = false) { 24 + if(target !is null) { 25 + if(target.isPlanet) { 26 + Planet@ toPl = cast<Planet>(target); 27 + vec3d dir = obj.position - toPl.position; 28 + dir = dir.normalized(toPl.OrbitSize * 0.9); 29 + if(spread) 30 + dir = quaterniond_fromAxisAngle(vec3d_up(), randomd(-0.15,0.15) * pi) * dir; 31 + pos = toPl.position + dir; 32 + return true; 33 + } 34 + else if(obj.hasLeaderAI && (target.isShip || target.isOrbital)) { 35 + vec3d dir = obj.position - target.position; 36 + dir = dir.normalized(obj.getEngagementRange()); 37 + pos = target.position + dir; 38 + return true; 39 + } 40 + else { 41 + Region@ reg = cast<Region>(target); 42 + if(reg is null) 43 + @reg = target.region; 44 + if(reg !is null) { 45 + vec3d dir = obj.position - reg.position; 46 + dir = dir.normalized(reg.radius * 0.85); 47 + if(spread) 48 + dir = quaterniond_fromAxisAngle(vec3d_up(), randomd(-0.15,0.15) * pi) * dir; 49 + pos = reg.position + dir; 50 + return true; 51 + } 52 + } 53 + } 54 + return false; 55 +} 56 + 57 +bool targetPosition(MoveOrder& ord, vec3d& toPosition) { 58 + if(ord.target !is null) { 59 + return getNearPosition(ord.obj, ord.target, toPosition); 60 + } 61 + else { 62 + toPosition = ord.position; 63 + return true; 64 + } 65 +} 66 + 67 +double usableFTL(AI& ai, MoveOrder& ord) { 68 + double storage = ai.empire.FTLCapacity; 69 + double avail = ai.empire.FTLStored; 70 + 71 + double reserved = 0.0; 72 + if(ord.priority < MP_Critical) 73 + reserved += ai.behavior.ftlReservePctCritical; 74 + if(ord.priority < MP_Normal) 75 + reserved += ai.behavior.ftlReservePctNormal; 76 + avail -= reserved * storage; 77 + 78 + return avail; 79 +} 80 + 81 +enum MovePriority { 82 + MP_Background, 83 + MP_Normal, 84 + MP_Critical 85 +}; 86 + 87 +class MoveOrder { 88 + int id = -1; 89 + uint priority = MP_Normal; 90 + Object@ obj; 91 + Object@ target; 92 + vec3d position; 93 + bool completed = false; 94 + bool failed = false; 95 + 96 + void save(Movement& movement, SaveFile& file) { 97 + file << priority; 98 + file << obj; 99 + file << target; 100 + file << position; 101 + file << completed; 102 + file << failed; 103 + } 104 + 105 + void load(Movement& movement, SaveFile& file) { 106 + file >> priority; 107 + file >> obj; 108 + file >> target; 109 + file >> position; 110 + file >> completed; 111 + file >> failed; 112 + } 113 + 114 + void cancel() { 115 + failed = true; 116 + obj.clearOrders(); 117 + } 118 + 119 + bool tick(AI& ai, Movement& movement, double time) { 120 + //Check if we still exist 121 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 122 + failed = true; 123 + return false; 124 + } 125 + 126 + uint ftlMode = F_Pass; 127 + if(movement.ftl !is null) { 128 + ftlMode = movement.ftl.tick(this, time); 129 + if(ftlMode == F_Kill) 130 + return false; 131 + if(ftlMode == F_Done) 132 + return true; 133 + } 134 + 135 + //Check if we've arrived 136 + if(target !is null) { 137 + if(!target.valid) { 138 + failed = true; 139 + return false; 140 + } 141 + double targDist = target.radius + 45.0 + obj.radius; 142 + if(target.isRegion) 143 + targDist = target.radius * 0.86 + obj.radius; 144 + if(target.position.distanceTo(obj.position) < targDist) { 145 + completed = true; 146 + return false; 147 + } 148 + } 149 + else { 150 + double targDist = obj.radius * 2.0; 151 + if(obj.position.distanceTo(position) < targDist) { 152 + completed = true; 153 + return false; 154 + } 155 + } 156 + 157 + //Fail out if our order failed 158 + if(ftlMode == F_Pass) { 159 + if(!obj.hasOrders) { 160 + failed = true; 161 + return false; 162 + } 163 + } 164 + 165 + return true; 166 + } 167 +}; 168 + 169 +class Movement : AIComponent { 170 + int nextMoveOrderId = 0; 171 + array<MoveOrder@> moveOrders; 172 + 173 + array<Oddity@> oddities; 174 + 175 + FTL@ ftl; 176 + 177 + void create() { 178 + @ftl = cast<FTL>(ai.ftl); 179 + } 180 + 181 + void save(SaveFile& file) { 182 + file << nextMoveOrderId; 183 + 184 + uint cnt = moveOrders.length; 185 + file << cnt; 186 + for(uint i = 0; i < cnt; ++i) { 187 + saveMoveOrder(file, moveOrders[i]); 188 + moveOrders[i].save(this, file); 189 + } 190 + } 191 + 192 + void load(SaveFile& file) { 193 + file >> nextMoveOrderId; 194 + 195 + uint cnt = 0; 196 + file >> cnt; 197 + for(uint i = 0; i < cnt; ++i) { 198 + auto@ ord = loadMoveOrder(file); 199 + if(ord !is null) { 200 + ord.load(this, file); 201 + if(ord.obj !is null) 202 + moveOrders.insertLast(ord); 203 + } 204 + else { 205 + MoveOrder().load(this, file); 206 + } 207 + } 208 + } 209 + 210 + array<MoveOrder@> loadIds; 211 + MoveOrder@ loadMoveOrder(SaveFile& file) { 212 + int id = -1; 213 + file >> id; 214 + bool failed = false, completed = false; 215 + file >> failed; 216 + file >> completed; 217 + if(id == -1) { 218 + return null; 219 + } 220 + else { 221 + for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) { 222 + if(loadIds[i].id == id) 223 + return loadIds[i]; 224 + } 225 + MoveOrder data; 226 + data.id = id; 227 + data.failed = failed; 228 + data.completed = completed; 229 + loadIds.insertLast(data); 230 + return data; 231 + } 232 + } 233 + 234 + void saveMoveOrder(SaveFile& file, MoveOrder@ data) { 235 + int id = -1; 236 + bool failed = false, completed = false; 237 + if(data !is null) { 238 + id = data.id; 239 + failed = data.failed; 240 + completed = data.completed; 241 + } 242 + file << id; 243 + file << failed; 244 + file << completed; 245 + } 246 + 247 + void postLoad(AI& ai) { 248 + loadIds.length = 0; 249 + getOddityGates(oddities); 250 + } 251 + 252 + array<PathNode@> path; 253 + double getPathDistance(const vec3d& fromPosition, const vec3d& toPosition, double accel = 1.0) { 254 + pathOddityGates(oddities, ai.empire, path, fromPosition, toPosition, accel); 255 + return ::getPathDistance(fromPosition, toPosition, path); 256 + } 257 + 258 + double eta(Object& obj, Object& toObject, uint priority = MP_Normal) { 259 + return eta(obj, toObject.position, priority); 260 + } 261 + 262 + double eta(Object& obj, const vec3d& position, uint priority = MP_Normal) { 263 + //TODO: Use FTL 264 + //TODO: Path through gates/wormholes 265 + return newtonArrivalTime(obj.maxAcceleration, position - obj.position, obj.velocity); 266 + } 267 + 268 + void order(MoveOrder& ord) { 269 + if(ord.target !is null && ord.target is ord.obj.region) 270 + return; 271 + 272 + bool madeOrder = false; 273 + 274 + if(ftl !is null) { 275 + uint mode = ftl.order(ord); 276 + if(mode == F_Kill || mode == F_Done) 277 + return; 278 + madeOrder = (mode == F_Continue); 279 + } 280 + 281 + if(ord.target !is null) { 282 + ord.obj.addGotoOrder(ord.target, append=madeOrder); 283 + ord.position = ord.target.position; 284 + } 285 + else 286 + ord.obj.addMoveOrder(ord.position, append=madeOrder); 287 + } 288 + 289 + void add(MoveOrder& ord) { 290 + for(uint i = 0, cnt = moveOrders.length; i < cnt; ++i) { 291 + if(moveOrders[i].obj is ord.obj) { 292 + moveOrders[i].failed = true; 293 + moveOrders.removeAt(i); 294 + --i; --cnt; 295 + } 296 + } 297 + 298 + moveOrders.insertLast(ord); 299 + order(ord); 300 + } 301 + 302 + MoveOrder@ move(Object& obj, Object& toObject, uint priority = MP_Normal, bool spread = false, bool nearOnly = false) { 303 + if(toObject.isRegion) { 304 + if(obj.region is toObject) 305 + nearOnly = false; 306 + else 307 + nearOnly = true; 308 + } 309 + if(nearOnly) { 310 + vec3d pos; 311 + bool canNear = getNearPosition(obj, toObject, pos, spread); 312 + if(canNear) 313 + return move(obj, pos, priority); 314 + } 315 + 316 + MoveOrder ord; 317 + ord.id = nextMoveOrderId++; 318 + @ord.obj = obj; 319 + @ord.target = toObject; 320 + ord.priority = priority; 321 + 322 + add(ord); 323 + return ord; 324 + } 325 + 326 + MoveOrder@ move(Object& obj, const vec3d& position, uint priority = MP_Normal, bool spread = false) { 327 + MoveOrder ord; 328 + ord.id = nextMoveOrderId++; 329 + @ord.obj = obj; 330 + ord.position = position; 331 + ord.priority = priority; 332 + 333 + add(ord); 334 + return ord; 335 + } 336 + 337 + void tick(double time) override { 338 + for(uint i = 0, cnt = moveOrders.length; i < cnt; ++i) { 339 + if(!moveOrders[i].tick(ai, this, time)) { 340 + moveOrders.removeAt(i); 341 + --i; --cnt; 342 + } 343 + } 344 + } 345 + 346 + void focusTick(double time) override { 347 + //Update our gate navigation list 348 + getOddityGates(oddities); 349 + } 350 +}; 351 + 352 +AIComponent@ createMovement() { 353 + return Movement(); 354 +}
Added scripts/server/empire_ai/weasel/Orbitals.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.Budget; 3 +import empire_ai.weasel.Systems; 4 + 5 +from ai.orbitals import AIOrbitals, OrbitalAIHook, OrbitalUse; 6 +import ai.consider; 7 +import ai.construction; 8 + 9 +import orbitals; 10 +import saving; 11 + 12 +final class OrbitalAI { 13 + Object@ obj; 14 + const OrbitalModule@ type; 15 + double prevTick = 0; 16 + Object@ around; 17 + 18 + void init(AI& ai, Orbitals& orbitals) { 19 + if(obj.isOrbital) 20 + @type = getOrbitalModule(cast<Orbital>(obj).coreModule); 21 + } 22 + 23 + void save(Orbitals& orbitals, SaveFile& file) { 24 + file << obj; 25 + } 26 + 27 + void load(Orbitals& orbitals, SaveFile& file) { 28 + file >> obj; 29 + } 30 + 31 + void remove(AI& ai, Orbitals& orbitals) { 32 + } 33 + 34 + void tick(AI& ai, Orbitals& orbitals, double time) { 35 + //Deal with losing planet ownership 36 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 37 + orbitals.remove(this); 38 + return; 39 + } 40 + 41 + //Record what we're orbiting around 42 + if(around !is null) { 43 + if(!obj.isOrbitingAround(around)) 44 + @around = obj.getOrbitingAround(); 45 + } 46 + else { 47 + if(obj.hasOrbitCenter) 48 + @around = obj.getOrbitingAround(); 49 + } 50 + } 51 +}; 52 + 53 +class Orbitals : AIComponent, AIOrbitals { 54 + Budget@ budget; 55 + Systems@ systems; 56 + 57 + array<OrbitalAI@> orbitals; 58 + uint orbIdx = 0; 59 + 60 + array<IOrbitalConstruction@> genericBuilds; 61 + 62 + bool buildOrbitals = true; 63 + 64 + void create() { 65 + @budget = cast<Budget>(ai.budget); 66 + @systems = cast<Systems>(ai.systems); 67 + 68 + //Register specialized orbital types 69 + for(uint i = 0, cnt = getOrbitalModuleCount(); i < cnt; ++i) { 70 + auto@ type = getOrbitalModule(i); 71 + for(uint n = 0, ncnt = type.ai.length; n < ncnt; ++n) { 72 + auto@ hook = cast<OrbitalAIHook>(type.ai[n]); 73 + if(hook !is null) 74 + hook.register(this, type); 75 + } 76 + } 77 + } 78 + 79 + Empire@ get_empire() { 80 + return ai.empire; 81 + } 82 + 83 + Considerer@ get_consider() { 84 + return cast<Considerer>(ai.consider); 85 + } 86 + 87 + OrbitalAI@ getInSystem(const OrbitalModule@ module, Region@ reg) { 88 + if(module is null) 89 + return null; 90 + for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) { 91 + if(orbitals[i].type is module) { 92 + if(orbitals[i].obj.region is reg) 93 + return orbitals[i]; 94 + } 95 + } 96 + return null; 97 + } 98 + 99 + bool haveInSystem(const OrbitalModule@ module, Region@ reg) { 100 + if(module is null) 101 + return false; 102 + for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) { 103 + if(orbitals[i].type is module) { 104 + if(orbitals[i].obj.region is reg) 105 + return true; 106 + } 107 + } 108 + return false; 109 + } 110 + 111 + bool haveAround(const OrbitalModule@ module, Object@ around) { 112 + if(module is null) 113 + return false; 114 + for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) { 115 + if(orbitals[i].type is module) { 116 + if(orbitals[i].around is around) 117 + return true; 118 + } 119 + } 120 + return false; 121 + } 122 + 123 + void registerUse(OrbitalUse use, const OrbitalModule& type) { 124 + switch(use) { 125 + case OU_Shipyard: 126 + @ai.defs.Shipyard = type; 127 + break; 128 + case OU_TradeOutpost: 129 + @ai.defs.TradeOutpost = type; 130 + break; 131 + case OU_TradeStation: 132 + @ai.defs.TradeStation = type; 133 + break; 134 + } 135 + } 136 + 137 + void save(SaveFile& file) { 138 + uint cnt = orbitals.length; 139 + file << cnt; 140 + for(uint i = 0; i < cnt; ++i) { 141 + auto@ data = orbitals[i]; 142 + saveAI(file, data); 143 + data.save(this, file); 144 + } 145 + } 146 + 147 + void load(SaveFile& file) { 148 + uint cnt = 0; 149 + file >> cnt; 150 + for(uint i = 0; i < cnt; ++i) { 151 + auto@ data = loadAI(file); 152 + if(data !is null) 153 + data.load(this, file); 154 + else 155 + OrbitalAI().load(this, file); 156 + } 157 + } 158 + 159 + OrbitalAI@ loadAI(SaveFile& file) { 160 + Object@ obj; 161 + file >> obj; 162 + 163 + if(obj is null) 164 + return null; 165 + 166 + OrbitalAI@ data = getAI(obj); 167 + if(data is null) { 168 + @data = OrbitalAI(); 169 + @data.obj = obj; 170 + data.prevTick = gameTime; 171 + orbitals.insertLast(data); 172 + data.init(ai, this); 173 + } 174 + return data; 175 + } 176 + 177 + void saveAI(SaveFile& file, OrbitalAI@ ai) { 178 + Object@ obj; 179 + if(ai !is null) 180 + @obj = ai.obj; 181 + file << obj; 182 + } 183 + 184 + void start() { 185 + checkForOrbitals(); 186 + } 187 + 188 + void checkForOrbitals() { 189 + auto@ data = ai.empire.getOrbitals(); 190 + Object@ obj; 191 + while(receive(data, obj)) { 192 + if(obj !is null) 193 + register(obj); 194 + } 195 + } 196 + 197 + bool isBuilding(const OrbitalModule& type) { 198 + for(uint i = 0, cnt = genericBuilds.length; i < cnt; ++i) { 199 + if(genericBuilds[i].module is type) 200 + return true; 201 + } 202 + return false; 203 + } 204 + 205 + void tick(double time) { 206 + double curTime = gameTime; 207 + 208 + if(orbitals.length != 0) { 209 + orbIdx = (orbIdx+1) % orbitals.length; 210 + 211 + auto@ data = orbitals[orbIdx]; 212 + data.tick(ai, this, curTime - data.prevTick); 213 + data.prevTick = curTime; 214 + } 215 + } 216 + 217 + uint prevCount = 0; 218 + double checkTimer = 0; 219 + void focusTick(double time) override { 220 + //Check for any newly obtained planets 221 + uint curCount = ai.empire.orbitalCount; 222 + checkTimer += time; 223 + if(curCount != prevCount || checkTimer > 60.0) { 224 + checkForOrbitals(); 225 + prevCount = curCount; 226 + checkTimer = 0; 227 + } 228 + 229 + //Deal with building AI hints 230 + 231 + } 232 + 233 + OrbitalAI@ getAI(Object& obj) { 234 + for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) { 235 + if(orbitals[i].obj is obj) 236 + return orbitals[i]; 237 + } 238 + return null; 239 + } 240 + 241 + OrbitalAI@ register(Object& obj) { 242 + OrbitalAI@ data = getAI(obj); 243 + if(data is null) { 244 + @data = OrbitalAI(); 245 + @data.obj = obj; 246 + data.prevTick = gameTime; 247 + orbitals.insertLast(data); 248 + data.init(ai, this); 249 + } 250 + return data; 251 + } 252 + 253 + void remove(OrbitalAI@ data) { 254 + data.remove(ai, this); 255 + orbitals.remove(data); 256 + } 257 +}; 258 + 259 +AIComponent@ createOrbitals() { 260 + return Orbitals(); 261 +}
Added scripts/server/empire_ai/weasel/Planets.as.
1 +import empire_ai.weasel.WeaselAI; 2 + 3 +import empire_ai.weasel.Events; 4 +import empire_ai.weasel.Resources; 5 +import empire_ai.weasel.Budget; 6 +import empire_ai.weasel.Systems; 7 + 8 +import ai.construction; 9 +import ai.events; 10 + 11 +import planets.PlanetSurface; 12 + 13 +import void relationRecordLost(AI& ai, Empire& emp, Object@ obj) from "empire_ai.weasel.Relations"; 14 + 15 +from constructions import ConstructionType, getConstructionTypeCount, getConstructionType; 16 +from ai.constructions import AIConstructions, ConstructionAIHook, ConstructionUse; 17 + 18 +import ai.consider; 19 + 20 +import buildings; 21 +import saving; 22 + 23 +final class BuildingRequest : IBuildingConstruction { 24 + protected int _id = -1; 25 + PlanetAI@ plAI; 26 + AllocateBudget@ alloc; 27 + const BuildingType@ type; 28 + double expires = INFINITY; 29 + bool built = false; 30 + bool canceled = false; 31 + bool scatter = false; 32 + vec2i builtAt; 33 + 34 + BuildingRequest(Budget& budget, const BuildingType@ type, double priority, uint moneyType) { 35 + @this.type = type; 36 + @alloc = budget.allocate(moneyType, type.buildCostEst, type.maintainCostEst, priority=priority); 37 + } 38 + 39 + BuildingRequest() { 40 + } 41 + 42 + int id { 43 + get const { return _id; } 44 + set { _id = value; } 45 + } 46 + 47 + bool get_started() const { return built; } 48 + 49 + bool completed { 50 + get const { return getProgress() >= 1.0; } 51 + set { } 52 + } 53 + 54 + const BuildingType@ get_building() const { return type; } 55 + 56 + void save(Planets& planets, SaveFile& file) { 57 + planets.saveAI(file, plAI); 58 + planets.budget.saveAlloc(file, alloc); 59 + file.writeIdentifier(SI_Building, type.id); 60 + file << expires; 61 + file << built; 62 + file << canceled; 63 + file << builtAt; 64 + file << scatter; 65 + } 66 + 67 + void load(Planets& planets, SaveFile& file) { 68 + @plAI = planets.loadAI(file); 69 + @alloc = planets.budget.loadAlloc(file); 70 + @type = getBuildingType(file.readIdentifier(SI_Building)); 71 + file >> expires; 72 + file >> built; 73 + file >> canceled; 74 + file >> builtAt; 75 + if(file >= SV_0153) 76 + file >> scatter; 77 + } 78 + 79 + double cachedProgress = 0.0; 80 + double nextProgressCache = 0.0; 81 + double getProgress() { 82 + if(!built) 83 + return 0.0; 84 + if(gameTime < nextProgressCache) 85 + return cachedProgress; 86 + 87 + cachedProgress = plAI.obj.getBuildingProgressAt(builtAt.x, builtAt.y); 88 + if(cachedProgress > 0.95) 89 + nextProgressCache = gameTime + 1.0; 90 + else if(cachedProgress < 0.5) 91 + nextProgressCache = gameTime + 30.0; 92 + else 93 + nextProgressCache = gameTime + 10.0; 94 + 95 + return cachedProgress; 96 + } 97 + 98 + bool tick(AI& ai, Planets& planets, double time) { 99 + if(expires < gameTime) { 100 + if(planets.log) 101 + ai.print(type.name+" build request expired", plAI.obj); 102 + canceled = true; 103 + return false; 104 + } 105 + 106 + if(alloc is null || alloc.allocated) { 107 + builtAt = plAI.buildBuilding(ai, planets, type, scatter=scatter); 108 + if(builtAt == vec2i(-1,-1)) { 109 + planets.budget.remove(alloc); 110 + canceled = true; 111 + } 112 + else 113 + built = true; 114 + return false; 115 + } 116 + return true; 117 + } 118 +}; 119 + 120 +final class ConstructionRequest : IGenericConstruction { 121 + protected int _id = -1; 122 + PlanetAI@ plAI; 123 + AllocateBudget@ alloc; 124 + const ConstructionType@ type; 125 + double expires = INFINITY; 126 + bool built = false; 127 + bool canceled = false; 128 + vec2i builtAt; 129 + 130 + ConstructionRequest(Budget& budget, Object@ buildAt, const ConstructionType@ type, double priority, uint moneyType) { 131 + @this.type = type; 132 + @alloc = budget.allocate(moneyType, type.getBuildCost(buildAt), type.getMaintainCost(buildAt), priority=priority); 133 + } 134 + 135 + ConstructionRequest() { 136 + } 137 + 138 + int id { 139 + get const { return _id; } 140 + set { _id = value; } 141 + } 142 + 143 + bool get_started() const { return built; } 144 + 145 + bool completed { 146 + get const { 147 + double progress = getProgress(); 148 + return progress == -1.0 || progress >= 1.0; 149 + } 150 + set { } 151 + } 152 + 153 + const ConstructionType@ get_construction() const { return type; } 154 + 155 + void save(Planets& planets, SaveFile& file) { 156 + planets.saveAI(file, plAI); 157 + planets.budget.saveAlloc(file, alloc); 158 + file.writeIdentifier(SI_ConstructionType, type.id); 159 + file << expires; 160 + file << built; 161 + file << canceled; 162 + file << builtAt; 163 + } 164 + 165 + void load(Planets& planets, SaveFile& file) { 166 + @plAI = planets.loadAI(file); 167 + @alloc = planets.budget.loadAlloc(file); 168 + @type = getConstructionType(file.readIdentifier(SI_ConstructionType)); 169 + file >> expires; 170 + file >> built; 171 + file >> canceled; 172 + file >> builtAt; 173 + } 174 + 175 + double cachedProgress = 0.0; 176 + double nextProgressCache = 0.0; 177 + double getProgress() { 178 + if(!built) 179 + return 0.0; 180 + if(gameTime < nextProgressCache) 181 + return cachedProgress; 182 + 183 + cachedProgress = plAI.obj.get_constructionProgress(); 184 + if(cachedProgress > 0.95) 185 + nextProgressCache = gameTime + 1.0; 186 + else if(cachedProgress < 0.5) 187 + nextProgressCache = gameTime + 30.0; 188 + else 189 + nextProgressCache = gameTime + 10.0; 190 + 191 + return cachedProgress; 192 + } 193 + 194 + bool tick(AI& ai, Planets& planets, double time) { 195 + if(expires < gameTime) { 196 + if(planets.log) 197 + ai.print(type.name+" construction request expired", plAI.obj); 198 + canceled = true; 199 + return false; 200 + } 201 + 202 + if(alloc is null || alloc.allocated) { 203 + if(!plAI.buildConstruction(ai, planets, type)) { 204 + planets.budget.remove(alloc); 205 + canceled = true; 206 + 207 + } 208 + else 209 + built = true; 210 + return false; 211 + } 212 + return true; 213 + } 214 +}; 215 + 216 +final class PlanetAI { 217 + Planet@ obj; 218 + 219 + int targetLevel = 0; 220 + int requestedLevel = 0; 221 + double prevTick = 0; 222 + 223 + array<ExportData@>@ resources; 224 + ImportData@ claimedChain; 225 + 226 + void init(AI& ai, Planets& planets) { 227 + @resources = planets.resources.availableResource(obj); 228 + planets.events.notifyPlanetAdded(this, EventArgs()); 229 + } 230 + 231 + void save(Planets& planets, SaveFile& file) { 232 + file << obj; 233 + file << targetLevel; 234 + file << requestedLevel; 235 + file << prevTick; 236 + 237 + uint cnt = 0; 238 + if(resources !is null) 239 + cnt = resources.length; 240 + file << cnt; 241 + for(uint i = 0; i < cnt; ++i) 242 + planets.resources.saveExport(file, resources[i]); 243 + planets.resources.saveImport(file, claimedChain); 244 + } 245 + 246 + void load(Planets& planets, SaveFile& file) { 247 + file >> obj; 248 + file >> targetLevel; 249 + file >> requestedLevel; 250 + file >> prevTick; 251 + uint cnt = 0; 252 + file >> cnt; 253 + @resources = array<ExportData@>(); 254 + for(uint i = 0; i < cnt; ++i) { 255 + auto@ data = planets.resources.loadExport(file); 256 + if(data !is null) 257 + resources.insertLast(data); 258 + } 259 + @claimedChain = planets.resources.loadImport(file); 260 + } 261 + 262 + void remove(AI& ai, Planets& planets) { 263 + if(claimedChain !is null) { 264 + claimedChain.claimedFor = false; 265 + @claimedChain = null; 266 + } 267 + @resources = null; 268 + planets.events.notifyPlanetRemoved(this, EventArgs()); 269 + } 270 + 271 + void tick(AI& ai, Planets& planets, double time) { 272 + //Deal with losing planet ownership 273 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 274 + if(obj.owner !is ai.empire) 275 + relationRecordLost(ai, obj.owner, obj); 276 + planets.remove(this); 277 + return; 278 + } 279 + 280 + //Handle when the planet's native resources change 281 + if(obj.nativeResourceCount != resources.length || (resources.length != 0 && obj.primaryResourceId != resources[0].resourceId)) 282 + planets.updateResourceList(obj, resources); 283 + 284 + //Level up resources if we need them 285 + if(resources.length != 0 && claimedChain is null) { 286 + int resLevel = resources[0].resource.level; 287 + if(resLevel > 0 && !resources[0].resource.exportable) 288 + resLevel += 1; 289 + if(targetLevel < resLevel) { 290 + //See if we need it for anything first 291 + @claimedChain = planets.resources.findUnclaimed(resources[0]); 292 + if(claimedChain !is null) 293 + claimedChain.claimedFor = true; 294 + 295 + //Chain the levelup before what needs it 296 + planets.requestLevel(this, resLevel, before=claimedChain); 297 + } 298 + } 299 + 300 + //Request imports if the planet needs to level up 301 + if(targetLevel > requestedLevel) { 302 + int nextLevel = min(targetLevel, min(obj.resourceLevel, requestedLevel)+1); 303 + if(nextLevel != requestedLevel) { 304 + planets.resources.organizeImports(obj, nextLevel); 305 + requestedLevel = nextLevel; 306 + } 307 + } 308 + else if(targetLevel < requestedLevel) { 309 + planets.resources.organizeImports(obj, targetLevel); 310 + requestedLevel = targetLevel; 311 + } 312 + } 313 + 314 + double get_colonizeWeight() { 315 + if(obj.isColonizing) 316 + return 0.0; 317 + if(obj.level == 0) 318 + return 0.0; 319 + if(!obj.canSafelyColonize) 320 + return 0.0; 321 + double w = 1.0; 322 + double pop = obj.population; 323 + double maxPop = obj.maxPopulation; 324 + if(pop < maxPop-0.1) { 325 + if(obj.resourceLevel > 1 && pop/maxPop < 0.9) 326 + return 0.0; 327 + w *= 0.01 * (pop / maxPop); 328 + } 329 + return w; 330 + } 331 + 332 + vec2i buildBuilding(AI& ai, Planets& planets, const BuildingType@ type, bool scatter = true) { 333 + if(type is null || !type.canBuildOn(obj)) 334 + return vec2i(-1,-1); 335 + 336 + if(planets.log) 337 + ai.print("Attempt to construct "+type.name, obj); 338 + 339 + PlanetSurface@ surface = planets.surface; 340 + receive(obj.getPlanetSurface(), surface); 341 + 342 + //Find the best place to build this building 343 + int bestPenalty = INT_MAX; 344 + int possibs = 0; 345 + vec2i best; 346 + vec2i center = vec2i(type.getCenter()); 347 + 348 + for(int x = 0, w = surface.size.x; x < w; ++x) { 349 + for(int y = 0, h = surface.size.y; y < h; ++y) { 350 + vec2i pos(x, y); 351 + 352 + bool valid = true; 353 + int penalty = 0; 354 + 355 + for(int xoff = 0; xoff < int(type.size.x); ++xoff) { 356 + for(int yoff = 0; yoff < int(type.size.y); ++yoff) { 357 + vec2i rpos = pos - center + vec2i(xoff, yoff); 358 + 359 + if(rpos.x < 0 || rpos.y < 0 || rpos.x >= w || rpos.y >= h) { 360 + valid = false; 361 + break; 362 + } 363 + 364 + auto@ biome = surface.getBiome(rpos.x, rpos.y); 365 + if(biome is null || !biome.buildable) { 366 + valid = false; 367 + break; 368 + } 369 + 370 + uint flags = surface.getFlags(rpos.x, rpos.y); 371 + if(flags & SuF_Usable == 0) { 372 + bool affinity = false; 373 + if(type.buildAffinities.length != 0) { 374 + for(uint i = 0, cnt = type.buildAffinities.length; i < cnt; ++i) { 375 + if(biome is type.buildAffinities[i].biome) { 376 + affinity = true; 377 + break; 378 + } 379 + } 380 + } 381 + if(!affinity && type.tileBuildCost > 0) { 382 + penalty += 1; 383 + 384 + if(biome.buildCost > 1.0) 385 + penalty += ceil((biome.buildCost - 1.0) / 0.1); 386 + } 387 + affinity = false; 388 + if(type.maintainAffinities.length != 0) { 389 + for(uint i = 0, cnt = type.maintainAffinities.length; i < cnt; ++i) { 390 + if(biome is type.maintainAffinities[i].biome) { 391 + affinity = true; 392 + break; 393 + } 394 + } 395 + } 396 + if(!affinity && type.tileMaintainCost > 0) 397 + penalty += 2; 398 + } 399 + 400 + auto@ bld = surface.getBuilding(rpos.x, rpos.y); 401 + if(bld !is null) { 402 + if(bld.type.civilian) { 403 + penalty += 2; 404 + } 405 + else { 406 + valid = false; 407 + break; 408 + } 409 + } 410 + } 411 + if(!valid) 412 + break; 413 + } 414 + 415 + if(valid) { 416 + if(penalty < bestPenalty) { 417 + possibs = 1; 418 + bestPenalty = penalty; 419 + best = pos; 420 + } 421 + else if(penalty == bestPenalty && scatter) { 422 + possibs += 1; 423 + if(randomd() < 1.0 / double(possibs)) 424 + best = pos; 425 + } 426 + } 427 + } 428 + } 429 + 430 + if(bestPenalty != INT_MAX) { 431 + if(planets.log) 432 + ai.print("Construct "+type.name+" at "+best+" with penalty "+bestPenalty, obj); 433 + obj.buildBuilding(type.id, best); 434 + return best; 435 + } 436 + 437 + if(planets.log) 438 + ai.print("Could not find place to construct "+type.name, obj); 439 + return vec2i(-1,-1); 440 + } 441 + 442 + bool buildConstruction(AI& ai, Planets& planets, const ConstructionType@ type) { 443 + if(type is null || !type.canBuild(obj)) 444 + return false; 445 + 446 + if(planets.log) 447 + ai.print("Construct "+type.name); 448 + obj.buildConstruction(type.id); 449 + 450 + return true; 451 + } 452 +} 453 + 454 +final class PotentialSource { 455 + Planet@ pl; 456 + double weight = 0; 457 +}; 458 + 459 +final class AsteroidData { 460 + Asteroid@ asteroid; 461 + array<ExportData@>@ resources; 462 + 463 + void save(Planets& planets, SaveFile& file) { 464 + file << asteroid; 465 + 466 + uint cnt = 0; 467 + if(resources !is null) 468 + cnt = resources.length; 469 + 470 + file << cnt; 471 + for(uint i = 0; i < cnt; ++i) 472 + planets.resources.saveExport(file, resources[i]); 473 + } 474 + 475 + void load(Planets& planets, SaveFile& file) { 476 + file >> asteroid; 477 + 478 + uint cnt = 0; 479 + file >> cnt; 480 + if(cnt != 0) 481 + @resources = array<ExportData@>(); 482 + for(uint i = 0; i < cnt; ++i) { 483 + auto@ res = planets.resources.loadExport(file); 484 + if(res !is null) 485 + resources.insertLast(res); 486 + } 487 + } 488 + 489 + bool tick(AI& ai, Planets& planets) { 490 + if(asteroid is null || !asteroid.valid || asteroid.owner !is ai.empire) { 491 + planets.resources.killImportsTo(asteroid); 492 + planets.resources.killResourcesFrom(asteroid); 493 + return false; 494 + } 495 + if(resources is null) { 496 + @resources = planets.resources.availableResource(asteroid); 497 + } 498 + else { 499 + if(asteroid.nativeResourceCount != resources.length || (resources.length != 0 && asteroid.primaryResourceId != resources[0].resourceId)) 500 + planets.updateResourceList(asteroid, resources); 501 + } 502 + return true; 503 + } 504 +}; 505 + 506 +class Planets : AIComponent, AIConstructions { 507 + Events@ events; 508 + Resources@ resources; 509 + Budget@ budget; 510 + Systems@ systems; 511 + 512 + PlanetSurface surface; 513 + 514 + array<AsteroidData@> ownedAsteroids; 515 + array<PlanetAI@> planets; 516 + array<PlanetAI@> bumped; 517 + uint planetIdx = 0; 518 + 519 + 520 + array<BuildingRequest@> building; 521 + int nextBuildingRequestId = 0; 522 + array<ConstructionRequest@> constructionRequests; 523 + int nextConstructionRequestId = 0; 524 + 525 + void create() { 526 + @events = cast<Events>(ai.events); 527 + @resources = cast<Resources>(ai.resources); 528 + @budget = cast<Budget>(ai.budget); 529 + @systems = cast<Systems>(ai.systems); 530 + 531 + //Register specialized construction types 532 + for(uint i = 0, cnt = getConstructionTypeCount(); i < cnt; ++i) { 533 + auto@ type = getConstructionType(i); 534 + for(uint n = 0, ncnt = type.ai.length; n < ncnt; ++n) { 535 + auto@ hook = cast<ConstructionAIHook>(type.ai[n]); 536 + if(hook !is null) 537 + hook.register(this, type); 538 + } 539 + } 540 + } 541 + 542 + Empire@ get_empire() { 543 + return ai.empire; 544 + } 545 + 546 + Considerer@ get_consider() { 547 + return cast<Considerer>(ai.consider); 548 + } 549 + 550 + void registerUse(ConstructionUse use, const ConstructionType& type) { 551 + switch(use) { 552 + case CU_MoonBase: 553 + @ai.defs.MoonBase = type; 554 + break; 555 + } 556 + } 557 + 558 + void save(SaveFile& file) { 559 + file << nextBuildingRequestId; 560 + file << nextConstructionRequestId; 561 + 562 + uint cnt = planets.length; 563 + file << cnt; 564 + for(uint i = 0; i < cnt; ++i) { 565 + auto@ plAI = planets[i]; 566 + saveAI(file, plAI); 567 + plAI.save(this, file); 568 + } 569 + 570 + cnt = building.length; 571 + file << cnt; 572 + for(uint i = 0; i < cnt; ++i) { 573 + saveBuildingRequest(file, building[i]); 574 + building[i].save(this, file); 575 + } 576 + 577 + cnt = constructionRequests.length; 578 + file << cnt; 579 + for(uint i = 0; i < cnt; ++i) { 580 + saveConstructionRequest(file, constructionRequests[i]); 581 + constructionRequests[i].save(this, file); 582 + } 583 + 584 + cnt = ownedAsteroids.length; 585 + file << cnt; 586 + for(uint i = 0; i < cnt; ++i) 587 + ownedAsteroids[i].save(this, file); 588 + } 589 + 590 + void load(SaveFile& file) { 591 + file >> nextBuildingRequestId; 592 + file >> nextConstructionRequestId; 593 + 594 + uint cnt = 0; 595 + file >> cnt; 596 + for(uint i = 0; i < cnt; ++i) { 597 + auto@ plAI = loadAI(file); 598 + if(plAI !is null) 599 + plAI.load(this, file); 600 + else 601 + PlanetAI().load(this, file); 602 + } 603 + 604 + file >> cnt; 605 + for(uint i = 0; i < cnt; ++i) { 606 + auto@ req = loadBuildingRequest(file); 607 + if(req !is null) { 608 + req.load(this, file); 609 + building.insertLast(req); 610 + } 611 + } 612 + 613 + file >> cnt; 614 + for(uint i = 0; i < cnt; ++i) { 615 + auto@ req = loadConstructionRequest(file); 616 + if (req !is null) { 617 + req.load(this, file); 618 + constructionRequests.insertLast(req); 619 + } 620 + } 621 + 622 + file >> cnt; 623 + for(uint i = 0; i < cnt; ++i) { 624 + AsteroidData data; 625 + data.load(this, file); 626 + if(data.asteroid !is null) 627 + ownedAsteroids.insertLast(data); 628 + } 629 + } 630 + 631 + PlanetAI@ loadAI(SaveFile& file) { 632 + Planet@ obj; 633 + file >> obj; 634 + 635 + if(obj is null) 636 + return null; 637 + 638 + PlanetAI@ plAI = getAI(obj); 639 + if(plAI is null) { 640 + @plAI = PlanetAI(); 641 + @plAI.obj = obj; 642 + plAI.prevTick = gameTime; 643 + planets.insertLast(plAI); 644 + } 645 + return plAI; 646 + } 647 + 648 + void saveAI(SaveFile& file, PlanetAI@ ai) { 649 + Planet@ pl; 650 + if(ai !is null) 651 + @pl = ai.obj; 652 + file << pl; 653 + } 654 + 655 + array<BuildingRequest@> buildingLoadIds; 656 + BuildingRequest@ loadBuildingRequest(int id) { 657 + if(id == -1) 658 + return null; 659 + for(uint i = 0, cnt = buildingLoadIds.length; i < cnt; ++i) { 660 + if(buildingLoadIds[i].id == id) 661 + return buildingLoadIds[i]; 662 + } 663 + BuildingRequest data; 664 + data.id = id; 665 + buildingLoadIds.insertLast(data); 666 + return data; 667 + } 668 + BuildingRequest@ loadBuildingRequest(SaveFile& file) { 669 + int id = -1; 670 + file >> id; 671 + if(id == -1) 672 + return null; 673 + else 674 + return loadBuildingRequest(id); 675 + } 676 + void saveBuildingRequest(SaveFile& file, BuildingRequest@ data) { 677 + int id = -1; 678 + if(data !is null) 679 + id = data.id; 680 + file << id; 681 + } 682 + array<ConstructionRequest@> constructionLoadIds; 683 + ConstructionRequest@ loadConstructionRequest(int id) { 684 + if(id == -1) 685 + return null; 686 + for(uint i = 0, cnt = constructionLoadIds.length; i < cnt; ++i) { 687 + if(constructionLoadIds[i].id == id) 688 + return constructionLoadIds[i]; 689 + } 690 + ConstructionRequest data; 691 + data.id = id; 692 + constructionLoadIds.insertLast(data); 693 + return data; 694 + } 695 + ConstructionRequest@ loadConstructionRequest(SaveFile& file) { 696 + int id = -1; 697 + file >> id; 698 + if(id == -1) 699 + return null; 700 + else 701 + return loadConstructionRequest(id); 702 + } 703 + void saveConstructionRequest(SaveFile& file, ConstructionRequest@ data) { 704 + int id = -1; 705 + if(data !is null) 706 + id = data.id; 707 + file << id; 708 + } 709 + void postLoad(AI& ai) { 710 + buildingLoadIds.length = 0; 711 + constructionLoadIds.length= 0; 712 + } 713 + 714 + void start() { 715 + checkForPlanets(); 716 + } 717 + 718 + void checkForPlanets() { 719 + auto@ data = ai.empire.getPlanets(); 720 + Object@ obj; 721 + while(receive(data, obj)) { 722 + Planet@ pl = cast<Planet>(obj); 723 + if(pl !is null) 724 + register(cast<Planet>(obj)); 725 + } 726 + } 727 + 728 + uint roidIdx = 0; 729 + void tick(double time) { 730 + double curTime = gameTime; 731 + 732 + if(planets.length != 0) { 733 + planetIdx = (planetIdx+1) % planets.length; 734 + 735 + auto@ plAI = planets[planetIdx]; 736 + plAI.tick(ai, this, curTime - plAI.prevTick); 737 + plAI.prevTick = curTime; 738 + } 739 + 740 + for(int i = bumped.length-1; i >= 0; --i) { 741 + auto@ plAI = bumped[i]; 742 + double tickTime = curTime - plAI.prevTick; 743 + if(tickTime != 0) { 744 + plAI.tick(ai, this, tickTime); 745 + plAI.prevTick = curTime; 746 + } 747 + } 748 + bumped.length = 0; 749 + 750 + if(ownedAsteroids.length != 0) { 751 + roidIdx = (roidIdx+1) % ownedAsteroids.length; 752 + if(!ownedAsteroids[roidIdx].tick(ai, this)) 753 + ownedAsteroids.removeAt(roidIdx); 754 + } 755 + 756 + //Construct any buildings we are waiting on 757 + for(uint i = 0, cnt = building.length; i < cnt; ++i) { 758 + if(!building[i].tick(ai, this, time)) { 759 + building.removeAt(i); 760 + --i; --cnt; 761 + break; 762 + } 763 + } 764 + 765 + //Construct any constructions we are waiting on 766 + for(uint i = 0, cnt = constructionRequests.length; i < cnt; ++i) { 767 + if(!constructionRequests[i].tick(ai, this, time)) { 768 + constructionRequests.removeAt(i); 769 + --i; --cnt; 770 + break; 771 + } 772 + } 773 + } 774 + 775 + uint prevCount = 0; 776 + double checkTimer = 0; 777 + uint sysIdx = 0, ownedIdx = 0; 778 + void focusTick(double time) override { 779 + //Check for any newly obtained planets 780 + uint curCount = ai.empire.planetCount; 781 + checkTimer += time; 782 + if(curCount != prevCount || checkTimer > 60.0) { 783 + checkForPlanets(); 784 + prevCount = curCount; 785 + checkTimer = 0; 786 + } 787 + 788 + //Find any asteroids we've gained 789 + if(systems.all.length != 0) { 790 + sysIdx = (sysIdx+1) % systems.all.length; 791 + auto@ sys = systems.all[sysIdx]; 792 + for(uint i = 0, cnt = sys.asteroids.length; i < cnt; ++i) 793 + register(sys.asteroids[i]); 794 + } 795 + if(systems.owned.length != 0) { 796 + ownedIdx = (ownedIdx+1) % systems.owned.length; 797 + auto@ sys = systems.owned[ownedIdx]; 798 + for(uint i = 0, cnt = sys.asteroids.length; i < cnt; ++i) 799 + register(sys.asteroids[i]); 800 + } 801 + } 802 + 803 + void bump(Planet@ pl) { 804 + if(pl !is null) 805 + bump(getAI(pl)); 806 + } 807 + 808 + void bump(PlanetAI@ plAI) { 809 + if(plAI !is null) 810 + bumped.insertLast(plAI); 811 + } 812 + 813 + PlanetAI@ getAI(Planet& obj) { 814 + for(uint i = 0, cnt = planets.length; i < cnt; ++i) { 815 + if(planets[i].obj is obj) 816 + return planets[i]; 817 + } 818 + return null; 819 + } 820 + 821 + PlanetAI@ register(Planet& obj) { 822 + PlanetAI@ plAI = getAI(obj); 823 + if(plAI is null) { 824 + @plAI = PlanetAI(); 825 + @plAI.obj = obj; 826 + plAI.prevTick = gameTime; 827 + planets.insertLast(plAI); 828 + plAI.init(ai, this); 829 + } 830 + return plAI; 831 + } 832 + 833 + AsteroidData@ register(Asteroid@ obj) { 834 + if(obj is null || !obj.valid || obj.owner !is ai.empire) 835 + return null; 836 + for(uint i = 0, cnt = ownedAsteroids.length; i < cnt; ++i) { 837 + if(ownedAsteroids[i].asteroid is obj) 838 + return ownedAsteroids[i]; 839 + } 840 + 841 + AsteroidData data; 842 + @data.asteroid = obj; 843 + ownedAsteroids.insertLast(data); 844 + 845 + if(log) 846 + ai.print("Detected asteroid: "+obj.name, obj.region); 847 + 848 + return data; 849 + } 850 + 851 + void remove(PlanetAI@ plAI) { 852 + resources.killImportsTo(plAI.obj); 853 + resources.killResourcesFrom(plAI.obj); 854 + plAI.remove(ai, this); 855 + planets.remove(plAI); 856 + bumped.remove(plAI); 857 + } 858 + 859 + void requestLevel(PlanetAI@ plAI, int toLevel, ImportData@ before = null) { 860 + if(plAI is null) 861 + return; 862 + plAI.targetLevel = toLevel; 863 + if(before !is null) { 864 + for(int lv = max(plAI.requestedLevel, 1); lv <= toLevel; ++lv) 865 + resources.organizeImports(plAI.obj, lv, before); 866 + plAI.requestedLevel = toLevel; 867 + } 868 + else { 869 + bump(plAI); 870 + } 871 + } 872 + 873 + BuildingRequest@ requestBuilding(PlanetAI@ plAI, const BuildingType@ type, double priority = 1.0, double expire = INFINITY, bool scatter = true, uint moneyType = BT_Development) { 874 + if(plAI is null) 875 + return null; 876 + 877 + if(log) 878 + ai.print("Requested building of type "+type.name, plAI.obj); 879 + 880 + BuildingRequest req(budget, type, priority, moneyType); 881 + req.scatter = scatter; 882 + req.id = nextBuildingRequestId++; 883 + req.expires = gameTime + expire; 884 + @req.plAI = plAI; 885 + 886 + building.insertLast(req); 887 + return req; 888 + } 889 + 890 + ConstructionRequest@ requestConstruction(PlanetAI@ plAI, Object@ buildAt, const ConstructionType@ type, double priority = 1.0, double expire = INFINITY, uint moneyType = BT_Development) { 891 + if(plAI is null) 892 + return null; 893 + 894 + if(log) 895 + ai.print("Requested construction of type "+type.name, plAI.obj); 896 + 897 + ConstructionRequest req(budget, buildAt, type, priority, moneyType); 898 + req.id = nextConstructionRequestId++; 899 + req.expires = gameTime + expire; 900 + @req.plAI = plAI; 901 + 902 + constructionRequests.insertLast(req); 903 + return req; 904 + } 905 + 906 + bool isBuilding(Planet@ planet, const BuildingType@ type) { 907 + for(uint i = 0, cnt = building.length; i < cnt; ++i) { 908 + if(building[i].type is type && building[i].plAI.obj is planet) 909 + return true; 910 + } 911 + return false; 912 + } 913 + 914 + bool isBuilding(Planet@ planet, const ConstructionType@ type) { 915 + for(uint i = 0, cnt = constructionRequests.length; i < cnt; ++i) { 916 + if(constructionRequests[i].type is type && constructionRequests[i].plAI.obj is planet) 917 + return true; 918 + } 919 + return false; 920 + } 921 + 922 + void getColonizeSources(array<PotentialSource@>& sources) { 923 + sources.length = 0; 924 + for(uint i = 0, cnt = planets.length; i < cnt; ++i) { 925 + auto@ plAI = planets[i]; 926 + if(!plAI.obj.valid) 927 + continue; 928 + 929 + double w = plAI.colonizeWeight; 930 + if(w == 0) 931 + continue; 932 + if(plAI.obj.owner !is ai.empire) 933 + continue; 934 + 935 + PotentialSource src; 936 + @src.pl = planets[i].obj; 937 + src.weight = w; 938 + sources.insertLast(src); 939 + } 940 + } 941 + 942 + array<ExportData@> newResources; 943 + array<ExportData@> removedResources; 944 + array<Resource> checkResources; 945 + void updateResourceList(Object@ obj, array<ExportData@>& resList) { 946 + newResources.length = 0; 947 + removedResources = resList; 948 + 949 + checkResources.syncFrom(obj.getNativeResources()); 950 + 951 + uint nativeCnt = checkResources.length; 952 + for(uint i = 0; i < nativeCnt; ++i) { 953 + int id = checkResources[i].id; 954 + 955 + bool found = false; 956 + for(uint n = 0, ncnt = removedResources.length; n < ncnt; ++n) { 957 + if(removedResources[n].resourceId == id) { 958 + removedResources.removeAt(n); 959 + found = true; 960 + break; 961 + } 962 + } 963 + 964 + if(!found) { 965 + auto@ type = checkResources[i].type; 966 + auto@ res = resources.availableResource(obj, type, id); 967 + 968 + if(i == 0) 969 + resList.insertAt(0, res); 970 + else 971 + resList.insertLast(res); 972 + newResources.insertLast(res); 973 + } 974 + else if(i == 0 && resList.length > 1 && resList[0].resourceId != id) { 975 + for(uint n = 0, ncnt = resList.length; n < ncnt; ++n) { 976 + if(resList[n].resourceId == id) { 977 + auto@ res = resList[n]; 978 + resList.removeAt(n); 979 + resList.insertAt(0, res); 980 + break; 981 + } 982 + } 983 + } 984 + } 985 + 986 + //Get rid of resources we no longer have 987 + for(uint i = 0, cnt = removedResources.length; i < cnt; ++i) { 988 + resources.removeResource(removedResources[i]); 989 + resList.remove(removedResources[i]); 990 + } 991 + 992 + //Tell the resources component to try to immediately use the new resources 993 + for(uint i = 0, cnt = newResources.length; i < cnt; ++i) 994 + resources.checkReplaceCurrent(newResources[i]); 995 + } 996 +}; 997 + 998 +AIComponent@ createPlanets() { 999 + return Planets(); 1000 +}
Added scripts/server/empire_ai/weasel/Relations.as.
1 +// Relations 2 +// --------- 3 +// Manages the relationships we have with other empires, including treaties, hatred, and wars. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 + 8 +import empire_ai.weasel.Intelligence; 9 +import empire_ai.weasel.Fleets; 10 +import empire_ai.weasel.Systems; 11 +import empire_ai.weasel.Planets; 12 + 13 +import warandpeace; 14 +import influence; 15 +from influence_global import activeTreaties, influenceLock, joinTreaty, leaveTreaty, declineTreaty, Treaty, sendPeaceOffer, createTreaty, offerSurrender, demandSurrender, leaveTreatiesWith; 16 + 17 +enum HateType { 18 + HT_SystemPresence, 19 + HT_FleetPresence, 20 + HT_COUNT 21 +}; 22 + 23 +class Hate { 24 + uint type; 25 + double amount = 0.0; 26 + Object@ obj; 27 + SystemAI@ sys; 28 + 29 + void save(Relations& relations, SaveFile& file) { 30 + file << type; 31 + file << amount; 32 + file << obj; 33 + relations.systems.saveAI(file, sys); 34 + } 35 + 36 + void load(Relations& relations, SaveFile& file) { 37 + file >> type; 38 + file >> amount; 39 + file >> obj; 40 + @sys = relations.systems.loadAI(file); 41 + } 42 + 43 + bool get_valid() { 44 + if(type == HT_SystemPresence) 45 + return sys !is null; 46 + if(type == HT_FleetPresence) 47 + return obj !is null && sys !is null; 48 + return true; 49 + } 50 + 51 + bool update(AI& ai, Relations& relations, Relation& rel, double time) { 52 + if(type == HT_SystemPresence) { 53 + amount = 0.25; 54 + if(sys.seenPresent & rel.empire.mask == 0) 55 + return false; 56 + if(sys.seenPresent & ai.empire.mask == 0) 57 + return false; 58 + } 59 + else if(type == HT_FleetPresence) { 60 + if(!obj.valid || obj.owner !is rel.empire) 61 + return false; 62 + if(sys.seenPresent & ai.empire.mask == 0) 63 + return false; 64 + if(obj.region !is sys.obj) 65 + return false; 66 + if(obj.getFleetStrength() < 1000.0) 67 + amount = 0.1; 68 + else 69 + amount = 0.5; 70 + } 71 + 72 + rel.hate += amount * time; 73 + return true; 74 + } 75 + 76 + string dump() { 77 + switch(type) { 78 + case HT_SystemPresence: 79 + return "system presence in "+sys.obj.name; 80 + case HT_FleetPresence: 81 + return "fleet presence "+obj.name+" in "+sys.obj.name; 82 + } 83 + return "unknown"; 84 + } 85 +}; 86 + 87 +final class Relation { 88 + Empire@ empire; 89 + 90 + //Whether we've met this empire 91 + bool contacted = false; 92 + 93 + //Whether we're currently at war 94 + bool atWar = false; 95 + 96 + //Last time we tried to make peace 97 + double lastPeaceTry = 0; 98 + 99 + //Whether this is our war of aggression 100 + bool aggressive = false; 101 + 102 + //Whether this is our ally 103 + bool allied = false; 104 + 105 + //Our relationship data 106 + double hate = 0.0; 107 + array<Hate@> hates; 108 + 109 + //Masks 110 + uint borderedTo = 0; 111 + uint alliedTo = 0; 112 + 113 + //Whether we consider this empire a threat to us 114 + bool isThreat = false; 115 + 116 + //How much we would value having this empire as an ally 117 + double allyValue = 0.0; 118 + //How much we think we can beat this empire and all its allies 119 + double defeatable = 0.0; 120 + //Relative strength of this empire to us in a vacuum 121 + double relStrength = 0.0; 122 + 123 + //How much we've lost to them in this recent war 124 + double warLost = 0.0; 125 + //How much we've taken from them in this recent war 126 + double warTaken = 0.0; 127 + 128 + void save(Relations& relations, SaveFile& file) { 129 + file << contacted; 130 + file << atWar; 131 + file << aggressive; 132 + file << allied; 133 + 134 + file << hate; 135 + uint cnt = hates.length; 136 + file << cnt; 137 + for(uint i = 0; i < cnt; ++i) 138 + hates[i].save(relations, file); 139 + 140 + file << borderedTo; 141 + file << alliedTo; 142 + file << isThreat; 143 + file << allyValue; 144 + file << defeatable; 145 + file << relStrength; 146 + file << warTaken; 147 + file << warLost; 148 + file << lastPeaceTry; 149 + } 150 + 151 + void load(Relations& relations, SaveFile& file) { 152 + file >> contacted; 153 + file >> atWar; 154 + file >> aggressive; 155 + file >> allied; 156 + 157 + file >> hate; 158 + uint cnt = 0; 159 + file >> cnt; 160 + for(uint i = 0; i < cnt; ++i) { 161 + Hate ht; 162 + ht.load(relations, file); 163 + if(ht.valid) 164 + hates.insertLast(ht); 165 + } 166 + 167 + file >> borderedTo; 168 + file >> alliedTo; 169 + file >> isThreat; 170 + file >> allyValue; 171 + file >> defeatable; 172 + file >> relStrength; 173 + file >> warTaken; 174 + file >> warLost; 175 + file >> lastPeaceTry; 176 + } 177 + 178 + void trackSystem(AI& ai, Relations& relations, SystemAI@ sys) { 179 + for(uint i = 0, cnt = hates.length; i < cnt; ++i) { 180 + auto@ ht = hates[i]; 181 + if(ht.type != HT_SystemPresence) 182 + continue; 183 + if(ht.sys is sys) 184 + return; 185 + } 186 + 187 + Hate ht; 188 + ht.type = HT_SystemPresence; 189 + @ht.sys = sys; 190 + hates.insertLast(ht); 191 + 192 + if(relations.log) 193 + ai.print("Gain hate of "+empire.name+": "+ht.dump()); 194 + } 195 + 196 + void trackFleet(AI& ai, Relations& relations, FleetIntel@ intel, SystemAI@ sys) { 197 + for(uint i = 0, cnt = hates.length; i < cnt; ++i) { 198 + auto@ ht = hates[i]; 199 + if(ht.type != HT_FleetPresence) 200 + continue; 201 + if(ht.obj is intel.obj && ht.sys is sys) 202 + return; 203 + } 204 + 205 + Hate ht; 206 + ht.type = HT_FleetPresence; 207 + @ht.sys = sys; 208 + @ht.obj = intel.obj; 209 + hates.insertLast(ht); 210 + 211 + if(relations.log) 212 + ai.print("Gain hate of "+empire.name+": "+ht.dump()); 213 + } 214 + 215 + void tick(AI& ai, Relations& relations, double time) { 216 + if(!contacted) { 217 + if(ai.empire.ContactMask & empire.mask != 0) 218 + contacted = true; 219 + } 220 + 221 + bool curWar = ai.empire.isHostile(empire); 222 + if(curWar != atWar) 223 + atWar = curWar; 224 + if(!atWar) { 225 + aggressive = false; 226 + warLost = 0.0; 227 + warTaken = 0.0; 228 + lastPeaceTry = 0.0; 229 + } 230 + 231 + borderedTo = relations.intelligence.get(empire).borderedTo; 232 + alliedTo = empire.mask | empire.mutualDefenseMask | empire.ForcedPeaceMask.value; 233 + 234 + defeatable = relations.intelligence.defeatability(alliedTo, ai.mask | ai.allyMask); 235 + relStrength = relations.intelligence.defeatability(ai.mask, empire.mask); 236 + isThreat = defeatable < 0.8 && (borderedTo & ai.empire.mask) != 0; 237 + 238 + //Check how valuable of an ally this empire would make 239 + allyValue = 1.0; 240 + for(uint i = 0, cnt = relations.relations.length; i < cnt; ++i) { 241 + auto@ other = relations.relations[i]; 242 + if(other is null || other is this || other.empire is null) 243 + continue; 244 + if(other.borderedTo & empire.mask == 0) 245 + continue; 246 + if(alliedTo & empire.mask != 0) 247 + continue; 248 + 249 + if(other.atWar) 250 + allyValue *= 3.0; 251 + else if(other.isThreat) 252 + allyValue *= 1.5; 253 + } 254 + 255 + //Become aggressive here if we're aggressive against one of its allies 256 + if(atWar && !aggressive) { 257 + for(uint i = 0, cnt = relations.relations.length; i < cnt; ++i) { 258 + auto@ other = relations.relations[i]; 259 + if(other is null || other is this || other.empire is null) 260 + continue; 261 + if(other.aggressive && this.alliedTo & other.empire.mask != 0) { 262 + aggressive = true; 263 + break; 264 + } 265 + } 266 + } 267 + 268 + //Update our hatred of them 269 + for(uint i = 0, cnt = hates.length; i < cnt; ++i) { 270 + if(!hates[i].update(ai, relations, this, time)) { 271 + if(relations.log) 272 + ai.print("Hate with "+empire.name+" expired: "+hates[i].dump()); 273 + hates.removeAt(i); 274 + --i; --cnt; 275 + } 276 + } 277 + 278 + hate *= pow(ai.behavior.hateDecayRate, time / 60.0); 279 + if(ai.behavior.biased && !empire.isAI) 280 + hate += 1.0; 281 + 282 + if(ai.behavior.forbidDiplomacy) return; 283 + 284 + //If we really really hate them, declare war 285 + if(!atWar || !aggressive) { 286 + double reqHate = 100.0; 287 + if(defeatable < 1.0) 288 + reqHate *= sqr(1.0 / defeatable); 289 + reqHate *= pow(2.0, relations.warCount()); 290 + 291 + if(hate > reqHate && (!ai.behavior.passive || atWar) && defeatable >= ai.behavior.hatredWarOverkill) { 292 + //Make sure our other requirements for war are met 293 + if(relations.fleets.haveCombatReadyFleets()) { 294 + if(canDeclareWar(ai)) { 295 + if(relations.log) 296 + ai.print("Declaring hatred war on "+empire.name+": "+hate+" / "+reqHate); 297 + if(atWar) 298 + aggressive = true; 299 + else 300 + relations.declareWar(empire, aggressive=true); 301 + } 302 + } 303 + } 304 + } 305 + } 306 + 307 + bool isAllied(AI& ai) { 308 + return alliedTo & ai.empire.mask != 0; 309 + } 310 + 311 + bool canDeclareWar(AI& ai) { 312 + if(empire.SubjugatedBy !is null) 313 + return false; 314 + if(ai.empire.SubjugatedBy !is null) 315 + return false; 316 + if(!contacted) 317 + return false; 318 + if(ai.empire.ForcedPeaceMask & empire.mask != 0) 319 + return false; 320 + return true; 321 + } 322 +}; 323 + 324 +class Relations : AIComponent { 325 + Intelligence@ intelligence; 326 + Systems@ systems; 327 + Fleets@ fleets; 328 + Planets@ planets; 329 + 330 + array<Relation@> relations; 331 + 332 + bool expansionLocked = false; 333 + double treatyRespond = 0; 334 + double treatyConsider = 0; 335 + 336 + double warPoints = 0.0; 337 + 338 + void create() { 339 + @intelligence = cast<Intelligence>(ai.intelligence); 340 + @fleets = cast<Fleets>(ai.fleets); 341 + @systems = cast<Systems>(ai.systems); 342 + @planets = cast<Planets>(ai.planets); 343 + } 344 + 345 + void start() { 346 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 347 + Empire@ emp = getEmpire(i); 348 + if(emp is ai.empire) 349 + continue; 350 + if(!emp.major) 351 + continue; 352 + 353 + Relation r; 354 + @r.empire = emp; 355 + 356 + if(relations.length <= uint(emp.index)) 357 + relations.length = uint(emp.index)+1; 358 + @relations[emp.index] = r; 359 + } 360 + } 361 + 362 + void save(SaveFile& file) { 363 + uint cnt = relations.length; 364 + file << cnt; 365 + for(uint i = 0; i < cnt; ++i) { 366 + if(relations[i] is null) { 367 + file.write0(); 368 + continue; 369 + } 370 + 371 + file.write1(); 372 + relations[i].save(this, file); 373 + } 374 + 375 + file << expansionLocked; 376 + file << treatyRespond; 377 + file << treatyConsider; 378 + } 379 + 380 + void load(SaveFile& file) { 381 + uint cnt = 0; 382 + file >> cnt; 383 + relations.length = cnt; 384 + 385 + for(uint i = 0; i < cnt; ++i) { 386 + if(!file.readBit()) 387 + continue; 388 + 389 + @relations[i] = Relation(); 390 + @relations[i].empire = getEmpire(i); 391 + relations[i].load(this, file); 392 + } 393 + 394 + file >> expansionLocked; 395 + file >> treatyRespond; 396 + file >> treatyConsider; 397 + } 398 + 399 + double getPointValue(Object@ obj) { 400 + if(obj is null) 401 + return 0.0; 402 + if(obj.isShip) { 403 + auto@ dsg = cast<Ship>(obj).blueprint.design; 404 + if(dsg !is null) 405 + return dsg.size; 406 + } 407 + else if(obj.isPlanet) { 408 + return 10.0 * pow(3.0, double(obj.level)); 409 + } 410 + return 0.0; 411 + } 412 + 413 + void recordTakenFrom(Empire& emp, double amount) { 414 + if(!emp.valid) 415 + return; 416 + if(log) 417 + ai.print("Taken value "+amount+" from "+emp.name); 418 + auto@ rel = get(emp); 419 + if(rel !is null) 420 + rel.warTaken += amount; 421 + } 422 + 423 + void recordLostTo(Empire& emp, double amount) { 424 + if(!emp.valid) 425 + return; 426 + if(log) 427 + ai.print("Lost value "+amount+" to "+emp.name); 428 + auto@ rel = get(emp); 429 + if(rel !is null) 430 + rel.warLost += amount; 431 + } 432 + 433 + void recordLostTo(Empire& emp, Object@ obj) { 434 + recordLostTo(emp, getPointValue(obj)); 435 + } 436 + 437 + void recordTakenFrom(Empire& emp, Object@ obj) { 438 + recordTakenFrom(emp, getPointValue(obj)); 439 + } 440 + 441 + Relation@ get(Empire@ emp) { 442 + if(emp is null) 443 + return null; 444 + if(!emp.major) 445 + return null; 446 + if(uint(emp.index) >= relations.length) 447 + return null; 448 + return relations[emp.index]; 449 + } 450 + 451 + bool isFightingWar(bool aggressive = false) { 452 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 453 + if(relations[i] is null) 454 + continue; 455 + if(relations[i].atWar) { 456 + if(!aggressive || relations[i].aggressive) 457 + return true; 458 + } 459 + } 460 + return false; 461 + } 462 + 463 + uint warCount() { 464 + uint count = 0; 465 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 466 + if(relations[i] is null) 467 + continue; 468 + if(relations[i].atWar) 469 + count += 1; 470 + } 471 + return count; 472 + } 473 + 474 + void declareWar(Empire@ onEmpire, bool aggressive = true) { 475 + //Break all treaties 476 + leaveTreatiesWith(ai.empire, onEmpire.mask); 477 + 478 + //Declare actual war 479 + auto@ rel = get(onEmpire); 480 + rel.aggressive = aggressive; 481 + ::declareWar(ai.empire, onEmpire); 482 + } 483 + 484 + uint sysIdx = 0; 485 + uint relIdx = 0; 486 + void tick(double time) override { 487 + //Find new ways to hate other empires 488 + if(systems.all.length != 0) { 489 + sysIdx = (sysIdx+1) % systems.all.length; 490 + auto@ sys = systems.all[sysIdx]; 491 + if(sys.owned && sys.seenPresent & ~ai.mask != 0) { 492 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 493 + auto@ rel = relations[i]; 494 + if(rel is null) 495 + continue; 496 + if(sys.seenPresent & rel.empire.mask != 0) 497 + rel.trackSystem(ai, this, sys); 498 + } 499 + } 500 + } 501 + 502 + if(relations.length != 0) { 503 + relIdx = (relIdx+1) % relations.length; 504 + auto@ rel = relations[relIdx]; 505 + auto@ itl = intelligence.intel[relIdx]; 506 + if(rel !is null && itl !is null) { 507 + for(uint i = 0, cnt = itl.fleets.length; i < cnt; ++i) { 508 + if(!itl.fleets[i].visible) 509 + continue; 510 + 511 + auto@ inSys = systems.getAI(itl.fleets[i].obj.region); 512 + if(inSys !is null && inSys.owned) 513 + rel.trackFleet(ai, this, itl.fleets[i], inSys); 514 + } 515 + } 516 + } 517 + } 518 + 519 + uint relInd = 0; 520 + void focusTick(double time) override { 521 + //Update our current relations 522 + if(relations.length != 0) { 523 + relInd = (relInd+1) % relations.length; 524 + if(relations[relInd] !is null) 525 + relations[relInd].tick(ai, this, time); 526 + } 527 + 528 + //Compute how many points we have in total that can be taken 529 + warPoints = 0.0; 530 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 531 + Ship@ ship = cast<Ship>(fleets.fleets[i].obj); 532 + if(ship !is null && ship.valid && ship.owner is ai.empire) 533 + warPoints += getPointValue(ship); 534 + } 535 + for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) { 536 + Planet@ pl = cast<Planet>(planets.planets[i].obj); 537 + if(pl !is null && pl.valid && pl.owner is ai.empire) 538 + warPoints += getPointValue(pl); 539 + } 540 + 541 + //Become aggressive if we cannot expand anywhere anymore 542 + expansionLocked = true; 543 + for(uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) { 544 + auto@ sys = systems.outsideBorder[i]; 545 + if(sys.seenPresent == 0) { 546 + bool havePlanets = false; 547 + for(uint n = 0, ncnt = sys.planets.length; n < ncnt; ++n) { 548 + if(sys.planets[n].quarantined) 549 + continue; 550 + havePlanets = true; 551 + break; 552 + } 553 + if(havePlanets) { 554 + expansionLocked = false; 555 + break; 556 + } 557 + } 558 + } 559 + 560 + if(ai.behavior.forbidDiplomacy) return; 561 + 562 + //Deal with our AI's aggressive behavior 563 + if(ai.behavior.aggressive || (expansionLocked && ai.behavior.aggressiveWhenBoxedIn && !ai.behavior.passive)) { 564 + //Try to make sure we're always fighting at least one aggressive war 565 + bool atWar = false, aggro = false; 566 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 567 + if(relations[i] is null) 568 + continue; 569 + if(relations[i].atWar) { 570 + atWar = true; 571 + if(relations[i].aggressive) 572 + aggro = true; 573 + } 574 + } 575 + 576 + if(!atWar) { 577 + if(fleets.haveCombatReadyFleets()) { 578 + //Declare war on people who share our border and are defeatable 579 + Empire@ best; 580 + double bestWeight = 0; 581 + 582 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 583 + auto@ rel = relations[i]; 584 + if(rel is null) 585 + continue; 586 + 587 + auto@ intel = intelligence.get(rel.empire); 588 + if(intel.shared.length == 0 && intel.theirBorder.length == 0) 589 + continue; 590 + if(!rel.canDeclareWar(ai)) 591 + continue; 592 + if(!ai.behavior.biased || rel.empire.isAI) { 593 + if(rel.defeatable < ai.behavior.aggressiveWarOverkill) 594 + continue; 595 + } 596 + 597 + double w = rel.defeatable * rel.hate; 598 + if(rel.isAllied(ai)) 599 + w *= 0.01; 600 + if(w > bestWeight) { 601 + bestWeight = w; 602 + @best = rel.empire; 603 + } 604 + } 605 + 606 + if(best !is null) { 607 + if(log) 608 + ai.print("Declare aggressive war against "+best.name); 609 + declareWar(best, aggressive=true); 610 + } 611 + } 612 + } 613 + else if(!aggro) { 614 + //Start going aggressive on someone defeatable we are already at war with 615 + Empire@ best; 616 + double bestWeight = 0; 617 + 618 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 619 + auto@ rel = relations[i]; 620 + if(rel is null) 621 + continue; 622 + if(!rel.atWar) 623 + continue; 624 + if(rel.defeatable < ai.behavior.aggressiveWarOverkill) 625 + continue; 626 + 627 + double w = rel.defeatable * rel.hate; 628 + if(w > bestWeight) { 629 + bestWeight = w; 630 + @best = rel.empire; 631 + } 632 + } 633 + 634 + if(best !is null) { 635 + //Go aggressive then! 636 + if(log) 637 + ai.print("Become aggressive against "+best.name); 638 + get(best).aggressive = true; 639 + } 640 + } 641 + } 642 + 643 + //Respond to treaties 644 + if(gameTime > treatyRespond) { 645 + treatyRespond = gameTime + randomd(8.0, 20.0); 646 + 647 + Treaty@ respondTreaty; 648 + 649 + { 650 + Lock lck(influenceLock); 651 + for(uint i = 0, cnt = activeTreaties.length; i < cnt; ++i) { 652 + auto@ trty = activeTreaties[i]; 653 + if(trty.inviteMask & ai.mask != 0 && trty.presentMask & ai.mask == 0) { 654 + Message msg; 655 + trty.write(msg); 656 + 657 + @respondTreaty = Treaty(); 658 + respondTreaty.read(msg); 659 + break; 660 + } 661 + } 662 + } 663 + 664 + if(respondTreaty !is null) { 665 + bool accept = false; 666 + Empire@ invitedBy = respondTreaty.leader; 667 + if(invitedBy is null) 668 + @invitedBy = respondTreaty.joinedEmpires[0]; 669 + Relation@ other = get(invitedBy); 670 + 671 + if(respondTreaty.hasClause("SubjugateClause")) { 672 + //This is a surrender offer or demand 673 + if(respondTreaty.leader is null) { 674 + //An offer 675 + accept = true; 676 + } 677 + else if(respondTreaty.joinedEmpires.length != 0) { 678 + //A demand 679 + auto@ other = get(respondTreaty.joinedEmpires[0]); 680 + if(other.defeatable < ai.behavior.surrenderMinStrength) { 681 + if(warPoints / (other.warLost + warPoints) < ai.behavior.acceptSurrenderRatio) { 682 + accept = true; 683 + } 684 + } 685 + } 686 + } 687 + else if(respondTreaty.hasClause("MutualDefenseClause") 688 + || respondTreaty.hasClause("AllianceClause")) { 689 + //This is an alliance treaty 690 + if(other.atWar) { 691 + //Need to be at peace first 692 + accept = false; 693 + } 694 + else { 695 + //See if this empire can help us defeat someone 696 + if(other.allyValue >= 3.0 && other.relStrength >= 0.5) 697 + accept = true; 698 + } 699 + } 700 + else if(respondTreaty.hasClause("PeaceClause")) { 701 + //This is a peace offering 702 + accept = shouldPeace(other); 703 + } 704 + else if(respondTreaty.hasClause("VisionClause")) { 705 + //This is a vision sharing treaty 706 + if(other !is null) 707 + accept = !other.isThreat && !other.atWar && other.hate <= 50.0; 708 + } 709 + else if(respondTreaty.hasClause("TradeClause")) { 710 + //This is a trade sharing treaty 711 + if(other !is null) 712 + accept = !other.isThreat && !other.atWar && other.hate <= 10.0; 713 + } 714 + 715 + if(accept) { 716 + if(log) 717 + ai.print("Accept treaty: "+respondTreaty.name, emp=invitedBy); 718 + joinTreaty(ai.empire, respondTreaty.id); 719 + } 720 + else { 721 + if(log) 722 + ai.print("Reject treaty: "+respondTreaty.name, emp=invitedBy); 723 + declineTreaty(ai.empire, respondTreaty.id); 724 + } 725 + } 726 + } 727 + 728 + //See if we should send a treaty over to someone 729 + if(gameTime > treatyConsider) { 730 + treatyConsider = gameTime + randomd(100.0, 300.0); 731 + 732 + uint offset = randomi(0, relations.length-1); 733 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 734 + auto@ other = relations[(i+offset) % cnt]; 735 + if(other is null) 736 + continue; 737 + 738 + //Check if we should make peace with them 739 + if(other.atWar) { 740 + if(other.lastPeaceTry < gameTime - 600.0 && shouldPeace(other, isOffer=true)) { 741 + if(other.aggressive) 742 + other.aggressive = false; 743 + if(log) 744 + ai.print("Send peace offer.", emp=other.empire); 745 + other.lastPeaceTry = gameTime; 746 + sendPeaceOffer(ai.empire, other.empire); 747 + break; 748 + } 749 + } 750 + 751 + if(other.atWar) { 752 + //Check if we should try to surrender to them 753 + if(other.defeatable < ai.behavior.surrenderMinStrength) { 754 + if(warPoints / (other.warLost + warPoints) < ai.behavior.offerSurrenderRatio) { 755 + if(log) 756 + ai.print("Send surrender offer.", emp=other.empire); 757 + offerSurrender(ai.empire, other.empire); 758 + break; 759 + } 760 + } 761 + 762 + //Check if we should try to demand their surrender 763 + if(other.defeatable >= 1.0 / ai.behavior.surrenderMinStrength && other.warTaken >= warPoints * 0.1) { 764 + if(log) 765 + ai.print("Demand surrender.", emp=other.empire); 766 + demandSurrender(ai.empire, other.empire); 767 + break; 768 + } 769 + } 770 + 771 + //Check if we should try to ally with them 772 + if(!other.atWar && !other.isThreat && other.allyValue >= 3.0) { 773 + Treaty treaty; 774 + treaty.addClause(getInfluenceClauseType("AllianceClause")); 775 + treaty.addClause(getInfluenceClauseType("VisionClause")); 776 + treaty.addClause(getInfluenceClauseType("MutualDefenseClause")); 777 + 778 + if(treaty.canInvite(ai.empire, other.empire)) { 779 + treaty.inviteMask = other.empire.mask; 780 + 781 + //Generate treaty name 782 + string genName; 783 + uint genCount = 0; 784 + for(uint i = 0, cnt = systemCount; i < cnt; ++i) { 785 + auto@ reg = getSystem(i).object; 786 + if(reg.TradeMask & (ai.mask | other.empire.mask) != 0) { 787 + genCount += 1; 788 + if(randomd() < 1.0 / double(genCount)) 789 + genName = reg.name; 790 + } 791 + } 792 + treaty.name = format(locale::TREATY_NAME_GEN, genName); 793 + 794 + if(log) 795 + ai.print("Send alliance offer.", emp=other.empire); 796 + createTreaty(ai.empire, treaty); 797 + } 798 + } 799 + } 800 + } 801 + } 802 + 803 + bool shouldPeace(Relation@ other, bool isOffer = false) { 804 + bool accept = false; 805 + if(other.aggressive) { 806 + //We're trying to conquer these people, don't accept peace unless 807 + //we're fighting someone scarier or we're losing 808 + double otherWar = 0.0; 809 + uint otherInd = uint(-1); 810 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 811 + auto@ rel = relations[i]; 812 + if(rel is null || rel is other) 813 + continue; 814 + if(rel.empire.mask & other.alliedTo != 0) 815 + continue; 816 + if(!rel.atWar) 817 + continue; 818 + otherWar = max(otherWar, rel.defeatable); 819 + otherInd = i; 820 + } 821 + 822 + if(otherInd != uint(-1) && otherWar < other.defeatable) { 823 + accept = true; 824 + if(!relations[otherInd].aggressive) 825 + relations[otherInd].aggressive = otherWar >= ai.behavior.aggressiveWarOverkill; 826 + } 827 + else if(other.defeatable < 0.25) { 828 + accept = true; 829 + } 830 + } 831 + else { 832 + //We don't have any ~particular qualms with these people, peace should be good 833 + if(!isOffer) { 834 + if(other.defeatable < 0.5 || other.hate < 50.0) 835 + accept = true; 836 + } 837 + } 838 + return accept; 839 + } 840 + 841 + void turn() override { 842 + if(log) { 843 + ai.print("Relations Report on Empires:"); 844 + ai.print(" war points: "+warPoints); 845 + for(uint i = 0, cnt = relations.length; i < cnt; ++i) { 846 + auto@ rel = relations[i]; 847 + if(rel is null) 848 + continue; 849 + ai.print(" "+ai.pad(rel.empire.name, 15) 850 + +" war: "+ai.pad(rel.atWar+" / "+rel.aggressive, 15) 851 + +" threat: "+ai.pad(""+rel.isThreat, 8) 852 + +" defeatable: "+ai.pad(toString(rel.defeatable,2), 8) 853 + +" hate: "+ai.pad(toString(rel.hate,0), 8) 854 + +" ally value: "+ai.pad(toString(rel.allyValue,1), 8) 855 + +" taken: "+ai.pad(toString(rel.warTaken,1), 8) 856 + +" lost: "+ai.pad(toString(rel.warLost,1), 8) 857 + ); 858 + } 859 + } 860 + } 861 +}; 862 + 863 +AIComponent@ createRelations() { 864 + return Relations(); 865 +} 866 + 867 +void relationRecordLost(AI& ai, Empire& emp, Object@ obj) { 868 + cast<Relations>(ai.relations).recordLostTo(emp, obj); 869 +}
Added scripts/server/empire_ai/weasel/Research.as.
1 +// Research 2 +// -------- 3 +// Spends research points to unlock and improve things in the research grid. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 + 8 +import empire_ai.weasel.Development; 9 + 10 +import research; 11 + 12 +const double baseAimResearchRate = 2.0; 13 + 14 +class Research : AIComponent { 15 + Development@ development; 16 + 17 + TechnologyGrid grid; 18 + array<TechnologyNode@> immediateQueue; 19 + 20 + void create() { 21 + @development = cast<Development>(ai.development); 22 + } 23 + 24 + void save(SaveFile& file) { 25 + uint cnt = immediateQueue.length; 26 + file << cnt; 27 + for(uint i = 0; i < cnt; ++i) 28 + file << immediateQueue[i].id; 29 + } 30 + 31 + void load(SaveFile& file) { 32 + updateGrid(); 33 + 34 + uint cnt = 0; 35 + file >> cnt; 36 + for(uint i = 0; i < cnt; ++i) { 37 + int id = 0; 38 + file >> id; 39 + 40 + for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) { 41 + if(grid.nodes[i].id == id) { 42 + immediateQueue.insertLast(grid.nodes[i]); 43 + break; 44 + } 45 + } 46 + } 47 + 48 + } 49 + 50 + void updateGrid() { 51 + //Receive the full grid from the empire to path on 52 + grid.nodes.length = 0; 53 + 54 + DataList@ recvData = ai.empire.getTechnologyNodes(); 55 + TechnologyNode@ node = TechnologyNode(); 56 + while(receive(recvData, node)) { 57 + grid.nodes.insertLast(node); 58 + @node = TechnologyNode(); 59 + } 60 + 61 + grid.regenBounds(); 62 + } 63 + 64 + double getEndPointWeight(const TechnologyType& tech) { 65 + //TODO: Might want to make this configurable by data file 66 + return 1.0; 67 + } 68 + 69 + bool isEndPoint(const TechnologyType& tech) { 70 + return tech.cls >= Tech_BigUpgrade; 71 + } 72 + 73 + double findResearch(int atIndex, array<TechnologyNode@>& path, array<bool>& visited, bool initial = false) { 74 + if(visited[atIndex]) 75 + return 0.0; 76 + visited[atIndex] = true; 77 + 78 + auto@ node = grid.nodes[atIndex]; 79 + if(!initial) { 80 + if(node.bought) 81 + return 0.0; 82 + if(!node.hasRequirements(ai.empire)) 83 + return 0.0; 84 + 85 + path.insertLast(node); 86 + 87 + if(isEndPoint(node.type)) 88 + return getEndPointWeight(node.type); 89 + } 90 + 91 + vec2i startPos = node.position; 92 + double totalWeight = 0.0; 93 + 94 + array<TechnologyNode@> tmp; 95 + array<TechnologyNode@> chosen; 96 + tmp.reserve(20); 97 + chosen.reserve(20); 98 + 99 + for(uint d = 0; d < 6; ++d) { 100 + vec2i otherPos = startPos; 101 + if(grid.doAdvance(otherPos, HexGridAdjacency(d))) { 102 + int otherIndex = grid.getIndex(otherPos); 103 + if(otherIndex != -1) { 104 + tmp.length = 0; 105 + double w = findResearch(otherIndex, tmp, visited); 106 + if(w != 0.0) { 107 + totalWeight += w; 108 + if(randomd() < w / totalWeight) { 109 + chosen = tmp; 110 + } 111 + } 112 + } 113 + } 114 + } 115 + 116 + for(uint i = 0, cnt = chosen.length; i < cnt; ++i) 117 + path.insertLast(chosen[i]); 118 + return max(totalWeight, 0.01); 119 + } 120 + 121 + void queueNewResearch() { 122 + if(log) 123 + ai.print("Attempted to find new research to queue"); 124 + 125 + //Update our grid representation 126 + updateGrid(); 127 + 128 + //Find a good path to do 129 + array<bool> visited(grid.nodes.length, false); 130 + 131 + double totalWeight = 0.0; 132 + 133 + auto@ path = array<TechnologyNode@>(); 134 + auto@ tmp = array<TechnologyNode@>(); 135 + path.reserve(20); 136 + tmp.reserve(20); 137 + 138 + for(int i = 0, cnt = grid.nodes.length; i < cnt; ++i) { 139 + if(grid.nodes[i].bought) { 140 + tmp.length = 0; 141 + double weight = findResearch(i, tmp, visited, initial=true); 142 + if(weight != 0.0) { 143 + totalWeight += weight; 144 + if(randomd() < weight / totalWeight) { 145 + auto@ swp = path; 146 + @path = tmp; 147 + @tmp = swp; 148 + } 149 + } 150 + } 151 + } 152 + 153 + if(path.length != 0) { 154 + for(uint i = 0, cnt = path.length; i < cnt; ++i) { 155 + if(log) 156 + ai.print("Queue research: "+path[i].type.name+" at "+path[i].position); 157 + immediateQueue.insertLast(path[i]); 158 + } 159 + } 160 + } 161 + 162 + double immTimer = randomd(10.0, 60.0); 163 + void focusTick(double time) override { 164 + if (ai.behavior.forbidResearch) return; 165 + 166 + //Queue some new research if we have to 167 + if(immediateQueue.length == 0) { 168 + immTimer -= time; 169 + if(immTimer <= 0.0) { 170 + immTimer = 60.0; 171 + queueNewResearch(); 172 + } 173 + } 174 + else { 175 + immTimer = 0.0; 176 + } 177 + 178 + //Deal with current queued research 179 + if(immediateQueue.length != 0) { 180 + auto@ node = immediateQueue[0]; 181 + if(!receive(ai.empire.getTechnologyNode(node.id), node)) { 182 + immediateQueue.removeAt(0); 183 + } 184 + else if(!node.available || node.bought) { 185 + immediateQueue.removeAt(0); 186 + } 187 + else { 188 + double cost = node.getPointCost(ai.empire); 189 + if(cost == 0) { 190 + //Try it once and then give up 191 + ai.empire.research(node.id, secondary=true); 192 + immediateQueue.removeAt(0); 193 + 194 + if(log) 195 + ai.print("Attempt secondary research: "+node.type.name+" at "+node.position); 196 + } 197 + else if(cost <= ai.empire.ResearchPoints) { 198 + //If we have enough to buy it, buy it 199 + ai.empire.research(node.id); 200 + immediateQueue.removeAt(0); 201 + 202 + if(log) 203 + ai.print("Purchase research: "+node.type.name+" at "+node.position); 204 + } 205 + } 206 + } 207 + 208 + //Update research generation rate goal 209 + development.aimResearchRate = clamp(gameTime / (20.0 * 60.0) - 0.5, 0.0, baseAimResearchRate); 210 + } 211 +}; 212 + 213 +AIComponent@ createResearch() { 214 + return Research(); 215 +}
Added scripts/server/empire_ai/weasel/Resources.as.
1 +import empire_ai.weasel.WeaselAI; 2 + 3 +import empire_ai.weasel.Events; 4 + 5 +import empire_ai.weasel.ImportData; 6 + 7 +import ai.events; 8 + 9 +import resources; 10 +import planet_levels; 11 +import system_pathing; 12 +import systems; 13 + 14 +from orbitals import OrbitalModule; 15 + 16 +interface RaceResources { 17 + void levelRequirements(Object& obj, int targetLevel, array<ResourceSpec@>& specs); 18 +}; 19 + 20 +final class Resources : AIComponent { 21 + Events@ events; 22 + 23 + RaceResources@ race; 24 + 25 + array<ImportData@> requested; 26 + array<ImportData@> active; 27 + int nextImportId = 0; 28 + 29 + array<ExportData@> available; 30 + array<ExportData@> used; 31 + int nextExportId = 0; 32 + 33 + void create() { 34 + @events = cast<Events>(ai.events); 35 + @race = cast<RaceResources>(ai.race); 36 + } 37 + 38 + void save(SaveFile& file) { 39 + file << nextImportId; 40 + file << nextExportId; 41 + 42 + uint cnt = 0; 43 + 44 + cnt = requested.length; 45 + file << cnt; 46 + for(uint i = 0; i < cnt; ++i) { 47 + saveImport(file, requested[i]); 48 + file << requested[i]; 49 + } 50 + 51 + cnt = active.length; 52 + file << cnt; 53 + for(uint i = 0; i < cnt; ++i) { 54 + saveImport(file, active[i]); 55 + file << active[i]; 56 + } 57 + 58 + cnt = available.length; 59 + file << cnt; 60 + for(uint i = 0; i < cnt; ++i) { 61 + saveExport(file, available[i]); 62 + file << available[i]; 63 + saveImport(file, available[i].request); 64 + } 65 + 66 + cnt = used.length; 67 + file << cnt; 68 + for(uint i = 0; i < cnt; ++i) { 69 + saveExport(file, used[i]); 70 + file << used[i]; 71 + saveImport(file, used[i].request); 72 + } 73 + } 74 + 75 + void load(SaveFile& file) { 76 + file >> nextImportId; 77 + file >> nextExportId; 78 + 79 + uint cnt = 0; 80 + 81 + file >> cnt; 82 + for(uint i = 0; i < cnt; ++i) { 83 + auto@ data = loadImport(file); 84 + file >> data; 85 + requested.insertLast(data); 86 + } 87 + 88 + file >> cnt; 89 + for(uint i = 0; i < cnt; ++i) { 90 + auto@ data = loadImport(file); 91 + file >> data; 92 + active.insertLast(data); 93 + } 94 + 95 + file >> cnt; 96 + for(uint i = 0; i < cnt; ++i) { 97 + auto@ data = loadExport(file); 98 + file >> data; 99 + @data.request = loadImport(file); 100 + available.insertLast(data); 101 + } 102 + 103 + file >> cnt; 104 + for(uint i = 0; i < cnt; ++i) { 105 + auto@ data = loadExport(file); 106 + file >> data; 107 + @data.request = loadImport(file); 108 + used.insertLast(data); 109 + } 110 + } 111 + 112 + array<ImportData@> importIds; 113 + ImportData@ loadImport(int id) { 114 + if(id == -1) 115 + return null; 116 + for(uint i = 0, cnt = importIds.length; i < cnt; ++i) { 117 + if(importIds[i].id == id) 118 + return importIds[i]; 119 + } 120 + ImportData data; 121 + data.id = id; 122 + importIds.insertLast(data); 123 + return data; 124 + } 125 + ImportData@ loadImport(SaveFile& file) { 126 + int id = -1; 127 + file >> id; 128 + if(id == -1) 129 + return null; 130 + else 131 + return loadImport(id); 132 + } 133 + void saveImport(SaveFile& file, ImportData@ data) { 134 + int id = -1; 135 + if(data !is null) 136 + id = data.id; 137 + file << id; 138 + } 139 + array<ExportData@> exportIds; 140 + ExportData@ loadExport(int id) { 141 + if(id == -1) 142 + return null; 143 + for(uint i = 0, cnt = exportIds.length; i < cnt; ++i) { 144 + if(exportIds[i].id == id) 145 + return exportIds[i]; 146 + } 147 + ExportData data; 148 + data.id = id; 149 + exportIds.insertLast(data); 150 + return data; 151 + } 152 + ExportData@ loadExport(SaveFile& file) { 153 + int id = -1; 154 + file >> id; 155 + if(id == -1) 156 + return null; 157 + else 158 + return loadExport(id); 159 + } 160 + void saveExport(SaveFile& file, ExportData@ data) { 161 + int id = -1; 162 + if(data !is null) 163 + id = data.id; 164 + file << id; 165 + } 166 + void postLoad(AI& ai) { 167 + importIds.length = 0; 168 + exportIds.length = 0; 169 + } 170 + 171 + void start() { 172 + focusTick(0); 173 + } 174 + 175 + void tick(double time) { 176 + } 177 + 178 + uint checkIdx = 0; 179 + void focusTick(double time) { 180 + //Do a check to make sure our resource export setup is still correct 181 + if(used.length != 0) { 182 + checkIdx = (checkIdx+1) % used.length; 183 + ExportData@ res = used[checkIdx]; 184 + if(res.request !is null && res.request.obj !is null && !res.isExportedTo(res.request.obj)) { 185 + if(log) 186 + ai.print("Break export to "+res.request.obj.name+": link changed underfoot", res.obj); 187 + breakImport(res); 188 + } 189 + else { 190 + bool valid = true; 191 + if(res.obj is null || res.obj.owner !is ai.empire || !res.obj.valid) 192 + valid = false; 193 + //Don't break these imports, we want to wait for the decay to happen 194 + else if((res.request is null || !res.request.obj.hasSurfaceComponent || res.request.obj.decayTime <= 0) && !res.obj.isAsteroid && !res.usable) { 195 + valid = false; 196 + } 197 + else if(res.request !is null) { 198 + if(res.request.obj.owner !is ai.empire || !res.request.obj.valid) 199 + valid = false; 200 + } 201 + if(!valid) 202 + breakImport(res); 203 + } 204 + 205 + } 206 + 207 + //TODO: Make sure universal unique only applies once per planet 208 + 209 + //Match requested with available 210 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 211 + auto@ req = requested[i]; 212 + req.cycled = true; 213 + 214 + if(req.obj is null) 215 + continue; 216 + if(req.beingMet) { 217 + ai.print("Error: Requested is being met", req.obj); 218 + continue; 219 + } 220 + 221 + ExportData@ source; 222 + double sourceWeight = 0.0; 223 + 224 + for(uint j = 0, jcnt = available.length; j < jcnt; ++j) { 225 + auto@ av = available[j]; 226 + if(av.request !is null) { 227 + ai.print("Error: Available is being used", av.obj); 228 + continue; 229 + } 230 + 231 + if(!req.spec.meets(av.resource, av.obj, req.obj)) 232 + continue; 233 + if(!av.usable || av.obj is null || !av.obj.valid || av.obj.owner !is ai.empire) 234 + continue; 235 + //Check if a trade route exists between the two locations 236 + if(!canTradeBetween(av.obj, req.obj) && av.obj.region !is null && req.obj.region !is null) { 237 + auto@ territoryA = av.obj.region.getTerritory(ai.empire); 238 + auto@ territoryB = req.obj.region.getTerritory(ai.empire); 239 + if (territoryA !is territoryB) { 240 + if (log) 241 + ai.print("trade route requested between " + addrstr(territoryA) + " and " + addrstr(territoryB)); 242 + events.notifyTradeRouteNeeded(this, TradeRouteNeededEventArgs(territoryA, territoryB)); 243 + } 244 + continue; 245 + } 246 + if(av.localOnly && av.obj !is req.obj) 247 + continue; 248 + 249 + double weight = 1.0; 250 + if(req.obj is av.obj) 251 + weight = INFINITY; 252 + 253 + if(weight > sourceWeight) { 254 + sourceWeight = weight; 255 + @source = av; 256 + } 257 + } 258 + 259 + if(source !is null) { 260 + link(req, source); 261 + --i; --cnt; 262 + } 263 + } 264 + } 265 + 266 + void turn() { 267 + } 268 + 269 + bool get_hasOpenRequests() { 270 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 271 + auto@ req = requested[i]; 272 + if(req.isOpen) 273 + return true; 274 + } 275 + return false; 276 + } 277 + 278 + TradePath tradePather; 279 + int tradeDistance(Region& fromRegion, Region& toRegion) { 280 + @tradePather.forEmpire = ai.empire; 281 + tradePather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true); 282 + if(!tradePather.valid) 283 + return -1; 284 + return tradePather.pathSize - 1; 285 + } 286 + 287 + bool canTradeBetween(Object& fromObj, Object& toObj) { 288 + Region@ fromRegion = fromObj.region; 289 + if(fromRegion is null) 290 + return false; 291 + Region@ toRegion = toObj.region; 292 + if(toRegion is null) 293 + return false; 294 + return canTradeBetween(fromRegion, toRegion); 295 + } 296 + 297 + bool canTradeBetween(Region& fromRegion, Region& toRegion) { 298 + if(fromRegion.sharesTerritory(ai.empire, toRegion)) 299 + return true; 300 + int dist = tradeDistance(fromRegion, toRegion); 301 + if(dist < 0) 302 + return false; 303 + return true; 304 + } 305 + 306 + void link(ImportData@ req, ExportData@ source) { 307 + //Manage the data 308 + @source.request = req; 309 + @source.developUse = null; 310 + req.set(source); 311 + 312 + requested.remove(req); 313 + active.insertLast(req); 314 + 315 + req.beingMet = true; 316 + 317 + available.remove(source); 318 + used.insertLast(source); 319 + 320 + if(log) 321 + ai.print("link "+source.resource.name+" from "+source.obj.name+" to "+req.obj.name); 322 + 323 + //Perform the actual export 324 + if(source.obj !is req.obj) 325 + source.obj.exportResourceByID(source.resourceId, req.obj); 326 + else 327 + source.obj.exportResourceByID(source.resourceId, null); 328 + } 329 + 330 + ImportData@ requestResource(Object& toObject, ResourceSpec& spec, bool forLevel = false, bool activate = true, bool prioritize = false) { 331 + ImportData data; 332 + data.idleSince = gameTime; 333 + data.id = nextImportId++; 334 + @data.obj = toObject; 335 + @data.spec = spec; 336 + data.forLevel = forLevel; 337 + 338 + if(log) 339 + ai.print("requested resource: "+spec.dump(), toObject); 340 + 341 + if(activate) { 342 + if(prioritize) 343 + requested.insertAt(0, data); 344 + else 345 + requested.insertLast(data); 346 + } 347 + return data; 348 + } 349 + 350 + ExportData@ availableResource(Object& fromObject, const ResourceType& resource, int id) { 351 + ExportData data; 352 + data.id = nextExportId++; 353 + @data.obj = fromObject; 354 + @data.resource = resource; 355 + data.resourceId = id; 356 + 357 + if(log) 358 + ai.print("available resource: "+resource.name, fromObject); 359 + 360 + available.insertLast(data); 361 + return data; 362 + } 363 + 364 + void checkReplaceCurrent(ExportData@ res) { 365 + //If the planet that this resource is on is currently importing this same resource, switch it around 366 + if(res.request !is null) 367 + return; 368 + 369 + for(uint i = 0, cnt = used.length; i < cnt; ++i) { 370 + auto@ other = used[i]; 371 + auto@ request = other.request; 372 + if(request is null) 373 + continue; 374 + if(request.obj !is res.obj) 375 + continue; 376 + 377 + if(request.spec.meets(res.resource, res.obj, res.obj)) { 378 + //Swap the import with using the local resource 379 + if(other.resource.exportable) { 380 + breakImport(other); 381 + link(request, res); 382 + return; 383 + } 384 + } 385 + } 386 + } 387 + 388 + array<Resource> checkResources; 389 + array<ExportData@>@ availableResource(Object& fromObject) { 390 + array<ExportData@> list; 391 + 392 + checkResources.syncFrom(fromObject.getNativeResources()); 393 + 394 + uint nativeCount = checkResources.length; 395 + for(uint i = 0; i < nativeCount; ++i) { 396 + auto@ r = checkResources[i].type; 397 + if(r !is null) 398 + list.insertLast(availableResource(fromObject, r, checkResources[i].id)); 399 + } 400 + 401 + return list; 402 + } 403 + 404 + ExportData@ findResource(Object@ obj, int resourceId) { 405 + for(uint i = 0, cnt = available.length; i < cnt; ++i) { 406 + if(available[i].obj is obj && available[i].resourceId == resourceId) 407 + return available[i]; 408 + } 409 + for(uint i = 0, cnt = used.length; i < cnt; ++i) { 410 + if(used[i].obj is obj && used[i].resourceId == resourceId) 411 + return used[i]; 412 + } 413 + return null; 414 + } 415 + 416 + ImportData@ findUnclaimed(ExportData@ forResource) { 417 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 418 + auto@ req = requested[i]; 419 + if(req.claimedFor) 420 + continue; 421 + if(req.beingMet) 422 + continue; 423 + 424 + if(!req.spec.meets(forResource.resource, forResource.obj, req.obj)) 425 + continue; 426 + if(!canTradeBetween(req.obj, forResource.obj)) 427 + continue; 428 + 429 + return req; 430 + } 431 + return null; 432 + } 433 + 434 + void breakImport(ImportData@ data) { 435 + if(data.fromObject !is null) { 436 + auto@ source = findResource(data.fromObject, data.resourceId); 437 + if(source !is null) { 438 + breakImport(source); 439 + return; 440 + } 441 + } 442 + 443 + @data.fromObject = null; 444 + data.resourceId = -1; 445 + data.beingMet = false; 446 + data.idleSince = gameTime; 447 + 448 + active.remove(data); 449 + requested.insertAt(0, data); 450 + } 451 + 452 + void breakImport(ExportData@ data) { 453 + if(data.request !is null) { 454 + if(data.request.obj !is data.obj) 455 + data.obj.exportResource(data.resourceId, null); 456 + 457 + data.request.beingMet = false; 458 + @data.request.fromObject = null; 459 + data.request.resourceId = -1; 460 + data.request.idleSince = gameTime; 461 + 462 + active.remove(data.request); 463 + requested.insertAt(0, data.request); 464 + 465 + @data.request = null; 466 + } 467 + 468 + used.remove(data); 469 + available.insertLast(data); 470 + } 471 + 472 + void cancelRequest(ImportData@ data) { 473 + if(data.beingMet) { 474 + breakImport(data); 475 + active.remove(data); 476 + } 477 + else { 478 + requested.remove(data); 479 + } 480 + } 481 + 482 + void removeResource(ExportData@ data) { 483 + if(data.request !is null) { 484 + breakImport(data); 485 + used.remove(data); 486 + @data.obj = null; 487 + } 488 + else { 489 + available.remove(data); 490 + @data.obj = null; 491 + } 492 + } 493 + 494 + ImportData@ claimImport(ImportData@ data) { 495 + data.beingMet = true; 496 + requested.remove(data); 497 + active.insertLast(data); 498 + return data; 499 + } 500 + 501 + void relinquishImport(ImportData@ data) { 502 + data.beingMet = false; 503 + active.remove(data); 504 + requested.insertLast(data); 505 + } 506 + 507 + void organizeImports(Object& obj, int targetLevel, ImportData@ before = null) { 508 + //Organize any imports for this object so it tries to get to a particular target level 509 + if(log) 510 + ai.print("Organizing imports for level", obj, targetLevel); 511 + 512 + //Get the requirement list 513 + const PlanetLevel@ lvl = getPlanetLevel(obj, targetLevel); 514 + if(lvl is null) { 515 + ai.print("Error: could not find planet level", obj, targetLevel); 516 + return; //Welp, can't do nothing here 517 + } 518 + 519 + //Collect all the requests this planet currently has outstanding 520 + array<ImportData@> activeRequests; 521 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 522 + auto@ req = requested[i]; 523 + if(req.obj !is obj) 524 + continue; 525 + if(!req.forLevel) 526 + continue; 527 + 528 + activeRequests.insertLast(req); 529 + } 530 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 531 + auto@ req = active[i]; 532 + if(req.obj !is obj) 533 + continue; 534 + if(!req.forLevel) 535 + continue; 536 + 537 + activeRequests.insertLast(req); 538 + } 539 + 540 + //TODO: This needs to be able to deal with dummy resources 541 + 542 + //Match import requests with level requirements 543 + array<ResourceSpec@> addSpecs; 544 + const ResourceRequirements@ reqs = lvl.reqs; 545 + 546 + for(uint i = 0, cnt = reqs.reqs.length; i < cnt; ++i) { 547 + auto@ need = reqs.reqs[i]; 548 + for(uint n = 0; n < need.amount; ++n) 549 + addSpecs.insertLast(implementSpec(need)); 550 + } 551 + 552 + if(race !is null) 553 + race.levelRequirements(obj, targetLevel, addSpecs); 554 + 555 + for(uint i = 0, cnt = addSpecs.length; i < cnt; ++i) { 556 + auto@ spec = addSpecs[i]; 557 + 558 + bool foundMatch = false; 559 + for(uint j = 0, jcnt = activeRequests.length; j < jcnt; ++j) { 560 + if(activeRequests[j].spec == spec) { 561 + foundMatch = true; 562 + activeRequests.removeAt(j); 563 + break; 564 + } 565 + } 566 + 567 + if(foundMatch) { 568 + addSpecs.removeAt(i); 569 + --i; --cnt; 570 + } 571 + } 572 + 573 + //Cancel any import requests that we don't need anymore 574 + for(uint i = 0, cnt = activeRequests.length; i < cnt; ++i) 575 + cancelRequest(activeRequests[i]); 576 + 577 + //Insert any imports above any imports of the planet we're exporting to 578 + int place = -1; 579 + if(before !is null) { 580 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 581 + if(requested[i] is before) { 582 + place = int(i); 583 + break; 584 + } 585 + } 586 + } 587 + 588 + //Insert everything we need to add 589 + addSpecs.sortDesc(); 590 + for(uint i = 0, cnt = addSpecs.length; i < cnt; ++i) { 591 + ImportData@ req = requestResource(obj, addSpecs[i], forLevel=true, activate=false); 592 + if(place == -1) { 593 + requested.insertLast(req); 594 + } 595 + else { 596 + requested.insertAt(place, req); 597 + place += 1; 598 + } 599 + } 600 + } 601 + 602 + void killImportsTo(Object& obj) { 603 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 604 + if(requested[i].obj is obj) { 605 + cancelRequest(requested[i]); 606 + --i; --cnt; 607 + } 608 + } 609 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 610 + if(active[i].obj is obj) { 611 + cancelRequest(active[i]); 612 + --i; --cnt; 613 + } 614 + } 615 + } 616 + 617 + void killResourcesFrom(Object& obj) { 618 + for(uint i = 0, cnt = available.length; i < cnt; ++i) { 619 + if(available[i].obj is obj) { 620 + removeResource(available[i]); 621 + --i; --cnt; 622 + } 623 + } 624 + for(uint i = 0, cnt = used.length; i < cnt; ++i) { 625 + if(used[i].obj is obj) { 626 + removeResource(used[i]); 627 + --i; --cnt; 628 + } 629 + } 630 + } 631 + 632 + ImportData@ getImport(const string& fromName, uint index = 0) { 633 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 634 + if(requested[i].obj.name.equals_nocase(fromName)) { 635 + if(index == 0) 636 + return requested[i]; 637 + else 638 + index -= 1; 639 + } 640 + } 641 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 642 + if(active[i].obj.name.equals_nocase(fromName)) { 643 + if(index == 0) 644 + return active[i]; 645 + else 646 + index -= 1; 647 + } 648 + } 649 + return null; 650 + } 651 + 652 + ExportData@ getExport(const string& fromName, uint index = 0) { 653 + for(uint i = 0, cnt = available.length; i < cnt; ++i) { 654 + if(available[i].obj.name.equals_nocase(fromName)) { 655 + if(index == 0) 656 + return available[i]; 657 + else 658 + index -= 1; 659 + } 660 + } 661 + for(uint i = 0, cnt = used.length; i < cnt; ++i) { 662 + if(used[i].obj.name.equals_nocase(fromName)) { 663 + if(index == 0) 664 + return used[i]; 665 + else 666 + index -= 1; 667 + } 668 + } 669 + return null; 670 + } 671 + 672 + void getImportsOf(array<ImportData@>& output, uint resType, Planet@ toPlanet = null) { 673 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 674 + auto@ req = active[i]; 675 + if(req.spec.type != RST_Specific) 676 + continue; 677 + if(req.spec.resource.id != resType) 678 + continue; 679 + if(toPlanet !is null && req.obj !is toPlanet) 680 + continue; 681 + output.insertLast(req); 682 + } 683 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 684 + auto@ req = requested[i]; 685 + if(req.spec.type != RST_Specific) 686 + continue; 687 + if(req.spec.resource.id != resType) 688 + continue; 689 + if(toPlanet !is null && req.obj !is toPlanet) 690 + continue; 691 + output.insertLast(req); 692 + } 693 + } 694 + 695 + void dumpRequests(Object@ forObject = null) { 696 + for(uint i = 0, cnt = requested.length; i < cnt; ++i) { 697 + if(forObject !is null && requested[i].obj !is forObject) 698 + continue; 699 + print(requested[i].obj.name+" requests "+requested[i].spec.dump()); 700 + } 701 + if(forObject !is null) { 702 + for(uint i = 0, cnt = used.length; i < cnt; ++i) { 703 + if(used[i].request is null || used[i].request.obj !is forObject) 704 + continue; 705 + print(used[i].request.obj.name+" is getting "+used[i].request.spec.dump()+" from "+used[i].obj.name); 706 + } 707 + } 708 + } 709 +}; 710 + 711 +AIComponent@ createResources() { 712 + return Resources(); 713 +}
Added scripts/server/empire_ai/weasel/Scouting.as.
1 +// Scouting 2 +// -------- 3 +// Orders the construction of scouts, explores the galaxy with them and makes 4 +// sure we have vision where we need vision, as well as scanning anomalies. 5 +// 6 + 7 +import empire_ai.weasel.WeaselAI; 8 + 9 +import empire_ai.weasel.Fleets; 10 +import empire_ai.weasel.Systems; 11 +import empire_ai.weasel.Designs; 12 +import empire_ai.weasel.Construction; 13 +import empire_ai.weasel.Movement; 14 +import empire_ai.weasel.Creeping; 15 + 16 +final class ScoutingMission : Mission { 17 + Region@ region; 18 + MoveOrder@ move; 19 + 20 + void save(Fleets& fleets, SaveFile& file) override { 21 + file << region; 22 + fleets.movement.saveMoveOrder(file, move); 23 + } 24 + 25 + void load(Fleets& fleets, SaveFile& file) override { 26 + file >> region; 27 + @move = fleets.movement.loadMoveOrder(file); 28 + } 29 + 30 + double getPerformWeight(AI& ai, FleetAI& fleet) { 31 + if(fleet.fleetClass != FC_Scout) { 32 + if(fleet.fleetClass == FC_Mothership) 33 + return 0.0; 34 + if(gameTime > ai.behavior.scoutAllTimer) 35 + return 0.0; 36 + } 37 + return 1.0 / region.position.distanceTo(fleet.obj.position); 38 + } 39 + 40 + void start(AI& ai, FleetAI& fleet) override { 41 + uint mprior = MP_Background; 42 + if(gameTime < 6.0 * 60.0) 43 + mprior = MP_Critical; 44 + else if(priority > MiP_Normal) 45 + mprior = MP_Normal; 46 + @move = cast<Movement>(ai.movement).move(fleet.obj, region, mprior); 47 + } 48 + 49 + void tick(AI& ai, FleetAI& fleet, double time) { 50 + if(move.failed) 51 + canceled = true; 52 + if(move.completed) { 53 + //We managed to scout this system 54 + if(fleet.obj.region !is region) { 55 + @move = cast<Movement>(ai.movement).move(fleet.obj, region.position + random3d(400.0)); 56 + return; 57 + } 58 + completed = true; 59 + 60 + //Detect any anomalies and put them into the scanning queue 61 + //TODO: Detect newly created anomalies in systems we already have vision over? 62 + if(region.anomalyCount != 0) { 63 + auto@ list = region.getAnomalies(); 64 + Object@ obj; 65 + while(receive(list, obj)) { 66 + Anomaly@ anom = cast<Anomaly>(obj); 67 + if(anom !is null) 68 + cast<Scouting>(ai.scouting).recordAnomaly(anom); 69 + } 70 + } 71 + } 72 + } 73 +}; 74 + 75 +final class ScanningMission : Mission { 76 + Anomaly@ anomaly; 77 + MoveOrder@ move; 78 + 79 + void save(Fleets& fleets, SaveFile& file) override { 80 + file << anomaly; 81 + fleets.movement.saveMoveOrder(file, move); 82 + } 83 + 84 + void load(Fleets& fleets, SaveFile& file) override { 85 + file >> anomaly; 86 + @move = fleets.movement.loadMoveOrder(file); 87 + } 88 + 89 + double getPerformWeight(AI& ai, FleetAI& fleet) { 90 + if(fleet.fleetClass != FC_Scout) { 91 + if(gameTime > ai.behavior.scoutAllTimer) 92 + return 0.0; 93 + } 94 + return 1.0 / anomaly.position.distanceTo(fleet.obj.position); 95 + } 96 + 97 + void start(AI& ai, FleetAI& fleet) override { 98 + uint mprior = MP_Background; 99 + if(priority > MiP_Normal) 100 + mprior = MP_Normal; 101 + @move = cast<Movement>(ai.movement).move(fleet.obj, anomaly, mprior); 102 + } 103 + 104 + void tick(AI& ai, FleetAI& fleet, double time) { 105 + if(move !is null) { 106 + if(move.failed) { 107 + canceled = true; 108 + return; 109 + } 110 + if(move.completed) 111 + @move = null; 112 + } 113 + if(move is null) { 114 + if(anomaly is null || !anomaly.valid) { 115 + completed = true; 116 + return; 117 + } 118 + 119 + if(anomaly.getEmpireProgress(ai.empire) >= 1.f) { 120 + uint choose = 0; 121 + uint possibs = 0; 122 + uint optCnt = anomaly.getOptionCount(); 123 + for(uint i = 0; i < optCnt; ++i) { 124 + if(anomaly.isOptionSafe[i]) { 125 + possibs += 1; 126 + if(randomd() < 1.0 / double(possibs)) 127 + choose = i; 128 + } 129 + } 130 + 131 + if(!ai.behavior.forbidAnomalyChoice && possibs != 0) { 132 + anomaly.choose(ai.empire, choose); 133 + } 134 + else { 135 + completed = true; 136 + } 137 + } 138 + else { 139 + if(!fleet.obj.hasOrders) 140 + fleet.obj.addScanOrder(anomaly); 141 + } 142 + } 143 + } 144 +}; 145 + 146 +class Scouting : AIComponent { 147 + Fleets@ fleets; 148 + Systems@ systems; 149 + Designs@ designs; 150 + Construction@ construction; 151 + Movement@ movement; 152 + Creeping@ creeping; 153 + 154 + DesignTarget@ scoutDesign; 155 + 156 + array<ScoutingMission@> queue; 157 + array<ScoutingMission@> active; 158 + 159 + array<Anomaly@> anomalies; 160 + array<ScanningMission@> scanQueue; 161 + array<ScanningMission@> scanActive; 162 + 163 + array<BuildFlagship@> constructing; 164 + 165 + int exploreHops = 0; 166 + bool buildScouts = true; 167 + 168 + void create() { 169 + @fleets = cast<Fleets>(ai.fleets); 170 + @systems = cast<Systems>(ai.systems); 171 + @designs = cast<Designs>(ai.designs); 172 + @construction = cast<Construction>(ai.construction); 173 + @movement = cast<Movement>(ai.movement); 174 + @creeping = cast<Creeping>(ai.creeping); 175 + } 176 + 177 + void save(SaveFile& file) { 178 + designs.saveDesign(file, scoutDesign); 179 + file << exploreHops; 180 + 181 + uint cnt = queue.length; 182 + file << cnt; 183 + for(uint i = 0; i < cnt; ++i) 184 + fleets.saveMission(file, queue[i]); 185 + 186 + cnt = active.length; 187 + file << cnt; 188 + for(uint i = 0; i < cnt; ++i) 189 + fleets.saveMission(file, active[i]); 190 + 191 + cnt = anomalies.length; 192 + file << cnt; 193 + for(uint i = 0; i < cnt; ++i) 194 + file << anomalies[i]; 195 + 196 + cnt = scanQueue.length; 197 + file << cnt; 198 + for(uint i = 0; i < cnt; ++i) 199 + fleets.saveMission(file, scanQueue[i]); 200 + 201 + cnt = scanActive.length; 202 + file << cnt; 203 + for(uint i = 0; i < cnt; ++i) 204 + fleets.saveMission(file, scanActive[i]); 205 + 206 + cnt = constructing.length; 207 + file << cnt; 208 + for(uint i = 0; i < cnt; ++i) 209 + construction.saveConstruction(file, constructing[i]); 210 + } 211 + 212 + void load(SaveFile& file) { 213 + @scoutDesign = designs.loadDesign(file); 214 + file >> exploreHops; 215 + 216 + uint cnt = 0; 217 + file >> cnt; 218 + for(uint i = 0; i < cnt; ++i) { 219 + auto@ miss = cast<ScoutingMission>(fleets.loadMission(file)); 220 + if(miss !is null) 221 + queue.insertLast(miss); 222 + } 223 + 224 + file >> cnt; 225 + for(uint i = 0; i < cnt; ++i) { 226 + auto@ miss = cast<ScoutingMission>(fleets.loadMission(file)); 227 + if(miss !is null) 228 + active.insertLast(miss); 229 + } 230 + 231 + file >> cnt; 232 + for(uint i = 0; i < cnt; ++i) { 233 + Anomaly@ anom; 234 + file >> anom; 235 + if(anom !is null) 236 + anomalies.insertLast(anom); 237 + } 238 + 239 + file >> cnt; 240 + for(uint i = 0; i < cnt; ++i) { 241 + auto@ miss = cast<ScanningMission>(fleets.loadMission(file)); 242 + if(miss !is null) 243 + scanQueue.insertLast(miss); 244 + } 245 + 246 + file >> cnt; 247 + for(uint i = 0; i < cnt; ++i) { 248 + auto@ miss = cast<ScanningMission>(fleets.loadMission(file)); 249 + if(miss !is null) 250 + scanActive.insertLast(miss); 251 + } 252 + 253 + file >> cnt; 254 + for(uint i = 0; i < cnt; ++i) { 255 + auto@ cons = cast<BuildFlagship>(construction.loadConstruction(file)); 256 + if(cons !is null) 257 + constructing.insertLast(cons); 258 + } 259 + } 260 + 261 + void start() { 262 + @scoutDesign = DesignTarget(DP_Scout, 16); 263 + scoutDesign.targetMaintenance = 40; 264 + designs.design(scoutDesign); 265 + } 266 + 267 + bool isScouting(Region@ region) { 268 + for(uint i = 0, cnt = queue.length; i < cnt; ++i) { 269 + if(queue[i].region is region) 270 + return true; 271 + } 272 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 273 + if(active[i].region is region) 274 + return true; 275 + } 276 + return false; 277 + } 278 + 279 + ScoutingMission@ scout(Region@ region, uint priority = MiP_High) { 280 + for(uint i = 0, cnt = queue.length; i < cnt; ++i) { 281 + if(queue[i].region is region) 282 + return queue[i]; 283 + } 284 + 285 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 286 + if(active[i].region is region) 287 + return active[i]; 288 + } 289 + 290 + if(log) 291 + ai.print("Queue scouting mission", region); 292 + 293 + ScoutingMission mission; 294 + @mission.region = region; 295 + mission.priority = priority; 296 + fleets.register(mission); 297 + 298 + queue.insertLast(mission); 299 + return mission; 300 + } 301 + 302 + void recordAnomaly(Anomaly@ anom) { 303 + for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) { 304 + if(scanActive[i].anomaly is anom) 305 + return; 306 + } 307 + 308 + if(anomalies.find(anom) == -1) 309 + anomalies.insertLast(anom); 310 + } 311 + 312 + ScanningMission@ scan(Anomaly& anomaly, uint priority = MiP_Normal) { 313 + for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) { 314 + if(scanActive[i].anomaly is anomaly) 315 + return scanActive[i]; 316 + } 317 + 318 + if(log) 319 + ai.print("Queue scanning mission on "+anomaly.name, anomaly.region); 320 + 321 + ScanningMission mission; 322 + @mission.anomaly = anomaly; 323 + mission.priority = priority; 324 + fleets.register(mission); 325 + anomalies.remove(anomaly); 326 + 327 + scanQueue.insertLast(mission); 328 + return mission; 329 + } 330 + 331 + void focusTick(double time) { 332 + //Remove completed scouting missions 333 + for(uint i = 0, cnt = active.length; i < cnt; ++i) { 334 + if(active[i].completed || active[i].canceled) { 335 + active.removeAt(i); 336 + --i; --cnt; 337 + } 338 + } 339 + 340 + //Remove completed scanning missions 341 + for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) { 342 + if(scanActive[i].completed || scanActive[i].canceled) { 343 + scanActive.removeAt(i); 344 + --i; --cnt; 345 + } 346 + } 347 + 348 + if(ai.behavior.forbidScouting) return; 349 + 350 + //Make sure we have enough scouts active and scouting 351 + if(fleets.count(FC_Scout) + constructing.length < ai.behavior.scoutsActive && buildScouts) 352 + constructing.insertLast(construction.buildFlagship(scoutDesign)); 353 + for(uint i = 0, cnt = constructing.length; i < cnt; ++i) { 354 + if(constructing[i].completed && constructing[i].completedAt + 30.0 < gameTime) { 355 + constructing.removeAt(i); 356 + --i; --cnt; 357 + } 358 + } 359 + 360 + //See if we can fill the scouting queue with something nice 361 + uint scoutClass = FC_Scout; 362 + if(gameTime < ai.behavior.scoutAllTimer) 363 + scoutClass = FC_ALL; 364 + bool haveIdle = fleets.haveIdle(scoutClass); 365 + 366 + //See if we should queue up a new anomaly scan 367 + if(scanQueue.length == 0 && anomalies.length != 0 && scanActive.length < ai.behavior.maxScanningMissions && haveIdle && (!ai.behavior.prioritizeScoutOverScan || active.length > 0)) { 368 + Anomaly@ best; 369 + double bestDist = INFINITY; 370 + for(uint i = 0, cnt = anomalies.length; i < cnt; ++i) { 371 + auto@ anom = anomalies[i]; 372 + if(anom is null || !anom.valid) { 373 + anomalies.removeAt(i); 374 + --i; --cnt; 375 + continue; 376 + } 377 + if(creeping.isQuarantined(anom.region)) 378 + continue; 379 + 380 + double d = fleets.closestIdleTo(scoutClass, anom.position); 381 + if(d < bestDist) { 382 + @best = anom; 383 + bestDist = d; 384 + } 385 + } 386 + 387 + if(best !is null) 388 + scan(best); 389 + } 390 + 391 + //Scan anomalies in our scan queue 392 + if(scanQueue.length != 0) { 393 + auto@ mission = scanQueue[0]; 394 + if(mission.anomaly is null || !mission.anomaly.valid) { 395 + scanQueue.removeAt(0); 396 + } 397 + else { 398 + auto@ flAI = fleets.performMission(mission); 399 + if(flAI !is null) { 400 + if(log) 401 + ai.print("Perform scanning mission with "+flAI.obj.name, mission.anomaly.region); 402 + 403 + scanQueue.remove(mission); 404 + scanActive.insertLast(mission); 405 + } 406 + } 407 + } 408 + 409 + //TODO: In large maps we should probably devote scouts to scouting enemies even before the map is fully explored 410 + if(queue.length == 0 && active.length < ai.behavior.maxScoutingMissions && haveIdle) { 411 + //Explore systems from the inside out 412 + if(exploreHops != -1) { 413 + double bestDist = INFINITY; 414 + bool remainingHops = false; 415 + bool emptyHops = true; 416 + Region@ best; 417 + 418 + for(uint i = 0, cnt = systems.all.length; i < cnt; ++i) { 419 + auto@ sys = systems.all[i]; 420 + 421 + if(sys.hopDistance == exploreHops) 422 + emptyHops = false; 423 + 424 + if(sys.explored || isScouting(sys.obj)) 425 + continue; 426 + 427 + if(sys.hopDistance == exploreHops) 428 + remainingHops = true; 429 + 430 + double d = fleets.closestIdleTo(scoutClass, sys.obj.position); 431 + if(sys.hopDistance != exploreHops) 432 + d *= pow(ai.behavior.exploreBorderWeight, double(sys.hopDistance - exploreHops)); 433 + 434 + if(d < bestDist) { 435 + bestDist = d; 436 + @best = sys.obj; 437 + } 438 + } 439 + 440 + if(best !is null) 441 + scout(best, priority=MiP_Normal); 442 + 443 + if(emptyHops) 444 + exploreHops = -1; 445 + else if(!remainingHops) 446 + exploreHops += 1; 447 + } 448 + else { 449 + //Gain vision over systems we haven't recently seen 450 + Region@ best; 451 + double bestWeight = 0; 452 + double curTime = gameTime; 453 + 454 + for(uint i = 0, cnt = systems.all.length; i < cnt; ++i) { 455 + auto@ sys = systems.all[i]; 456 + if(sys.visible || sys.visibleNow(ai)) 457 + continue; 458 + if(isScouting(sys.obj)) 459 + continue; 460 + 461 + double timer = curTime - sys.lastVisible; 462 + if(timer < ai.behavior.minScoutingInterval) 463 + continue; 464 + 465 + double w = 1.0; 466 + w *= timer / ai.behavior.minScoutingInterval; 467 + w /= fleets.closestIdleTo(scoutClass, sys.obj.position); 468 + 469 + if(!sys.explored) 470 + w *= 10.0; 471 + if(sys.seenPresent & ~ai.visionMask != 0) 472 + w *= 2.0; 473 + if(sys.seenPresent & ai.enemyMask != 0) { 474 + if(sys.hopDistance < 2) 475 + w *= 4.0; 476 + w *= 4.0; 477 + } 478 + 479 + if(w > bestWeight) { 480 + bestWeight = w; 481 + @best = sys.obj; 482 + } 483 + } 484 + 485 + if(best !is null) 486 + scout(best, priority=MiP_Normal); 487 + } 488 + } 489 + 490 + //Try to find a scout to perform our top scouting mission from the queue 491 + if(queue.length != 0) { 492 + auto@ mission = queue[0]; 493 + auto@ flAI = fleets.performMission(mission); 494 + if(flAI !is null) { 495 + if(log) 496 + ai.print("Perform scouting mission with "+flAI.obj.name, mission.region); 497 + 498 + active.insertLast(mission); 499 + queue.removeAt(0); 500 + } 501 + } 502 + } 503 +}; 504 + 505 +AIComponent@ createScouting() { 506 + return Scouting(); 507 +}
Added scripts/server/empire_ai/weasel/Systems.as.
1 +import empire_ai.weasel.WeaselAI; 2 + 3 +import empire_ai.weasel.Events; 4 + 5 +import empire_ai.weasel.searches; 6 + 7 +import ai.events; 8 + 9 +import systems; 10 +import system_pathing; 11 + 12 +final class SystemAI { 13 + const SystemDesc@ desc; 14 + Region@ obj; 15 + 16 + double prevTick = 0.0; 17 + 18 + array<Planet@> planets; 19 + array<Pickup@> pickups; 20 + array<Object@> pickupProtectors; 21 + array<Artifact@> artifacts; 22 + array<Asteroid@> asteroids; 23 + 24 + bool explored = false; 25 + bool owned = false; 26 + bool visible = false; 27 + 28 + int hopDistance = 0; 29 + bool visited = false; 30 + 31 + bool border = false; 32 + bool bordersEmpires = false; 33 + bool outsideBorder = false; 34 + 35 + double lastVisible = 0; 36 + uint seenPresent = 0; 37 + 38 + double focusDuration = 0; 39 + 40 + double enemyStrength = 0; 41 + double lastStrengthCheck = 0; 42 + 43 + double nextDetailed = 0; 44 + 45 + SystemAI() { 46 + } 47 + 48 + SystemAI(const SystemDesc@ sys) { 49 + @desc = sys; 50 + @obj = desc.object; 51 + } 52 + 53 + void save(SaveFile& file) { 54 + file << obj; 55 + file << prevTick; 56 + 57 + uint cnt = planets.length; 58 + file << cnt; 59 + for(uint i = 0; i < cnt; ++i) 60 + file << planets[i]; 61 + 62 + cnt = pickups.length; 63 + file << cnt; 64 + for(uint i = 0; i < cnt; ++i) 65 + file << pickups[i]; 66 + 67 + cnt = pickupProtectors.length; 68 + file << cnt; 69 + for(uint i = 0; i < cnt; ++i) 70 + file << pickupProtectors[i]; 71 + 72 + cnt = artifacts.length; 73 + file << cnt; 74 + for(uint i = 0; i < cnt; ++i) 75 + file << artifacts[i]; 76 + 77 + cnt = asteroids.length; 78 + file << cnt; 79 + for(uint i = 0; i < cnt; ++i) 80 + file << asteroids[i]; 81 + 82 + file << explored; 83 + file << owned; 84 + file << visible; 85 + file << hopDistance; 86 + file << border; 87 + file << bordersEmpires; 88 + file << outsideBorder; 89 + file << lastVisible; 90 + file << seenPresent; 91 + file << enemyStrength; 92 + file << lastStrengthCheck; 93 + } 94 + 95 + void load(SaveFile& file) { 96 + file >> obj; 97 + file >> prevTick; 98 + 99 + uint cnt = 0; 100 + file >> cnt; 101 + planets.length = cnt; 102 + for(uint i = 0; i < cnt; ++i) 103 + file >> planets[i]; 104 + 105 + file >> cnt; 106 + pickups.length = cnt; 107 + for(uint i = 0; i < cnt; ++i) 108 + file >> pickups[i]; 109 + 110 + file >> cnt; 111 + pickupProtectors.length = cnt; 112 + for(uint i = 0; i < cnt; ++i) 113 + file >> pickupProtectors[i]; 114 + 115 + file >> cnt; 116 + for(uint i = 0; i < cnt; ++i) { 117 + Artifact@ artif; 118 + file >> artif; 119 + if(artif !is null) 120 + artifacts.insertLast(artif); 121 + } 122 + 123 + file >> cnt; 124 + for(uint i = 0; i < cnt; ++i) { 125 + Asteroid@ roid; 126 + file >> roid; 127 + if(roid !is null) 128 + asteroids.insertLast(roid); 129 + } 130 + 131 + file >> explored; 132 + file >> owned; 133 + file >> visible; 134 + file >> hopDistance; 135 + file >> border; 136 + file >> bordersEmpires; 137 + file >> outsideBorder; 138 + file >> lastVisible; 139 + file >> seenPresent; 140 + file >> enemyStrength; 141 + file >> lastStrengthCheck; 142 + } 143 + 144 + bool visibleNow(AI& ai) { 145 + return obj.VisionMask & ai.visionMask != 0; 146 + } 147 + 148 + void strengthCheck(AI& ai, double minInterval = 30.0) { 149 + if(lastStrengthCheck + minInterval > gameTime) 150 + return; 151 + if(!visible && lastVisible < gameTime - 30.0) 152 + return; 153 + lastStrengthCheck = gameTime; 154 + enemyStrength = getTotalFleetStrength(obj, ai.enemyMask); 155 + } 156 + 157 + void tick(AI& ai, Systems& systems, double time) { 158 + //Check if we should be visible 159 + bool shouldVisible = obj.VisionMask & ai.visionMask != 0; 160 + if(visible != shouldVisible) { 161 + if(visible) 162 + lastVisible = gameTime; 163 + visible = shouldVisible; 164 + } 165 + 166 + //Check if we should be owned 167 + bool shouldOwned = obj.TradeMask & ai.mask != 0; 168 + if(owned != shouldOwned) { 169 + if(shouldOwned) { 170 + systems.owned.insertLast(this); 171 + systems.hopsChanged = true; 172 + hopDistance = 0; 173 + systems.events.notifyOwnedSystemAdded(this, EventArgs()); 174 + } 175 + else { 176 + hopDistance = 1; 177 + systems.owned.remove(this); 178 + systems.hopsChanged = true; 179 + systems.events.notifyOwnedSystemRemoved(this, EventArgs()); 180 + } 181 + owned = shouldOwned; 182 + } 183 + 184 + //Check if we should be border 185 + bool shouldBorder = false; 186 + bordersEmpires = false; 187 + if(owned) { 188 + for(uint i = 0, cnt = desc.adjacent.length; i < cnt; ++i) { 189 + auto@ other = systems.getAI(desc.adjacent[i]); 190 + if(other !is null && !other.owned) { 191 + if(other.seenPresent & ~ai.teamMask != 0) 192 + bordersEmpires = true; 193 + shouldBorder = true; 194 + break; 195 + } 196 + } 197 + for(uint i = 0, cnt = desc.wormholes.length; i < cnt; ++i) { 198 + auto@ other = systems.getAI(desc.wormholes[i]); 199 + if(other !is null && !other.owned) { 200 + if(other.seenPresent & ~ai.teamMask != 0) 201 + bordersEmpires = true; 202 + shouldBorder = true; 203 + break; 204 + } 205 + } 206 + } 207 + 208 + if(border != shouldBorder) { 209 + if(shouldBorder) { 210 + systems.border.insertLast(this); 211 + systems.events.notifyBorderSystemAdded(this, EventArgs()); 212 + } 213 + else { 214 + systems.border.remove(this); 215 + systems.events.notifyBorderSystemRemoved(this, EventArgs()); 216 + } 217 + border = shouldBorder; 218 + } 219 + 220 + //Check if we should be outsideBorder 221 + bool shouldOutsideBorder = !owned && hopDistance == 1; 222 + if(outsideBorder != shouldOutsideBorder) { 223 + if(shouldOutsideBorder) { 224 + systems.outsideBorder.insertLast(this); 225 + systems.events.notifyOutsideBorderSystemAdded(this, EventArgs()); 226 + } 227 + else { 228 + systems.outsideBorder.remove(this); 229 + systems.events.notifyOutsideBorderSystemRemoved(this, EventArgs()); 230 + } 231 + outsideBorder = shouldOutsideBorder; 232 + } 233 + 234 + //Check if we've been explored 235 + if(visible && !explored) { 236 + //Find all remnants in this system 237 + auto@ objs = findType(obj, null, OT_Pickup); 238 + for(uint i = 0, cnt = objs.length; i < cnt; ++i) { 239 + Pickup@ p = cast<Pickup>(objs[i]); 240 + if(p !is null) { 241 + pickups.insertLast(p); 242 + pickupProtectors.insertLast(p.getProtector()); 243 + } 244 + } 245 + 246 + explored = true; 247 + } 248 + 249 + //Deal with recording new data on this system 250 + if(explored) { 251 + uint plCnt = obj.planetCount; 252 + if(plCnt != planets.length) { 253 + auto@ objs = findType(obj, null, OT_Planet); 254 + planets.length = 0; 255 + planets.reserve(objs.length); 256 + for(uint i = 0, cnt = objs.length; i < cnt; ++i) { 257 + Planet@ pl = cast<Planet>(objs[i]); 258 + if(pl !is null) 259 + planets.insertLast(pl); 260 + } 261 + } 262 + } 263 + 264 + if(visible) { 265 + seenPresent = obj.PlanetsMask; 266 + 267 + uint astrCount = obj.asteroidCount; 268 + if(astrCount != asteroids.length) { 269 + auto@ objs = findType(obj, null, OT_Asteroid); 270 + asteroids.length = 0; 271 + asteroids.reserve(objs.length); 272 + for(uint i = 0, cnt = objs.length; i < cnt; ++i) { 273 + Asteroid@ a = cast<Asteroid>(objs[i]); 274 + if(a !is null) 275 + asteroids.insertLast(a); 276 + } 277 + } 278 + 279 + for(uint i = 0, cnt = pickups.length; i < cnt; ++i) { 280 + if(!pickups[i].valid) { 281 + pickups.removeAt(i); 282 + pickupProtectors.removeAt(i); 283 + break; 284 + } 285 + } 286 + 287 + if(nextDetailed < gameTime) { 288 + nextDetailed = gameTime + randomd(40.0, 100.0); 289 + 290 + auto@ objs = findType(obj, null, OT_Artifact); 291 + artifacts.length = 0; 292 + artifacts.reserve(objs.length); 293 + for(uint i = 0, cnt = objs.length; i < cnt; ++i) { 294 + Artifact@ a = cast<Artifact>(objs[i]); 295 + if(a !is null) 296 + artifacts.insertLast(a); 297 + } 298 + } 299 + } 300 + } 301 +}; 302 + 303 +class Systems : AIComponent { 304 + Events@ events; 305 + 306 + //All owned systems 307 + array<SystemAI@> owned; 308 + 309 + //All owned systems that are considered our empire's border 310 + array<SystemAI@> border; 311 + 312 + //All systems just outside our border 313 + array<SystemAI@> outsideBorder; 314 + 315 + //All systems 316 + array<SystemAI@> all; 317 + 318 + //Systems that need to be processed soon 319 + array<SystemAI@> bumped; 320 + array<SystemAI@> focused; 321 + 322 + uint sysIdx = 0; 323 + bool hopsChanged = false; 324 + 325 + void create() { 326 + @events = cast<Events>(ai.events); 327 + } 328 + 329 + void save(SaveFile& file) { 330 + uint cnt = all.length; 331 + file << cnt; 332 + for(uint i = 0; i < cnt; ++i) 333 + all[i].save(file); 334 + 335 + cnt = owned.length; 336 + file << cnt; 337 + for(uint i = 0; i < cnt; ++i) 338 + saveAI(file, owned[i]); 339 + 340 + cnt = border.length; 341 + file << cnt; 342 + for(uint i = 0; i < cnt; ++i) 343 + saveAI(file, border[i]); 344 + 345 + cnt = outsideBorder.length; 346 + file << cnt; 347 + for(uint i = 0; i < cnt; ++i) 348 + saveAI(file, outsideBorder[i]); 349 + } 350 + 351 + void load(SaveFile& file) { 352 + uint cnt = 0; 353 + file >> cnt; 354 + all.length = max(all.length, cnt); 355 + for(uint i = 0; i < cnt; ++i) { 356 + if(all[i] is null) 357 + @all[i] = SystemAI(); 358 + all[i].load(file); 359 + } 360 + 361 + file >> cnt; 362 + for(uint i = 0; i < cnt; ++i) { 363 + auto@ data = loadAI(file); 364 + if(data !is null) 365 + owned.insertLast(data); 366 + } 367 + 368 + file >> cnt; 369 + for(uint i = 0; i < cnt; ++i) { 370 + auto@ data = loadAI(file); 371 + if(data !is null) 372 + border.insertLast(data); 373 + } 374 + 375 + file >> cnt; 376 + for(uint i = 0; i < cnt; ++i) { 377 + auto@ data = loadAI(file); 378 + if(data !is null) 379 + outsideBorder.insertLast(data); 380 + } 381 + } 382 + 383 + void loadFinalize(AI& ai) override { 384 + for(uint i = 0, cnt = all.length; i < cnt; ++i) { 385 + auto@ sys = getSystem(i); 386 + @all[i].desc = sys; 387 + @all[i].obj = sys.object; 388 + } 389 + } 390 + 391 + void saveAI(SaveFile& file, SystemAI@ ai) { 392 + Region@ reg; 393 + if(ai !is null) 394 + @reg = ai.obj; 395 + file << reg; 396 + } 397 + 398 + SystemAI@ loadAI(SaveFile& file) { 399 + Region@ reg; 400 + file >> reg; 401 + 402 + if(reg is null) 403 + return null; 404 + 405 + uint id = reg.SystemId; 406 + if(id >= all.length) { 407 + all.length = id+1; 408 + @all[id] = SystemAI(); 409 + @all[id].obj = reg; 410 + } 411 + 412 + return all[id]; 413 + } 414 + 415 + void focusTick(double time) { 416 + if(all.length != systemCount) { 417 + uint prevCount = all.length; 418 + all.length = systemCount; 419 + for(uint i = prevCount, cnt = all.length; i < cnt; ++i) 420 + @all[i] = SystemAI(getSystem(i)); 421 + } 422 + if(hopsChanged) 423 + calculateHops(); 424 + } 425 + 426 + void tick(double time) override { 427 + double curTime = gameTime; 428 + 429 + if(all.length != 0) { 430 + uint tcount = max(ceil(time / 0.2), double(all.length)/20.0); 431 + for(uint n = 0; n < tcount; ++n) { 432 + sysIdx = (sysIdx+1) % all.length; 433 + 434 + auto@ sys = all[sysIdx]; 435 + sys.tick(ai, this, curTime - sys.prevTick); 436 + sys.prevTick = curTime; 437 + } 438 + } 439 + 440 + for(uint i = 0, cnt = bumped.length; i < cnt; ++i) { 441 + auto@ sys = bumped[i]; 442 + double tickTime = curTime - sys.prevTick; 443 + if(tickTime != 0) { 444 + sys.tick(ai, this, tickTime); 445 + sys.prevTick = curTime; 446 + } 447 + } 448 + bumped.length = 0; 449 + 450 + for(uint i = 0, cnt = focused.length; i < cnt; ++i) { 451 + auto@ sys = focused[i]; 452 + sys.focusDuration -= time; 453 + 454 + double tickTime = curTime - sys.prevTick; 455 + if(tickTime != 0) { 456 + sys.tick(ai, this, tickTime); 457 + sys.prevTick = curTime; 458 + } 459 + 460 + if(sys.focusDuration <= 0) { 461 + focused.removeAt(i); 462 + --i; --cnt; 463 + } 464 + } 465 + } 466 + 467 + void calculateHops() { 468 + if(!hopsChanged) 469 + return; 470 + hopsChanged = false; 471 + priority_queue q; 472 + for(uint i = 0, cnt = all.length; i < cnt; ++i) { 473 + auto@ sys = all[i]; 474 + sys.visited = false; 475 + if(sys.owned) { 476 + sys.hopDistance = 0; 477 + q.push(int(i), 0); 478 + } 479 + else 480 + sys.hopDistance = INT_MAX; 481 + } 482 + 483 + while(!q.empty()) { 484 + uint index = uint(q.top()); 485 + q.pop(); 486 + 487 + auto@ sys = all[index]; 488 + if(sys.visited) 489 + continue; 490 + 491 + int dist = sys.hopDistance; 492 + sys.visited = true; 493 + 494 + for(uint i = 0, cnt = sys.desc.adjacent.length; i < cnt; ++i) { 495 + uint otherInd = sys.desc.adjacent[i]; 496 + if(otherInd < all.length) { 497 + auto@ other = all[otherInd]; 498 + if(other.hopDistance > dist+1) { 499 + other.hopDistance = dist+1; 500 + q.push(otherInd, -other.hopDistance); 501 + } 502 + } 503 + } 504 + for(uint i = 0, cnt = sys.desc.wormholes.length; i < cnt; ++i) { 505 + uint otherInd = sys.desc.wormholes[i]; 506 + if(otherInd < all.length) { 507 + auto@ other = all[otherInd]; 508 + if(other.hopDistance > dist+1) { 509 + other.hopDistance = dist+1; 510 + q.push(otherInd, -other.hopDistance); 511 + } 512 + } 513 + } 514 + } 515 + } 516 + 517 + void focus(Region@ reg, double duration = 30.0) { 518 + bool found = false; 519 + for(uint i = 0, cnt = focused.length; i < cnt; ++i) { 520 + if(focused[i].obj is reg) { 521 + focused[i].focusDuration = max(focused[i].focusDuration, duration); 522 + found = true; 523 + break; 524 + } 525 + } 526 + 527 + if(!found) { 528 + auto@ sys = getAI(reg); 529 + if(sys !is null) { 530 + sys.focusDuration = duration; 531 + focused.insertLast(sys); 532 + } 533 + } 534 + } 535 + 536 + void bump(Region@ sys) { 537 + if(sys !is null) 538 + bump(getAI(sys)); 539 + } 540 + 541 + void bump(SystemAI@ sys) { 542 + if(sys !is null) 543 + bumped.insertLast(sys); 544 + } 545 + 546 + SystemAI@ getAI(uint idx) { 547 + if(idx < all.length) 548 + return all[idx]; 549 + return null; 550 + } 551 + 552 + SystemAI@ getAI(Region@ region) { 553 + if(region is null) 554 + return null; 555 + uint idx = region.SystemId; 556 + if(idx < all.length) 557 + return all[idx]; 558 + return null; 559 + } 560 + 561 + SystemPath pather; 562 + int hopDistance(Region& fromRegion, Region& toRegion){ 563 + pather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true); 564 + if(!pather.valid) 565 + return INT_MAX; 566 + return pather.pathSize - 1; 567 + } 568 + 569 + TradePath tradePather; 570 + int tradeDistance(Region& fromRegion, Region& toRegion) { 571 + @tradePather.forEmpire = ai.empire; 572 + tradePather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true); 573 + if(!tradePather.valid) 574 + return -1; 575 + return tradePather.pathSize - 1; 576 + } 577 + 578 + bool canTrade(Region& fromRegion, Region& toRegion) { 579 + if(fromRegion.sharesTerritory(ai.empire, toRegion)) 580 + return true; 581 + int dist = tradeDistance(fromRegion, toRegion); 582 + if(dist < 0) 583 + return false; 584 + return true; 585 + } 586 + 587 + SystemAI@ getAI(const string& name) { 588 + for(uint i = 0, cnt = all.length; i < cnt; ++i) { 589 + if(all[i].obj.name.equals_nocase(name)) 590 + return all[i]; 591 + } 592 + return null; 593 + } 594 + 595 + uint index(const string& name) { 596 + for(uint i = 0, cnt = all.length; i < cnt; ++i) { 597 + if(all[i].obj.name.equals_nocase(name)) 598 + return i; 599 + } 600 + return uint(-1); 601 + } 602 +}; 603 + 604 +AIComponent@ createSystems() { 605 + return Systems(); 606 +}
Added scripts/server/empire_ai/weasel/War.as.
1 +// War 2 +// --- 3 +// Attacks and defends from enemy attacks during situations of war. 4 +// 5 + 6 +import empire_ai.weasel.WeaselAI; 7 +import empire_ai.weasel.Intelligence; 8 +import empire_ai.weasel.Relations; 9 +import empire_ai.weasel.Fleets; 10 +import empire_ai.weasel.Systems; 11 +import empire_ai.weasel.Movement; 12 +import empire_ai.weasel.Scouting; 13 +import empire_ai.weasel.Military; 14 +import empire_ai.weasel.searches; 15 + 16 +import regions.regions; 17 + 18 +class BattleMission : Mission { 19 + Region@ battleIn; 20 + FleetAI@ fleet; 21 + MoveOrder@ move; 22 + Object@ defending; 23 + Planet@ capturing; 24 + Empire@ captureFrom; 25 + bool arrived = false; 26 + 27 + void save(Fleets& fleets, SaveFile& file) override { 28 + file << battleIn; 29 + fleets.saveAI(file, fleet); 30 + fleets.movement.saveMoveOrder(file, move); 31 + file << defending; 32 + file << capturing; 33 + file << captureFrom; 34 + file << arrived; 35 + } 36 + 37 + void load(Fleets& fleets, SaveFile& file) override { 38 + file >> battleIn; 39 + @fleet = fleets.loadAI(file); 40 + @move = fleets.movement.loadMoveOrder(file); 41 + file >> defending; 42 + file >> capturing; 43 + file >> captureFrom; 44 + file >> arrived; 45 + } 46 +}; 47 + 48 +double captureSupply(Empire& emp, Object& check) { 49 + double loy = check.getLoyaltyFacing(emp); 50 + double cost = config::SIEGE_LOYALTY_SUPPLY_COST * loy; 51 + cost *= emp.CaptureSupplyFactor; 52 + cost *= check.owner.CaptureSupplyDifficulty; 53 + return cost; 54 +} 55 + 56 +class Battle { 57 + SystemAI@ system; 58 + Region@ staging; 59 + array<BattleMission@> fleets; 60 + uint curPriority = MiP_Critical; 61 + bool isAttack = false; 62 + 63 + double enemyStrength; 64 + double ourStrength; 65 + double lastCombat = 0; 66 + double bestCapturePct; 67 + double lastHadFleets = 0; 68 + bool inCombat = false; 69 + bool isUnderSiege = false; 70 + 71 + Planet@ defendPlanet; 72 + Object@ eliminate; 73 + 74 + Battle() { 75 + lastHadFleets = gameTime; 76 + lastCombat = gameTime; 77 + } 78 + 79 + void save(War& war, SaveFile& file) { 80 + war.systems.saveAI(file, system); 81 + 82 + uint cnt = fleets.length; 83 + file << cnt; 84 + for(uint i = 0; i < cnt; ++i) 85 + war.fleets.saveMission(file, fleets[i]); 86 + 87 + file << curPriority; 88 + file << isAttack; 89 + file << lastCombat; 90 + file << inCombat; 91 + file << defendPlanet; 92 + file << eliminate; 93 + file << isUnderSiege; 94 + file << bestCapturePct; 95 + } 96 + 97 + void load(War& war, SaveFile& file) { 98 + @system = war.systems.loadAI(file); 99 + 100 + uint cnt = 0; 101 + file >> cnt; 102 + for(uint i = 0; i < cnt; ++i) { 103 + auto@ miss = cast<BattleMission>(war.fleets.loadMission(file)); 104 + if(miss !is null) 105 + fleets.insertLast(miss); 106 + } 107 + 108 + file >> curPriority; 109 + file >> isAttack; 110 + file >> lastCombat; 111 + file >> inCombat; 112 + file >> defendPlanet; 113 + file >> eliminate; 114 + file >> isUnderSiege; 115 + file >> bestCapturePct; 116 + } 117 + 118 + BattleMission@ join(AI& ai, War& war, FleetAI@ flAI) { 119 + BattleMission mission; 120 + @mission.fleet = flAI; 121 + @mission.battleIn = system.obj; 122 + mission.priority = curPriority; 123 + 124 + Object@ moveTo = system.obj; 125 + if(defendPlanet !is null) 126 + @moveTo = defendPlanet; 127 + else if(eliminate !is null && eliminate.isShip) 128 + @moveTo = eliminate; 129 + @mission.move = war.movement.move(flAI.obj, moveTo, MP_Critical, spread=true, nearOnly=true); 130 + 131 + //Station this fleet nearby after the battle is over 132 + if(staging !is null) 133 + war.military.stationFleet(flAI, staging); 134 + 135 + if(war.log) 136 + ai.print("Assign to battle at "+system.obj.name 137 + +" for strength "+standardize(ourStrength * 0.001, true) 138 + + " vs their "+standardize(enemyStrength * 0.001, true), flAI.obj); 139 + 140 + fleets.insertLast(mission); 141 + war.fleets.performMission(flAI, mission); 142 + return mission; 143 + } 144 + 145 + bool stayingHere(Object@ other) { 146 + if(other is null || !other.hasMover) 147 + return true; 148 + if(!inRegion(system.obj, other.position)) 149 + return false; 150 + double acc = other.maxAcceleration; 151 + if(acc <= 0.0001) 152 + return true; 153 + vec3d compDest = other.computedDestination; 154 + if(inRegion(system.obj, compDest)) 155 + return true; 156 + if(inRegion(system.obj, other.position + other.velocity * 10.0)) 157 + return true; 158 + return false; 159 + } 160 + 161 + bool tick(AI& ai, War& war, double time) { 162 + //Compute strength values 163 + enemyStrength = getTotalFleetStrength(system.obj, ai.enemyMask, planets=true); 164 + ourStrength = 0; 165 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) 166 + ourStrength += sqrt(fleets[i].fleet.strength); 167 + ourStrength *= ourStrength; 168 + inCombat = false; 169 + bool ourPlanetsPresent = system.obj.PlanetsMask & (ai.allyMask | ai.mask) != 0; 170 + 171 + if((enemyStrength < 0.01 || !ourPlanetsPresent) && defendPlanet is null) 172 + isUnderSiege = false; 173 + 174 + //Remove lost fleets 175 + bool anyArrived = false; 176 + bestCapturePct = 0.0; 177 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 178 + auto@ miss = fleets[i]; 179 + miss.priority = curPriority; 180 + if(!miss.fleet.obj.valid || miss.canceled) { 181 + if(!miss.fleet.obj.valid) { 182 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 183 + Empire@ other = getEmpire(i); 184 + if(!other.major || !ai.empire.isHostile(other)) 185 + continue; 186 + if(system.obj.ContestedMask & other.mask != 0) 187 + war.relations.recordLostTo(other, miss.fleet.obj); 188 + } 189 + } 190 + miss.canceled = true; 191 + if(war.log) 192 + ai.print("BATTLE: lost fleet "+miss.fleet.obj.name, system.obj); 193 + fleets.removeAt(i); 194 + --i; --cnt; 195 + if(fleets.length == 0) 196 + lastHadFleets = gameTime; 197 + continue; 198 + } 199 + if(miss.move !is null) { 200 + if(miss.move.failed) { 201 + miss.canceled = true; 202 + if(war.log) 203 + ai.print("BATTLE: move failed on lost fleet "+miss.fleet.obj.name, system.obj); 204 + fleets.removeAt(i); 205 + --i; --cnt; 206 + if(fleets.length == 0) 207 + lastHadFleets = gameTime; 208 + continue; 209 + } 210 + if(miss.move.completed) { 211 + miss.arrived = true; 212 + @miss.move = null; 213 + } 214 + } 215 + if(miss.arrived) { 216 + anyArrived = true; 217 + 218 + bool shouldRetreat = false; 219 + if(miss.fleet.supplies < 0.05) { 220 + if(isCapturingAny && eliminate is null) 221 + shouldRetreat = true; 222 + else if(ourStrength < enemyStrength * 0.75) 223 + shouldRetreat = true; 224 + } 225 + if(miss.fleet.fleetHealth < 0.25) { 226 + if(ourStrength < enemyStrength * 0.5) 227 + shouldRetreat = true; 228 + } 229 + //DOF - Adding some leader based checks 230 + if(miss.fleet.flagshipHealth < 0.5) { 231 + if(ourStrength < enemyStrength * 0.75) 232 + shouldRetreat = true; 233 + } 234 + if(shouldRetreat) { 235 + war.fleets.returnToBase(miss.fleet); 236 + fleets.removeAt(i); 237 + miss.canceled = true; 238 + --i; --cnt; 239 + if(fleets.length == 0) 240 + lastHadFleets = gameTime; 241 + continue; 242 + } 243 + } 244 + if(miss.capturing !is null) 245 + bestCapturePct = max(bestCapturePct, miss.capturing.capturePct); 246 + } 247 + 248 + //Defend our planets 249 + if(defendPlanet is null) { 250 + Planet@ defPl; 251 + double bestWeight = 0.0; 252 + for(uint i = 0, cnt = system.planets.length; i < cnt; ++i) { 253 + Planet@ pl = system.planets[i]; 254 + double w = 1.0; 255 + if(pl.owner is ai.empire) 256 + w *= 2.0; 257 + else if(pl.owner.mask & ai.allyMask != 0) 258 + w *= 0.5; 259 + else 260 + continue; 261 + double capt = pl.capturePct; 262 + if(capt <= 0.01) 263 + continue; 264 + w *= capt; 265 + 266 + if(!pl.enemiesInOrbit) 267 + continue; 268 + if(w > bestWeight) { 269 + bestWeight = w; 270 + @defPl = pl; 271 + } 272 + } 273 + 274 + if(defPl !is null) { 275 + if(war.log) 276 + ai.print("BATTLE: protect planet "+defPl.name, system.obj); 277 + 278 + @defendPlanet = defPl; 279 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) 280 + moveTo(fleets[i], defendPlanet, force=true); 281 + } 282 + } 283 + else { 284 + //Check if there are still enemies in orbit 285 + if(!defendPlanet.enemiesInOrbit || !defendPlanet.valid || defendPlanet.owner.isHostile(ai.empire)) 286 + @defendPlanet = null; 287 + } 288 + if(defendPlanet !is null) { 289 + //Make sure one fleet is in orbit 290 + inCombat = true; 291 + isUnderSiege = true; 292 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) 293 + moveTo(fleets[i], defendPlanet); 294 + } 295 + 296 + //Eliminate any remaining threats 297 + if(!inCombat) { 298 + //Eliminate any hostile targets in the system 299 + if(eliminate !is null) { 300 + //Make sure this is still a valid target to eliminate 301 + bool valid = true; 302 + if(!eliminate.valid) { 303 + valid = false; 304 + war.relations.recordTakenFrom(eliminate.owner, eliminate); 305 + } 306 + else if(!stayingHere(eliminate)) 307 + valid = false; 308 + else if(!eliminate.isVisibleTo(ai.empire)) 309 + valid = false; 310 + else if(!ai.empire.isHostile(eliminate.owner)) 311 + valid = false; 312 + 313 + if(!valid) { 314 + @eliminate = null; 315 + clearOrders(); 316 + } 317 + else { 318 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) 319 + attack(fleets[i], eliminate); 320 + inCombat = true; 321 + } 322 + } 323 + else { 324 + //Find a new target to eliminate 325 + Object@ check = findEnemy(system.obj, ai.empire, ai.enemyMask); 326 + if(check !is null) { 327 + if(stayingHere(check)) { 328 + @eliminate = check; 329 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 330 + auto@ obj = fleets[i].fleet.obj; 331 + if(!fleets[i].arrived) 332 + continue; 333 + obj.addAttackOrder(eliminate); 334 + } 335 + 336 + if(war.log) 337 + ai.print("BATTLE: Eliminate "+eliminate.name, system.obj); 338 + 339 + inCombat = true; 340 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) 341 + attack(fleets[i], eliminate, force=true); 342 + } 343 + } 344 + } 345 + } 346 + else { 347 + @eliminate = null; 348 + } 349 + 350 + //Capture enemy planets if possible 351 + //TODO: Respond to defense by abandoning all but 1 capture and swarming around the best one 352 + if(!inCombat) { 353 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 354 + auto@ miss = fleets[i]; 355 + if(miss.capturing !is null) { 356 + if(canCapture(ai, miss, miss.capturing) && miss.fleet.remainingSupplies > captureSupply(ai.empire, miss.capturing) && miss.fleet.obj.hasOrders) { 357 + inCombat = true; 358 + continue; 359 + } 360 + else { 361 + if(miss.capturing.owner is ai.empire && miss.captureFrom !is null) 362 + war.relations.recordTakenFrom(miss.captureFrom, miss.capturing); 363 + @miss.capturing = null; 364 + @miss.captureFrom = null; 365 + } 366 + } 367 + if(!miss.arrived) 368 + continue; 369 + 370 + Planet@ bestCapture; 371 + double totalWeight = 0; 372 + 373 + for(uint i = 0, cnt = system.planets.length; i < cnt; ++i) { 374 + Planet@ check = system.planets[i]; 375 + if(!canCapture(ai, miss, check)) 376 + continue; 377 + 378 + //Don't send two fleets to the same thing 379 + if(isCapturing(check)) 380 + continue; 381 + 382 + //Make sure we have the supplies remaining to capture 383 + if(miss.fleet.remainingSupplies < captureSupply(ai.empire, check) * ai.behavior.captureSupplyEstimate) 384 + continue; 385 + 386 + double str = check.getFleetStrength(); 387 + double w = 1.0; 388 + w *= check.getLoyaltyFacing(ai.empire); 389 + if(str != 0) 390 + w /= str; 391 + 392 + totalWeight += w; 393 + if(randomd() < w / totalWeight) 394 + @bestCapture = check; 395 + } 396 + 397 + if(bestCapture !is null) { 398 + if(war.log) 399 + ai.print("BATTLE: Capture "+bestCapture.name+" with "+miss.fleet.obj.name, system.obj); 400 + 401 + @miss.capturing = bestCapture; 402 + @miss.captureFrom = bestCapture.owner; 403 + miss.fleet.obj.addCaptureOrder(bestCapture); 404 + inCombat = true; 405 + } 406 + } 407 + } 408 + else { 409 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 410 + auto@ miss = fleets[i]; 411 + @miss.capturing = null; 412 + @miss.captureFrom = null; 413 + } 414 + } 415 + 416 + //Keep fleets here in non-critical mode for a few minutes 417 + if(!inCombat && (anyArrived || !isAttack)) { 418 + //TODO: Don't start this countdown until we've actually arrived 419 + if(gameTime > lastCombat + 90.0) { 420 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) 421 + fleets[i].completed = true; 422 + if(war.log) 423 + ai.print("BATTLE: ended", system.obj); 424 + return false; 425 + } 426 + else if(gameTime > lastCombat + 30.0) { 427 + curPriority = MiP_Normal; 428 + } 429 + else { 430 + curPriority = MiP_High; 431 + } 432 + } 433 + else { 434 + if(ourPlanetsPresent && isUnderSiege) { 435 + curPriority = MiP_Critical; 436 + if(war.winnability(this) < 0.5) 437 + curPriority = MiP_High; 438 + } 439 + else if(bestCapturePct > 0.75) 440 + curPriority = MiP_Critical; 441 + else 442 + curPriority = MiP_High; 443 + lastCombat = gameTime; 444 + } 445 + 446 + //If needed, claim fleets 447 + if(ourStrength < enemyStrength * ai.behavior.battleStrengthOverkill) { 448 + FleetAI@ claim; 449 + double bestWeight = 0; 450 + 451 + for(uint i = 0, cnt = war.fleets.fleets.length; i < cnt; ++i) { 452 + auto@ fleet = war.fleets.fleets[i]; 453 + double w = war.assignable(this, fleet); 454 + 455 + if(w > bestWeight) { 456 + bestWeight = w; 457 + @claim = fleet; 458 + } 459 + } 460 + 461 + if(claim !is null) 462 + join(ai, war, claim); 463 + } 464 + 465 + //Give up the battle when we should 466 + if(fleets.length == 0) { 467 + if(!ourPlanetsPresent && !isAttack) { 468 + //We lost all our planets before we could respond with anything. 469 + // We might be able to use an attack to claim it back later, but for now we just give up on it. 470 + if(war.log) 471 + ai.print("BATTLE: aborted defense, no fleets and no planets left", system.obj); 472 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) 473 + fleets[i].canceled = true; 474 + return false; 475 + } 476 + if(isAttack) { 477 + //We haven't been able to find any fleets to assign here for a while, 478 + //so just abort the attack 479 + if(gameTime - lastHadFleets > 60.0) { 480 + if(war.log) 481 + ai.print("BATTLE: aborted attack, no fleets available", system.obj); 482 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) 483 + fleets[i].canceled = true; 484 + return false; 485 + } 486 + } 487 + } 488 + 489 + return true; 490 + } 491 + 492 + void clearOrders() { 493 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 494 + auto@ obj = fleets[i].fleet.obj; 495 + if(!fleets[i].arrived) 496 + continue; 497 + if(obj.hasOrders) 498 + obj.clearOrders(); 499 + } 500 + } 501 + 502 + bool canCapture(AI& ai, BattleMission@ miss, Planet@ check) { 503 + if(!ai.empire.isHostile(check.owner)) 504 + return false; 505 + //TODO: Wait around a while maybe? 506 + if(check.isProtected(ai.empire)) 507 + return false; 508 + return true; 509 + } 510 + 511 + void moveTo(BattleMission@ miss, Planet@ defPl, bool force = false) { 512 + if(!miss.arrived) 513 + return; 514 + if(!force) { 515 + if(miss.fleet.obj.hasOrders) 516 + return; 517 + double dist = miss.fleet.obj.position.distanceTo(defPl.position); 518 + if(dist < defPl.OrbitSize) 519 + return; 520 + } 521 + vec3d pos = defPl.position; 522 + vec2d offset = random2d(defPl.OrbitSize * 0.85); 523 + pos.x += offset.x; 524 + pos.z += offset.y; 525 + miss.fleet.obj.addMoveOrder(pos); 526 + } 527 + 528 + void attack(BattleMission@ miss, Object@ target, bool force = false) { 529 + //TODO: make this not chase stuff out of the system like a madman? 530 + // (in attack logic as well) 531 + if(!miss.arrived) 532 + return; 533 + if(!force) { 534 + if(miss.fleet.obj.hasOrders) 535 + return; 536 + } 537 + miss.fleet.obj.addAttackOrder(target); 538 + } 539 + 540 + bool isCapturing(Planet@ pl) { 541 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 542 + if(fleets[i].capturing is pl) 543 + return true; 544 + } 545 + return false; 546 + } 547 + 548 + bool get_isCapturingAny() { 549 + for(uint i = 0, cnt = fleets.length; i < cnt; ++i) { 550 + if(fleets[i].capturing !is null) 551 + return true; 552 + } 553 + return false; 554 + } 555 +}; 556 + 557 +class War : AIComponent { 558 + Fleets@ fleets; 559 + Intelligence@ intelligence; 560 + Relations@ relations; 561 + Movement@ movement; 562 + Scouting@ scouting; 563 + Systems@ systems; 564 + Military@ military; 565 + 566 + array<Battle@> battles; 567 + 568 + ScoutingMission@ currentScout; 569 + 570 + void create() { 571 + @fleets = cast<Fleets>(ai.fleets); 572 + @intelligence = cast<Intelligence>(ai.intelligence); 573 + @relations = cast<Relations>(ai.relations); 574 + @movement = cast<Movement>(ai.movement); 575 + @scouting = cast<Scouting>(ai.scouting); 576 + @systems = cast<Systems>(ai.systems); 577 + @military = cast<Military>(ai.military); 578 + } 579 + 580 + void save(SaveFile& file) { 581 + uint cnt = battles.length; 582 + file << cnt; 583 + for(uint i = 0; i < cnt; ++i) 584 + battles[i].save(this, file); 585 + 586 + fleets.saveMission(file, currentScout); 587 + } 588 + 589 + void load(SaveFile& file) { 590 + uint cnt = 0; 591 + file >> cnt; 592 + battles.length = cnt; 593 + for(uint i = 0; i < cnt; ++i) { 594 + @battles[i] = Battle(); 595 + battles[i].load(this, file); 596 + } 597 + 598 + @currentScout = cast<ScoutingMission>(fleets.loadMission(file)); 599 + ai.behavior.remnantAllowArbitraryClear = false; 600 + } 601 + 602 + double getCombatReadyStrength() { 603 + double str = 0; 604 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 605 + auto@ flAI = fleets.fleets[i]; 606 + if(flAI.fleetClass != FC_Combat) 607 + continue; 608 + if(!flAI.readyForAction) 609 + continue; 610 + str += flAI.strength; 611 + } 612 + return str * str; 613 + } 614 + 615 + Battle@ attack(SystemAI@ sys) { 616 + Battle atk; 617 + @atk.system = sys; 618 + atk.isAttack = true; 619 + atk.curPriority = MiP_High; 620 + @atk.staging = military.getStagingFor(sys.obj); 621 + 622 + if(log) 623 + ai.print("Organizing an attack against "+sys.obj.name); 624 + 625 + claimFleetsFor(atk); 626 + battles.insertLast(atk); 627 + return atk; 628 + } 629 + 630 + Battle@ defend(SystemAI@ sys) { 631 + Battle def; 632 + @def.system = sys; 633 + @def.staging = military.getClosestStaging(sys.obj); 634 + 635 + if(log) 636 + ai.print("Organizing defense for "+sys.obj.name); 637 + 638 + battles.insertLast(def); 639 + return def; 640 + } 641 + 642 + void claimFleetsFor(Battle@ atk) { 643 + //TODO: This currently claims everything not in use, should it 644 + //leave some reserves for defense? Is that good? 645 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 646 + auto@ flAI = fleets.fleets[i]; 647 + if(flAI.fleetClass != FC_Combat) 648 + continue; 649 + if(!flAI.readyForAction) 650 + continue; 651 + atk.join(ai, this, flAI); 652 + } 653 + } 654 + 655 + void sendFleetToJoin(Battle@ atk) { 656 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 657 + auto@ flAI = fleets.fleets[i]; 658 + if(flAI.fleetClass != FC_Combat) 659 + continue; 660 + if(!flAI.readyForAction) 661 + continue; 662 + atk.join(ai, this, flAI); 663 + break; 664 + } 665 + } 666 + 667 + bool isFightingIn(Region@ reg) { 668 + for(uint i = 0, cnt = battles.length; i < cnt; ++i) { 669 + if(battles[i].system.obj is reg) 670 + return true; 671 + } 672 + return false; 673 + } 674 + 675 + void tick(double time) override { 676 + for(uint i = 0, cnt = battles.length; i < cnt; ++i) { 677 + if(!battles[i].tick(ai, this, time)) { 678 + battles.removeAt(i); 679 + --i; --cnt; 680 + } 681 + } 682 + } 683 + 684 + double ourStrength; 685 + double curTime; 686 + SystemAI@ best; 687 + double totalWeight; 688 + SystemAI@ scout; 689 + uint scoutCount; 690 + void check(SystemAI@ sys, double baseWeight) { 691 + if(isFightingIn(sys.obj)) 692 + return; 693 + 694 + if(!sys.visible) { 695 + sys.strengthCheck(ai, minInterval=5*60.0); 696 + if(sys.lastStrengthCheck < curTime - 5 * 60.0) { 697 + scoutCount += 1; 698 + if(randomd() < 1.0 / double(scoutCount)) 699 + @scout = sys; 700 + return; 701 + } 702 + } 703 + else { 704 + sys.strengthCheck(ai, minInterval=60.0); 705 + } 706 + 707 + double theirStrength = sys.enemyStrength; 708 + if(ourStrength < theirStrength * ai.behavior.attackStrengthOverkill) 709 + return; 710 + 711 + double w = baseWeight; 712 + 713 + //Try to capture less important systems at first 714 + //TODO: This should flip when we go from border skirmishes to subjugation war 715 + uint capturable = 0; 716 + for(uint i = 0, cnt = sys.planets.length; i < cnt; ++i) { 717 + Planet@ pl = sys.planets[i]; 718 + if(!ai.empire.isHostile(pl.owner)) 719 + continue; 720 + if(pl.isProtected(ai.empire)) 721 + continue; 722 + w /= 1.0 + double(pl.level); 723 + capturable += 1; 724 + } 725 + 726 + //Ignore protected systems 727 + if(capturable == 0) 728 + return; 729 + 730 + //See where their defenses are low 731 + if(theirStrength != 0) { 732 + double defRatio = ourStrength / theirStrength; 733 + if(defRatio > 4.0) { 734 + //We prefer destroying some minor assets over fighting an entirely undefended system, 735 + //because it hurts more to lose stuff. 736 + w *= 6.0; 737 + } 738 + } 739 + else { 740 + w *= 2.0; 741 + } 742 + 743 + totalWeight += w; 744 + if(randomd() < w / totalWeight) 745 + @best = sys; 746 + } 747 + 748 + int totalEnemySize(SystemAI@ sys) { 749 + int size = 0; 750 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 751 + Empire@ other = getEmpire(i); 752 + if(other.major && ai.empire.isHostile(other)) 753 + size += sys.obj.getStrength(other); 754 + } 755 + return size; 756 + } 757 + 758 + bool isUnderAttack(SystemAI@ sys) { 759 + if(sys.obj.ContestedMask & ai.mask == 0) 760 + return false; 761 + if(totalEnemySize(sys) < 100) { 762 + if(sys.obj.SiegedMask & ai.mask == 0) 763 + return false; 764 + } 765 + return true; 766 + } 767 + 768 + double assignable(Battle& battle, FleetAI& fleet) { 769 + if(fleet.fleetClass != FC_Combat) 770 + return 0.0; 771 + double w = 1.0; 772 + if(fleet.mission !is null) { 773 + w *= 0.1; 774 + if(fleet.mission.priority >= MiP_High) 775 + w *= 0.1; 776 + if(fleet.mission.priority >= battle.curPriority) 777 + return 0.0; 778 + 779 + auto@ miss = cast<BattleMission>(fleet.mission); 780 + if(miss !is null && miss.battleIn is battle.system.obj) 781 + return 0.0; 782 + } 783 + else if(fleet.isHome && fleet.stationed is battle.system.obj) { 784 + //This should be allowed to fight always 785 + } 786 + else if(battle.curPriority >= MiP_Critical) { 787 + if(fleet.supplies < 0.25) 788 + return 0.0; 789 + if(fleet.fleetHealth < 0.25) 790 + return 0.0; 791 + if(fleet.filled < 0.2) 792 + return 0.0; 793 + //DOF - Do not send badly damaged flagships 794 + if(fleet.flagshipHealth < 0.5) 795 + return 0.0; 796 + 797 + if(fleet.obj.isMoving) { 798 + if(fleet.obj.velocity.length / fleet.obj.maxAcceleration > 16.0) 799 + w *= 0.1; 800 + } 801 + } 802 + else { 803 + if(!fleet.readyForAction) 804 + return 0.0; 805 + } 806 + double fleetStrength = fleet.strength; 807 + if(battle.ourStrength + fleetStrength < battle.enemyStrength * ai.behavior.battleStrengthOverkill) 808 + w *= 0.25; 809 + return w; 810 + } 811 + 812 + double winnability(Battle& battle) { 813 + double ours = sqrt(battle.ourStrength); 814 + double theirs = battle.enemyStrength; 815 + if(theirs <= 0.0) 816 + return 10.0; 817 + 818 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 819 + auto@ fleet = fleets.fleets[i]; 820 + double w = assignable(battle, fleet); 821 + if(w != 0.0) 822 + ours += sqrt(fleet.strength); 823 + } 824 + ours *= ours; 825 + 826 + return ours / theirs; 827 + } 828 + 829 + void focusTick(double time) override { 830 + if(currentScout !is null) { 831 + if(currentScout.canceled || currentScout.completed) 832 + @currentScout = null; 833 + } 834 + 835 + //Change our behavior a little depending on the state 836 + ai.behavior.remnantAllowArbitraryClear = !relations.isFightingWar(aggressive=true) && battles.length == 0; 837 + 838 + //Find any systems that need defending 839 + //TODO: Defend allies at lowered priority 840 + if(ai.behavior.forbidDefense) { 841 + for(uint i = 0, cnt = systems.owned.length; i < cnt; ++i) { 842 + SystemAI@ sys = systems.owned[i]; 843 + if(!isUnderAttack(sys)) 844 + continue; 845 + if(isFightingIn(sys.obj)) 846 + continue; 847 + defend(sys); 848 + return; 849 + } 850 + } 851 + 852 + if(ai.behavior.forbidAttack) 853 + return; 854 + //Do attacks 855 + uint ready = fleets.countCombatReadyFleets(); 856 + if(ready != 0) { 857 + //See if we can start a new attack 858 + if(battles.length < ai.behavior.maxBattles && relations.isFightingWar(aggressive=true) 859 + && (battles.length == 0 || ready > ai.behavior.battleReserveFleets)) { 860 + //Determine our own strength 861 + ourStrength = getCombatReadyStrength(); 862 + 863 + //Evaluate systems to attack in our aggressive war 864 + @best = null; 865 + totalWeight = 0; 866 + curTime = gameTime; 867 + @scout = null; 868 + scoutCount = 0; 869 + //TODO: Consider aggressive wars against an empire to also be against that empire's vassals 870 + for(uint i = 0, cnt = intelligence.intel.length; i < cnt; ++i) { 871 + auto@ intel = intelligence.intel[i]; 872 + if(intel is null) 873 + continue; 874 + 875 + auto@ relation = relations.get(intel.empire); 876 + if(!relation.atWar || !relation.aggressive) 877 + continue; 878 + 879 + for(uint n = 0, ncnt = intel.shared.length; n < ncnt; ++n) { 880 + auto@ sys = intel.shared[n]; 881 + check(sys, 20.0); 882 + } 883 + 884 + for(uint n = 0, ncnt = intel.theirBorder.length; n < ncnt; ++n) { 885 + auto@ sys = intel.theirBorder[n]; 886 + check(sys, 1.0); 887 + } 888 + } 889 + 890 + //Make the attack with our fleets 891 + if(best !is null) 892 + attack(best); 893 + else if(scout !is null && currentScout is null) { 894 + if(log) 895 + ai.print("War requests scout to flyby "+scout.obj.name); 896 + @currentScout = scouting.scout(scout.obj); 897 + } 898 + } 899 + } 900 + } 901 +}; 902 + 903 +AIComponent@ createWar() { 904 + return War(); 905 +}
Added scripts/server/empire_ai/weasel/WeaselAI.as.
1 +import settings.game_settings; 2 +from empire_ai.EmpireAI import AIController; 3 + 4 +import AIComponent@ createEvents() from "empire_ai.weasel.Events"; 5 +import AIComponent@ createColonization() from "empire_ai.weasel.Colonization"; 6 +import AIComponent@ createResources() from "empire_ai.weasel.Resources"; 7 +import AIComponent@ createPlanets() from "empire_ai.weasel.Planets"; 8 +import AIComponent@ createSystems() from "empire_ai.weasel.Systems"; 9 +import AIComponent@ createFleets() from "empire_ai.weasel.Fleets"; 10 +import AIComponent@ createScouting() from "empire_ai.weasel.Scouting"; 11 +import AIComponent@ createDevelopment() from "empire_ai.weasel.Development"; 12 +import AIComponent@ createDesigns() from "empire_ai.weasel.Designs"; 13 +import AIComponent@ createBudget() from "empire_ai.weasel.Budget"; 14 +import AIComponent@ createConstruction() from "empire_ai.weasel.Construction"; 15 +import AIComponent@ createMilitary() from "empire_ai.weasel.Military"; 16 +import AIComponent@ createMovement() from "empire_ai.weasel.Movement"; 17 +import AIComponent@ createCreeping() from "empire_ai.weasel.Creeping"; 18 +import AIComponent@ createRelations() from "empire_ai.weasel.Relations"; 19 +import AIComponent@ createIntelligence() from "empire_ai.weasel.Intelligence"; 20 +import AIComponent@ createWar() from "empire_ai.weasel.War"; 21 +import AIComponent@ createResearch() from "empire_ai.weasel.Research"; 22 +import AIComponent@ createEnergy() from "empire_ai.weasel.Energy"; 23 +import IAIComponent@ createDiplomacy() from "empire_ai.weasel.Diplomacy"; 24 +import AIComponent@ createConsider() from "empire_ai.weasel.Consider"; 25 +import AIComponent@ createOrbitals() from "empire_ai.weasel.Orbitals"; 26 +import AIComponent@ createInfrastructure() from "empire_ai.weasel.Infrastructure"; 27 + 28 +import AIComponent@ createHyperdrive() from "empire_ai.weasel.ftl.Hyperdrive"; 29 +import AIComponent@ createGate() from "empire_ai.weasel.ftl.Gate"; 30 +import AIComponent@ createFling() from "empire_ai.weasel.ftl.Fling"; 31 +import AIComponent@ createSlipstream() from "empire_ai.weasel.ftl.Slipstream"; 32 +import AIComponent@ createJumpdrive() from "empire_ai.weasel.ftl.Jumpdrive"; 33 + 34 +import AIComponent@ createVerdant() from "empire_ai.weasel.race.Verdant"; 35 +import AIComponent@ createMechanoid() from "empire_ai.weasel.race.Mechanoid"; 36 +import AIComponent@ createStarChildren() from "empire_ai.weasel.race.StarChildren"; 37 +import AIComponent@ createExtragalactic() from "empire_ai.weasel.race.Extragalactic"; 38 +import AIComponent@ createLinked() from "empire_ai.weasel.race.Linked"; 39 +import AIComponent@ createDevout() from "empire_ai.weasel.race.Devout"; 40 +import AIComponent@ createAncient() from "empire_ai.weasel.race.Ancient"; 41 + 42 +import AIComponent@ createInvasion() from "empire_ai.weasel.misc.Invasion"; 43 +import bool hasInvasionMap() from "Invasion.InvasionMap"; 44 + 45 +from buildings import BuildingType; 46 +from orbitals import OrbitalModule; 47 +from constructions import ConstructionType; 48 +import util.formatting; 49 + 50 +from empire import ai_full_speed; 51 + 52 +from traits import getTraitID; 53 + 54 +export IAIComponent, AIComponent, AI; 55 +uint GUARD = 0xDEADBEEF; 56 + 57 +enum AddedComponent { 58 + AC_Invasion = 0x1, 59 +}; 60 + 61 +interface IAIComponent : Savable { 62 + void set(AI& ai); 63 + void setLog(); 64 + void setLogCritical(); 65 + 66 + double getPrevFocus(); 67 + void setPrevFocus(double value); 68 + 69 + void create(); 70 + void start(); 71 + 72 + void tick(double time); 73 + void focusTick(double time); 74 + void turn(); 75 + 76 + void save(SaveFile& file); 77 + void load(SaveFile& file); 78 + void postLoad(AI& ai); 79 + void postSave(AI& ai); 80 + void loadFinalize(AI& ai); 81 +}; 82 + 83 +class AIComponent : IAIComponent, Savable { 84 + AI@ ai; 85 + double prevFocus = 0; 86 + bool log = false; 87 + bool logCritical = false; 88 + bool logErrors = true; 89 + 90 + double getPrevFocus() { return prevFocus; } 91 + void setPrevFocus(double value) { prevFocus = value; } 92 + 93 + void setLog() { log = true; } 94 + void setLogCritical() { logCritical = true; } 95 + 96 + void set(AI& ai) { @this.ai = ai; } 97 + void create() {} 98 + void start() {} 99 + 100 + void tick(double time) {} 101 + void focusTick(double time) {} 102 + void turn() {} 103 + 104 + void save(SaveFile& file) {} 105 + void load(SaveFile& file) {} 106 + void postLoad(AI& ai) {} 107 + void postSave(AI& ai) {} 108 + void loadFinalize(AI& ai) {} 109 +}; 110 + 111 +class ProfileData { 112 + double tickPeak = 0.0; 113 + double tickAvg = 0.0; 114 + double tickCount = 0.0; 115 + 116 + double focusPeak = 0.0; 117 + double focusAvg = 0.0; 118 + double focusCount = 0.0; 119 +}; 120 + 121 +final class AIBehavior { 122 + //AIEmpire controls 123 + bool forbidDiplomacy = false; 124 + bool forbidColonization = false; 125 + bool forbidCreeping = false; 126 + bool forbidResearch = false; 127 + bool forbidDefense = false; 128 + bool forbidAttack = false; 129 + bool forbidConstruction = false; 130 + bool forbidScouting = false; 131 + bool forbidAnomalyChoice = false; 132 + bool forbidArtifact = false; 133 + bool forbidScuttle = false; 134 + 135 + //How many focuses we can manage in a tick 136 + uint focusPerTick = 2; 137 + 138 + //The maximum colonizations this AI can do in one turn 139 + uint maxColonizations = UINT_MAX; 140 + //How many colonizations we're guaranteed to be able to do in one turn regardless of finances 141 + uint guaranteeColonizations = 2; 142 + //How many colonizations at most we can be doing at once 143 + uint maxConcurrentColonizations = UINT_MAX; 144 + //Whether this AI will colonize planets in systems owned by someone else 145 + // TODO: This should be partially ignored for border systems, so it can try to aggressively expand into the border 146 + bool colonizeEnemySystems = false; 147 + bool colonizeNeutralOwnedSystems = false; 148 + bool colonizeAllySystems = false; 149 + //How much this AI values claiming new systems instead of colonizing stuff in its existing ones 150 + double weightOutwardExpand = 2.0; 151 + //How much money this AI considers a colonization event to cost out of the budget 152 + int colonizeBudgetCost = 80; 153 + //Whether to do any generic expansion beyond any requests 154 + bool colonizeGenericExpand = true; 155 + //Latest percentage into a budget cycle that we still allow colonization 156 + double colonizeMaxBudgetProgress = 0.66; 157 + //Time after initial ownership change that an incomplete colonization is canceled 158 + double colonizeFailGraceTime = 100.0; 159 + //Time a planet that we failed to colonize is disregarded for colonization 160 + double colonizePenalizeTime = 9.0 * 60.0; 161 + 162 + //Maximum amount of scouting missions that can be performed simultaneously 163 + uint maxScoutingMissions = UINT_MAX; 164 + //Minimum time after losing vision over a system that we can scout it again 165 + double minScoutingInterval = 3.0 * 60.0; 166 + //Weight that it gives to exploring things near our empire instead of greedily exploring nearby things 167 + double exploreBorderWeight = 2.0; 168 + //How long we consider all fleets viable for scouting with 169 + double scoutAllTimer = 3.0 * 60.0; 170 + //How many scouts we want to have active 171 + uint scoutsActive = 2; 172 + //How many scanning missions we can do at once 173 + uint maxScanningMissions = 1; 174 + //Whether to prioritize scouting over scanning if we only have one scout 175 + bool prioritizeScoutOverScan = true; 176 + 177 + //Weights for what to do in generic planet development 178 + // Leveling up an existing development focus 179 + double focusDevelopWeight = 1.0; 180 + // Colonizing a new scalable or high tier to focus on 181 + double focusColonizeNewWeight = 4.0; 182 + // Colonizing a new high tier resource to import to one of our focuses 183 + double focusColonizeHighTierWeight = 1.0; 184 + 185 + //How many potential designs are evaluated before choosing the best one 186 + uint designEvaluateCount = 10; 187 + //How long a fleet has to be fully idle before it returns to its stationed system 188 + double fleetIdleReturnStationedTime = 60.0; 189 + //How long we try to have a fleet be capable of firing before running out of supplies 190 + double fleetAimSupplyDuration = 2.0 * 60.0; 191 + 192 + //How long a potential construction can take at most before we consider it unreasonable 193 + double constructionMaxTime = 10.0 * 60.0; 194 + //How long a factory has to have been idle for us to consider constructing labor storage 195 + double laborStoreIdleTimer = 60.0; 196 + //Maximum amount of time worth of labor we want to store in our warehouses 197 + double laborStoreMaxFillTime = 60.0 * 10.0; 198 + //Whether to use labor to build asteroids in the background 199 + bool backgroundBuildAsteroids = true; 200 + //Whether to choose the best resource on an asteroid, instead of doing it randomly 201 + bool chooseAsteroidResource = true; 202 + //Whether to distribute labor to shipyards when planets are idle 203 + bool distributeLaborExports = true; 204 + //Whether to build a shipyard to consolidate multiple planets of labor where possible 205 + bool consolidateLaborExports = true; 206 + //Estimate amount of labor spent per point of support ship size 207 + double estSizeSupportLabor = 0.25; 208 + 209 + //Maximum combat fleets we can have in service at once (counts starting fleet(s)) 210 + uint maxActiveFleets = UINT_MAX; 211 + //How much flagship size we try to make per available money 212 + double shipSizePerMoney = 1.0 / 3.5; 213 + //How much flagship size we try to make per available labor 214 + double shipSizePerLabor = 1.0 / 0.33; 215 + //How much maintenance we expect per ship size 216 + double maintenancePerShipSize = 2.0; 217 + //Minimum percentage increase in size before we decide to retrofit a flagship to be bigger 218 + double shipRetrofitThreshold = 0.5; 219 + //Whether to retrofit our free starting fleet if appropriate 220 + bool retrofitFreeFleets = false; 221 + //Minimum percentage of average current flagship size new fleets should be 222 + double flagshipBuildMinAvgSize = 1.00; 223 + //Minimum game time before we consider constructing new flagships 224 + double flagshipBuildMinGameTime = 4.0 * 60.0; 225 + //Whether to build factories when we need labor 226 + bool buildFactoryForLabor = true; 227 + //Whether to build warehouses when we're not using labor 228 + bool buildLaborStorage = true; 229 + //Whether factories can queue labor resource imports when needed 230 + bool allowRequestLaborImports = true; 231 + //Whether fleets with ghosted supports attempt to rebuild the ghosts or just clear them 232 + bool fleetsRebuildGhosts = true; 233 + //When trying to order supports on a fleet, wait for the planet to construct its supports so we can claim them 234 + bool supportOrderWaitOnFactory = true; 235 + 236 + //How much stronger we need to be than a remnant fleet to clear it 237 + double remnantOverkillFactor = 1.5; 238 + //Whether to allow idle fleets to be sent to clear remnants 239 + // Modified by Relations 240 + bool remnantAllowArbitraryClear = true; 241 + 242 + //Whether we should aggressively try to take out enemies 243 + bool aggressive = false; 244 + //Whether to become aggressive after we get boxed in and can no longer expand anywhere 245 + bool aggressiveWhenBoxedIn = false; 246 + //Whether we should never declare war ourselves 247 + bool passive = false; 248 + //Whether to hate human players the most 249 + bool biased = false; 250 + //How much stronger we need to be than someone to declare war out of hatred 251 + double hatredWarOverkill = 0.5; 252 + //How much stronger we need to be than someone to try to take them out in an aggressive war 253 + double aggressiveWarOverkill = 1.5; 254 + //How much stronger we want to be before we attack a system 255 + double attackStrengthOverkill = 1.5; 256 + //How many battles we can be performing at once 257 + uint maxBattles = UINT_MAX; 258 + //How much we try to overkill while fighting 259 + double battleStrengthOverkill = 1.5; 260 + //How many fleets we don't commit to attacks when we're already currently fighting 261 + uint battleReserveFleets = 1; 262 + //How much extra supply we try to have before starting a capture, to make sure we can actually do it 263 + double captureSupplyEstimate = 1.5; 264 + //Maximum hop distance we use as staging areas for our attacks 265 + int stagingMaxHops = 5; 266 + //If our fleet fill is less than this, immediately move back to factory from staging 267 + double stagingToFactoryFill = 0.6; 268 + 269 + //How much ftl is reserved for critical applications 270 + double ftlReservePctCritical = 0.25; 271 + //How much ftl is reserved to not be used for background applications 272 + double ftlReservePctNormal = 0.25; 273 + 274 + //How many artifacts we consider where to use per focus turn 275 + uint artifactFocusConsiderCount = 2; 276 + 277 + //How long after trying to build a generically requested building we give up 278 + double genericBuildExpire = 3.0 * 60.0; 279 + 280 + //How much the hate in a relationship decays to every minute 281 + double hateDecayRate = 0.9; 282 + //How much weaker we need to be to even consider surrender 283 + double surrenderMinStrength = 0.5; 284 + //How many of our total war points have to be taken by an empire for us to surrender 285 + double acceptSurrenderRatio = 0.75; 286 + double offerSurrenderRatio = 0.5; 287 + 288 + void setDifficulty(int diff, uint flags) { 289 + //This changes the behavior values based on difficulty and flags 290 + if(flags & AIF_Aggressive != 0) 291 + aggressive = true; 292 + if(flags & AIF_Passive != 0) 293 + passive = true; 294 + if(flags & AIF_Biased != 0) 295 + biased = true; 296 + 297 + //Low difficulties can't colonize as many things at once 298 + if(diff <= 0) { 299 + maxConcurrentColonizations = 1; 300 + guaranteeColonizations = 1; 301 + weightOutwardExpand = 0.5; 302 + } 303 + else if(diff <= 1) { 304 + maxConcurrentColonizations = 2; 305 + weightOutwardExpand = 1.0; 306 + } 307 + 308 + //Hard AI becomes aggressive when it gets boxed in 309 + aggressiveWhenBoxedIn = diff >= 2; 310 + 311 + //Easy difficulty can't attack and defend at the same time 312 + if(diff <= 0) 313 + maxBattles = 1; 314 + 315 + //Low difficulties aren't as good at managing labor 316 + if(diff <= 0) { 317 + distributeLaborExports = false; 318 + consolidateLaborExports = false; 319 + buildLaborStorage = false; 320 + } 321 + else if(diff <= 1) { 322 + consolidateLaborExports = false; 323 + } 324 + 325 + //Low difficulties aren't as good at managing fleets 326 + if(diff <= 0) { 327 + maxActiveFleets = 2; 328 + retrofitFreeFleets = true; 329 + } 330 + 331 + //Low difficulties aren't as good at scouting 332 + if(diff <= 1) 333 + scoutAllTimer = 0.0; 334 + 335 + //Low difficulties are worse at designing 336 + if(diff <= 0) 337 + designEvaluateCount = 3; 338 + else if(diff <= 1) 339 + designEvaluateCount = 8; 340 + else 341 + designEvaluateCount = 12; 342 + 343 + //Easy is a bit slow 344 + if(diff <= 0) 345 + focusPerTick = 1; 346 + else if(diff >= 2) 347 + focusPerTick = 3; 348 + } 349 +}; 350 + 351 +final class AIDefs { 352 + const BuildingType@ Factory; 353 + const BuildingType@ LaborStorage; 354 + const ConstructionType@ MoonBase; 355 + const OrbitalModule@ Shipyard; 356 + const OrbitalModule@ TradeOutpost; 357 + const OrbitalModule@ TradeStation; 358 +}; 359 + 360 +final class AI : AIController, Savable { 361 + Empire@ empire; 362 + AIBehavior behavior; 363 + AIDefs defs; 364 + 365 + int cycleId = -1; 366 + uint componentCycle = 0; 367 + uint addedComponents = 0; 368 + 369 + uint majorMask = 0; 370 + uint difficulty = 0; 371 + uint flags = 0; 372 + bool isLoading = false; 373 + 374 + array<IAIComponent@> components; 375 + array<ProfileData> profileData; 376 + IAIComponent@ events; 377 + IAIComponent@ fleets; 378 + IAIComponent@ budget; 379 + IAIComponent@ colonization; 380 + IAIComponent@ resources; 381 + IAIComponent@ planets; 382 + IAIComponent@ systems; 383 + IAIComponent@ scouting; 384 + IAIComponent@ development; 385 + IAIComponent@ designs; 386 + IAIComponent@ construction; 387 + IAIComponent@ military; 388 + IAIComponent@ movement; 389 + IAIComponent@ creeping; 390 + IAIComponent@ relations; 391 + IAIComponent@ intelligence; 392 + IAIComponent@ war; 393 + IAIComponent@ research; 394 + IAIComponent@ energy; 395 + IAIComponent@ diplomacy; 396 + IAIComponent@ consider; 397 + IAIComponent@ orbitals; 398 + IAIComponent@ infrastructure; 399 + 400 + IAIComponent@ ftl; 401 + IAIComponent@ race; 402 + 403 + IAIComponent@ invasion; 404 + 405 + void createComponents() { 406 + //NOTE: This is also save/load order, so 407 + //make sure to add loading logic when changing this list 408 + @events = add(createEvents()); 409 + @budget = add(createBudget()); 410 + @planets = add(createPlanets()); 411 + @resources = add(createResources()); 412 + @systems = add(createSystems()); 413 + @colonization = add(createColonization()); 414 + @fleets = add(createFleets()); 415 + @scouting = add(createScouting()); 416 + @development = add(createDevelopment()); 417 + @designs = add(createDesigns()); 418 + @construction = add(createConstruction()); 419 + @military = add(createMilitary()); 420 + @movement = add(createMovement()); 421 + @creeping = add(createCreeping()); 422 + @relations = add(createRelations()); 423 + @intelligence = add(createIntelligence()); 424 + @war = add(createWar()); 425 + @research = add(createResearch()); 426 + @energy = add(createEnergy()); 427 + @diplomacy = add(createDiplomacy()); 428 + @consider = add(createConsider()); 429 + @orbitals = add(createOrbitals()); 430 + @infrastructure = add(createInfrastructure()); 431 + 432 + //Make FTL component 433 + if(empire.hasTrait(getTraitID("Hyperdrive"))) 434 + @ftl = add(createHyperdrive()); 435 + else if(empire.hasTrait(getTraitID("Gate"))) 436 + @ftl = add(createGate()); 437 + else if(empire.hasTrait(getTraitID("Fling"))) 438 + @ftl = add(createFling()); 439 + else if(empire.hasTrait(getTraitID("Slipstream"))) 440 + @ftl = add(createSlipstream()); 441 + else if(empire.hasTrait(getTraitID("Jumpdrive"))) 442 + @ftl = add(createJumpdrive()); 443 + /* Not implemented yet. 444 + else if(empire.hasTrait(getTraitID("Flux"))) 445 + @ftl = add(createFlux()); 446 + */ 447 + 448 + //Make racial component 449 + if(empire.hasTrait(getTraitID("Verdant"))) 450 + @race = add(createVerdant()); 451 + else if(empire.hasTrait(getTraitID("Mechanoid"))) 452 + @race = add(createMechanoid()); 453 + else if(empire.hasTrait(getTraitID("StarChildren"))) 454 + @race = add(createStarChildren()); 455 + else if(empire.hasTrait(getTraitID("Extragalactic"))) 456 + @race = add(createExtragalactic()); 457 + else if(empire.hasTrait(getTraitID("Linked"))) 458 + @race = add(createLinked()); 459 + else if(empire.hasTrait(getTraitID("Devout"))) 460 + @race = add(createDevout()); 461 + else if(empire.hasTrait(getTraitID("Ancient"))) 462 + @race = add(createAncient()); 463 + /* Not implemented yet. 464 + else if(empire.hasTrait(getTraitID("Technicists"))) 465 + @race = add(createResearchers()); 466 + else if(empire.hasTrait(getTraitID("Progenitors"))) 467 + @race = add(createProgenitors()); 468 + else if(empire.hasTrait(getTraitID("Berserkers"))) 469 + @race = add(createBerserkers()); 470 + else if(empire.hasTrait(getTraitID("Pacifists"))) 471 + @race = add(createPacifists()); 472 + */ 473 + 474 + //Misc components 475 + if(hasInvasionMap() || addedComponents & AC_Invasion != 0) { 476 + @invasion = add(createInvasion()); 477 + addedComponents |= AC_Invasion; 478 + } 479 + 480 + //if(empire is playerEmpire) { 481 + //log(race); 482 + // log(colonization); 483 + // log(resources); 484 + // log(construction); 485 + //} 486 + //log(intelligence); 487 + //logAll(); 488 + logCritical(); 489 + 490 + profileData.length = components.length; 491 + for(uint i = 0, cnt = components.length; i < cnt; ++i) 492 + components[i].create(); 493 + } 494 + 495 + void createGeneral() { 496 + } 497 + 498 + void init(Empire& emp, EmpireSettings& settings) { 499 + @this.empire = emp; 500 + flags = settings.aiFlags; 501 + difficulty = settings.difficulty; 502 + behavior.setDifficulty(difficulty, flags); 503 + 504 + createComponents(); 505 + } 506 + 507 + int getDifficultyLevel() { 508 + return difficulty; 509 + } 510 + 511 + void load(SaveFile& file) { 512 + file >> empire; 513 + file >> cycleId; 514 + file >> majorMask; 515 + file >> difficulty; 516 + file >> flags; 517 + if(file >= SV_0153) 518 + file >> addedComponents; 519 + behavior.setDifficulty(difficulty, flags); 520 + createComponents(); 521 + createGeneral(); 522 + 523 + uint loadCnt = 0; 524 + file >> loadCnt; 525 + loadCnt = loadCnt; 526 + for(uint i = 0; i < loadCnt; ++i) { 527 + double prevFocus = 0; 528 + file >> prevFocus; 529 + components[i].setPrevFocus(prevFocus); 530 + file >> components[i]; 531 + 532 + uint check = 0; 533 + file >> check; 534 + if(check != GUARD) 535 + error("ERROR: AI Load error detected in component "+addrstr(components[i])+" of "+empire.name); 536 + } 537 + for(uint i = 0, cnt = components.length; i < cnt; ++i) 538 + components[i].postLoad(this); 539 + isLoading = true; 540 + } 541 + 542 + void save(SaveFile& file) { 543 + file << empire; 544 + file << cycleId; 545 + file << majorMask; 546 + file << difficulty; 547 + file << flags; 548 + file << addedComponents; 549 + uint saveCnt = components.length; 550 + file << saveCnt; 551 + for(uint i = 0; i < saveCnt; ++i) { 552 + file << components[i].getPrevFocus(); 553 + file << components[i]; 554 + file << GUARD; 555 + } 556 + for(uint i = 0, cnt = components.length; i < cnt; ++i) 557 + components[i].postSave(this); 558 + } 559 + 560 + void log(IAIComponent@ comp) { 561 + if(comp is null) 562 + return; 563 + comp.setLog(); 564 + comp.setLogCritical(); 565 + } 566 + 567 + void logCritical() { 568 + for(uint i = 0, cnt = components.length; i < cnt; ++i) 569 + components[i].setLogCritical(); 570 + } 571 + 572 + void logAll() { 573 + for(uint i = 0, cnt = components.length; i < cnt; ++i) { 574 + components[i].setLog(); 575 + components[i].setLogCritical(); 576 + } 577 + } 578 + 579 + IAIComponent@ add(IAIComponent& component) { 580 + component.set(this); 581 + components.insertLast(component); 582 + return component; 583 + } 584 + 585 + void init(Empire& emp) { 586 + majorMask = 0; 587 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 588 + Empire@ emp = getEmpire(i); 589 + if(emp.major) 590 + majorMask |= emp.mask; 591 + } 592 + 593 + createGeneral(); 594 + } 595 + 596 + bool hasStarted = false; 597 + void tick(Empire& emp, double time) { 598 + if(isLoading) { 599 + for(uint i = 0, cnt = components.length; i < cnt; ++i) 600 + components[i].loadFinalize(this); 601 + isLoading = false; 602 + hasStarted = true; 603 + } 604 + else if(!hasStarted) { 605 + for(uint i = 0, cnt = components.length; i < cnt; ++i) 606 + components[i].start(); 607 + hasStarted = true; 608 + } 609 + else if(emp.Victory == -1) { 610 + //Don't do anything when actually defeated 611 + return; 612 + } 613 + 614 + //Manage gametime-specific behaviors 615 + behavior.colonizeGenericExpand = gameTime >= 6.0 * 60.0; 616 + 617 + //Find cycled turns 618 + int curCycle = emp.BudgetCycleId; 619 + if(curCycle != cycleId) { 620 + for(uint i = 0, cnt = components.length; i < cnt; ++i) 621 + components[i].turn(); 622 + cycleId = curCycle; 623 + } 624 + 625 + //Generic ticks 626 + double startTime = getExactTime(); 627 + for(uint i = 0, cnt = components.length; i < cnt; ++i) { 628 + auto@ comp = components[i]; 629 + comp.tick(time); 630 + 631 + double endTime = getExactTime(); 632 + //double ms = 1000.0 * (endTime - startTime); 633 + startTime = endTime; 634 + 635 + //auto@ dat = profileData[i]; 636 + //dat.tickPeak = max(dat.tickPeak, ms); 637 + //dat.tickAvg += ms; 638 + //dat.tickCount += 1.0; 639 + } 640 + 641 + //Do focuseds tick on components 642 + uint focusCount = behavior.focusPerTick; 643 + if(ai_full_speed.value == 1.0) 644 + focusCount = max(uint(round((time / 0.25) * behavior.focusPerTick)), behavior.focusPerTick); 645 + double allocStart = startTime; 646 + 647 + for(uint n = 0; n < focusCount; ++n) { 648 + componentCycle = (componentCycle+1) % components.length; 649 + auto@ focusComp = components[componentCycle]; 650 + focusComp.focusTick(gameTime - focusComp.getPrevFocus()); 651 + focusComp.setPrevFocus(gameTime); 652 + 653 + double endTime = getExactTime(); 654 + //double ms = 1000.0 * (endTime - startTime); 655 + startTime = endTime; 656 + if(endTime - allocStart > 4000.0) 657 + break; 658 + 659 + //auto@ dat = profileData[componentCycle]; 660 + //dat.focusPeak = max(dat.focusPeak, ms); 661 + //dat.focusAvg += ms; 662 + //dat.focusCount += 1.0; 663 + } 664 + } 665 + 666 + void dumpProfile() { 667 + for(uint i = 0, cnt = components.length; i < cnt; ++i) { 668 + auto@ c = profileData[i]; 669 + print(pad(addrstr(components[i]), 40)+" tick peak "+toString(c.tickPeak,2)+" tick avg "+toString(c.tickAvg/c.tickCount, 2) 670 + +" focus peak "+toString(c.focusPeak,2)+" focus avg "+toString(c.focusAvg/c.focusCount, 2)); 671 + } 672 + } 673 + 674 + void resetProfile() { 675 + for(uint i = 0, cnt = profileData.length; i < cnt; ++i) { 676 + auto@ c = profileData[i]; 677 + c.tickPeak = 0.0; 678 + c.tickAvg = 0.0; 679 + c.tickCount = 0.0; 680 + c.focusPeak = 0.0; 681 + c.focusAvg = 0.0; 682 + c.focusCount = 0.0; 683 + } 684 + } 685 + 686 + uint get_mask() { 687 + return empire.mask; 688 + } 689 + 690 + uint get_teamMask() { 691 + //TODO 692 + return empire.mask; 693 + } 694 + 695 + uint get_visionMask() { 696 + return majorMask & empire.visionMask; 697 + } 698 + 699 + uint get_allyMask() { 700 + return empire.mutualDefenseMask | empire.ForcedPeaceMask.value; 701 + } 702 + 703 + uint get_enemyMask() { 704 + return empire.hostileMask & majorMask; 705 + } 706 + 707 + uint get_neutralMask() { 708 + return majorMask & ~allyMask & ~mask & ~enemyMask; 709 + } 710 + 711 + uint get_otherMask() { 712 + return majorMask & ~mask; 713 + } 714 + 715 + string pad(const string& input, uint width) { 716 + string str = input; 717 + while(str.length < width) 718 + str += " "; 719 + return str; 720 + } 721 + 722 + void print(const string& info, Object@ related = null, double value = INFINITY, bool flag = false, Empire@ emp = null) { 723 + string str = info; 724 + if(related !is null) 725 + str = pad(related.name, 16)+" | "+str; 726 + str = pad("["+empire.index+": "+empire.name+" AI] ", 20)+str; 727 + str = formatGameTime(gameTime) + " " + str; 728 + if(value != INFINITY) 729 + str += " | Value = "+standardize(value, true); 730 + if(flag) 731 + str += " | FLAGGED On"; 732 + if(emp !is null) 733 + str += " | Target = "+emp.name; 734 + ::print(str); 735 + } 736 + 737 + void debugAI() {} 738 + void commandAI(string cmd) { 739 + if (cmd == "forbid all") { 740 + behavior.forbidDiplomacy = true; 741 + behavior.forbidColonization = true; 742 + behavior.forbidCreeping = true; 743 + behavior.forbidResearch = true; 744 + behavior.forbidDefense = true; 745 + behavior.forbidAttack = true; 746 + behavior.forbidConstruction = true; 747 + behavior.forbidScouting = true; 748 + behavior.forbidAnomalyChoice = true; 749 + behavior.forbidArtifact = true; 750 + behavior.forbidScuttle = true; 751 + } else if (cmd == "allow all") { 752 + behavior.forbidDiplomacy = false; 753 + behavior.forbidColonization = false; 754 + behavior.forbidCreeping = false; 755 + behavior.forbidResearch = false; 756 + behavior.forbidDefense = false; 757 + behavior.forbidAttack = false; 758 + behavior.forbidConstruction = false; 759 + behavior.forbidScouting = false; 760 + behavior.forbidAnomalyChoice = false; 761 + behavior.forbidArtifact = false; 762 + behavior.forbidScuttle = false; 763 + } else if (cmd == "forbid Diplomacy") { behavior.forbidDiplomacy = true; 764 + } else if (cmd == "allow Diplomacy") { behavior.forbidDiplomacy = false; 765 + } else if (cmd == "forbid Colonization") { behavior.forbidColonization = true; 766 + } else if (cmd == "allow Colonization") { behavior.forbidColonization = false; 767 + } else if (cmd == "forbid Creeping") { behavior.forbidCreeping = true; 768 + } else if (cmd == "allow Creeping") { behavior.forbidCreeping = false; 769 + } else if (cmd == "forbid Research") { behavior.forbidResearch = true; 770 + } else if (cmd == "allow Research") { behavior.forbidResearch = false; 771 + } else if (cmd == "forbid Defense") { behavior.forbidDefense = true; 772 + } else if (cmd == "allow Defense") { behavior.forbidDefense = false; 773 + } else if (cmd == "forbid Attack") { behavior.forbidAttack = true; 774 + } else if (cmd == "allow Attack") { behavior.forbidAttack = false; 775 + } else if (cmd == "forbid Construction") { behavior.forbidConstruction = true; 776 + } else if (cmd == "allow Construction") { behavior.forbidConstruction = false; 777 + } else if (cmd == "forbid Scouting") { behavior.forbidScouting = true; 778 + } else if (cmd == "allow Scouting") { behavior.forbidScouting = false; 779 + } else if (cmd == "forbid AnomalyChoice") { behavior.forbidAnomalyChoice = true; 780 + } else if (cmd == "allow AnomalyChoice") { behavior.forbidAnomalyChoice = false; 781 + } else if (cmd == "forbid Artifact") { behavior.forbidArtifact = true; 782 + } else if (cmd == "allow Artifact") { behavior.forbidArtifact = false; 783 + } else if (cmd == "forbid Scuttle") { behavior.forbidScuttle = true; 784 + } else if (cmd == "allow Scuttle") { behavior.forbidScuttle = false; 785 + } else { 786 + print("WeaselAI: got unhandled AI command: " + cmd); 787 + } 788 + } 789 + void aiPing(Empire@ fromEmpire, vec3d position, uint type) {} 790 + void pause(Empire& emp) {} 791 + void resume(Empire& emp) {} 792 + vec3d get_aiFocus() { return vec3d(); } 793 + string getOpinionOf(Empire& emp, Empire@ other) { return ""; } 794 + int getStandingTo(Empire& emp, Empire@ other) { return 0; } 795 +}; 796 + 797 +AIController@ createWeaselAI() { 798 + return AI(); 799 +}
Added scripts/server/empire_ai/weasel/debug.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.Colonization; 3 +import empire_ai.weasel.Construction; 4 +import empire_ai.weasel.Budget; 5 +import empire_ai.weasel.Designs; 6 +import empire_ai.weasel.Development; 7 +import empire_ai.weasel.Fleets; 8 +import empire_ai.weasel.Military; 9 +import empire_ai.weasel.Planets; 10 +import empire_ai.weasel.Resources; 11 +import empire_ai.weasel.Scouting; 12 +import empire_ai.weasel.Systems; 13 +import empire_ai.weasel.Creeping; 14 +import empire_ai.weasel.Movement; 15 +import empire_ai.weasel.Relations; 16 +import empire_ai.weasel.Intelligence; 17 +import empire_ai.weasel.War; 18 +import empire_ai.weasel.Research; 19 +import empire_ai.weasel.Energy; 20 +import empire_ai.weasel.Diplomacy; 21 +import empire_ai.weasel.ftl.Gate; 22 +import empire_ai.weasel.ftl.Hyperdrive; 23 +import empire_ai.weasel.ftl.Fling; 24 +import empire_ai.weasel.ftl.Slipstream; 25 +import empire_ai.weasel.ftl.Jumpdrive; 26 +import empire_ai.weasel.race.Verdant; 27 +import empire_ai.weasel.race.Mechanoid; 28 +import empire_ai.weasel.race.StarChildren; 29 +import empire_ai.weasel.race.Extragalactic; 30 +import empire_ai.weasel.race.Linked; 31 +import empire_ai.weasel.race.Devout; 32 +import empire_ai.weasel.race.Ancient; 33 +import empire_ai.weasel.misc.Invasion; 34 +import empire_ai.EmpireAI; 35 + 36 +AI@ ai(uint index) { 37 + Empire@ emp = getEmpire(index); 38 + return cast<AI>(cast<EmpireAI>(emp.EmpireAI).ctrl); 39 +} 40 + 41 +Colonization@ colonization(uint index) { 42 + return cast<Colonization>(ai(index).colonization); 43 +} 44 + 45 +Construction@ construction(uint index) { 46 + return cast<Construction>(ai(index).construction); 47 +} 48 + 49 +Budget@ budget(uint index) { 50 + return cast<Budget>(ai(index).budget); 51 +} 52 + 53 +Designs@ designs(uint index) { 54 + return cast<Designs>(ai(index).designs); 55 +} 56 + 57 +Development@ development(uint index) { 58 + return cast<Development>(ai(index).development); 59 +} 60 + 61 +Fleets@ fleets(uint index) { 62 + return cast<Fleets>(ai(index).fleets); 63 +} 64 + 65 +Military@ military(uint index) { 66 + return cast<Military>(ai(index).military); 67 +} 68 + 69 +Planets@ planets(uint index) { 70 + return cast<Planets>(ai(index).planets); 71 +} 72 + 73 +Resources@ resources(uint index) { 74 + return cast<Resources>(ai(index).resources); 75 +} 76 + 77 +Scouting@ scouting(uint index) { 78 + return cast<Scouting>(ai(index).scouting); 79 +} 80 + 81 +Systems@ systems(uint index) { 82 + return cast<Systems>(ai(index).systems); 83 +} 84 + 85 +Movement@ movement(uint index) { 86 + return cast<Movement>(ai(index).movement); 87 +} 88 + 89 +Creeping@ creeping(uint index) { 90 + return cast<Creeping>(ai(index).creeping); 91 +} 92 + 93 +Relations@ relations(uint index) { 94 + return cast<Relations>(ai(index).relations); 95 +} 96 + 97 +Intelligence@ intelligence(uint index) { 98 + return cast<Intelligence>(ai(index).intelligence); 99 +} 100 + 101 +War@ war(uint index) { 102 + return cast<War>(ai(index).war); 103 +} 104 + 105 +Research@ research(uint index) { 106 + return cast<Research>(ai(index).research); 107 +} 108 + 109 +Energy@ energy(uint index) { 110 + return cast<Energy>(ai(index).energy); 111 +} 112 + 113 +Diplomacy@ diplomacy(uint index) { 114 + return cast<Diplomacy>(ai(index).diplomacy); 115 +} 116 + 117 +Gate@ gate(uint index) { 118 + return cast<Gate>(ai(index).ftl); 119 +} 120 + 121 +Hyperdrive@ hyperdrive(uint index) { 122 + return cast<Hyperdrive>(ai(index).ftl); 123 +} 124 + 125 +Fling@ fling(uint index) { 126 + return cast<Fling>(ai(index).ftl); 127 +} 128 + 129 +Slipstream@ slipstream(uint index) { 130 + return cast<Slipstream>(ai(index).ftl); 131 +} 132 + 133 +Jumpdrive@ jumpdrive(uint index) { 134 + return cast<Jumpdrive>(ai(index).ftl); 135 +} 136 + 137 +Mechanoid@ mechanoid(uint index) { 138 + return cast<Mechanoid>(ai(index).race); 139 +} 140 + 141 +Verdant@ verdant(uint index) { 142 + return cast<Verdant>(ai(index).race); 143 +} 144 + 145 +StarChildren@ starchildren(uint index) { 146 + return cast<StarChildren>(ai(index).race); 147 +} 148 + 149 +Extragalactic@ extragalactic(uint index) { 150 + return cast<Extragalactic>(ai(index).race); 151 +} 152 + 153 +Linked@ linked(uint index) { 154 + return cast<Linked>(ai(index).race); 155 +} 156 + 157 +Devout@ devout(uint index) { 158 + return cast<Devout>(ai(index).race); 159 +} 160 + 161 +Ancient@ ancient(uint index) { 162 + return cast<Ancient>(ai(index).race); 163 +} 164 + 165 +Invasion@ invasion(uint index) { 166 + return cast<Invasion>(ai(index).invasion); 167 +}
Added scripts/server/empire_ai/weasel/ftl/Fling.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.Movement; 3 +import empire_ai.weasel.Military; 4 +import empire_ai.weasel.Construction; 5 +import empire_ai.weasel.Designs; 6 +import empire_ai.weasel.Development; 7 +import empire_ai.weasel.Systems; 8 +import empire_ai.weasel.Budget; 9 +import empire_ai.weasel.Fleets; 10 + 11 +import ftl; 12 +from orbitals import getOrbitalModuleID; 13 + 14 +const double FLING_MIN_DISTANCE_STAGE = 10000; 15 +const double FLING_MIN_DISTANCE_DEVELOP = 20000; 16 +const double FLING_MIN_TIMER = 3.0 * 60.0; 17 + 18 +int flingModule = -1; 19 + 20 +void init() { 21 + flingModule = getOrbitalModuleID("FlingCore"); 22 +} 23 + 24 +class FlingRegion : Savable { 25 + Region@ region; 26 + Object@ obj; 27 + bool installed = false; 28 + vec3d destination; 29 + 30 + void save(SaveFile& file) { 31 + file << region; 32 + file << obj; 33 + file << installed; 34 + file << destination; 35 + } 36 + 37 + void load(SaveFile& file) { 38 + file >> region; 39 + file >> obj; 40 + file >> installed; 41 + file >> destination; 42 + } 43 +}; 44 + 45 +class Fling : FTL { 46 + Military@ military; 47 + Designs@ designs; 48 + Construction@ construction; 49 + Development@ development; 50 + Systems@ systems; 51 + Budget@ budget; 52 + Fleets@ fleets; 53 + 54 + array<FlingRegion@> tracked; 55 + array<Object@> unused; 56 + 57 + BuildOrbital@ buildFling; 58 + double nextBuildTry = 15.0 * 60.0; 59 + bool wantToBuild = false; 60 + 61 + void create() override { 62 + @military = cast<Military>(ai.military); 63 + @designs = cast<Designs>(ai.designs); 64 + @construction = cast<Construction>(ai.construction); 65 + @development = cast<Development>(ai.development); 66 + @systems = cast<Systems>(ai.systems); 67 + @budget = cast<Budget>(ai.budget); 68 + @fleets = cast<Fleets>(ai.fleets); 69 + } 70 + 71 + void save(SaveFile& file) override { 72 + construction.saveConstruction(file, buildFling); 73 + file << nextBuildTry; 74 + file << wantToBuild; 75 + 76 + uint cnt = tracked.length; 77 + file << cnt; 78 + for(uint i = 0; i < cnt; ++i) 79 + file << tracked[i]; 80 + 81 + cnt = unused.length; 82 + file << cnt; 83 + for(uint i = 0; i < cnt; ++i) 84 + file << unused[i]; 85 + } 86 + 87 + void load(SaveFile& file) override { 88 + @buildFling = cast<BuildOrbital>(construction.loadConstruction(file)); 89 + file >> nextBuildTry; 90 + file >> wantToBuild; 91 + 92 + uint cnt = 0; 93 + file >> cnt; 94 + for(uint i = 0; i < cnt; ++i) { 95 + FlingRegion fr; 96 + file >> fr; 97 + tracked.insertLast(fr); 98 + } 99 + 100 + file >> cnt; 101 + for(uint i = 0; i < cnt; ++i) { 102 + Object@ obj; 103 + file >> obj; 104 + if(obj !is null) 105 + unused.insertLast(obj); 106 + } 107 + } 108 + 109 + uint order(MoveOrder& ord) override { 110 + if(!canFling(ord.obj)) 111 + return F_Pass; 112 + 113 + //Find the position to fling to 114 + vec3d toPosition; 115 + if(!targetPosition(ord, toPosition)) 116 + return F_Pass; 117 + 118 + //Don't fling if we're saving our FTL for a new beacon 119 + double avail = usableFTL(ai, ord); 120 + if((buildFling !is null && !buildFling.started) || wantToBuild) 121 + avail = min(avail, ai.empire.FTLStored - 250.0); 122 + 123 + //Make sure we have the ftl to fling 124 + if(flingCost(ord.obj, toPosition) > avail) 125 + return F_Pass; 126 + 127 + //Make sure we're in range of a beacon 128 + Object@ beacon = getClosest(ord.obj.position); 129 + if(beacon is null || beacon.position.distanceTo(ord.obj.position) > FLING_BEACON_RANGE) 130 + return F_Pass; 131 + 132 + ord.obj.addFlingOrder(beacon, toPosition); 133 + return F_Continue; 134 + } 135 + 136 + FlingRegion@ get(Region@ reg) { 137 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 138 + if(tracked[i].region is reg) 139 + return tracked[i]; 140 + } 141 + return null; 142 + } 143 + 144 + void remove(FlingRegion@ gt) { 145 + if(gt.obj !is null && gt.obj.valid && gt.obj.owner is ai.empire) 146 + unused.insertLast(gt.obj); 147 + tracked.remove(gt); 148 + } 149 + 150 + Object@ getClosest(const vec3d& position) { 151 + Object@ closest; 152 + double minDist = INFINITY; 153 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 154 + Object@ obj = tracked[i].obj; 155 + if(obj is null) 156 + continue; 157 + double d = obj.position.distanceTo(position); 158 + if(d < minDist) { 159 + minDist = d; 160 + @closest = obj; 161 + } 162 + } 163 + for(uint i = 0, cnt = unused.length; i < cnt; ++i) { 164 + Object@ obj = unused[i]; 165 + if(obj is null) 166 + continue; 167 + double d = obj.position.distanceTo(position); 168 + if(d < minDist) { 169 + minDist = d; 170 + @closest = obj; 171 + } 172 + } 173 + return closest; 174 + } 175 + 176 + FlingRegion@ getClosestRegion(const vec3d& position) { 177 + FlingRegion@ closest; 178 + double minDist = INFINITY; 179 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 180 + double d = tracked[i].region.position.distanceTo(position); 181 + if(d < minDist) { 182 + minDist = d; 183 + @closest = tracked[i]; 184 + } 185 + } 186 + return closest; 187 + } 188 + 189 + void assignTo(FlingRegion@ track, Object@ closest) { 190 + unused.remove(closest); 191 + @track.obj = closest; 192 + } 193 + 194 + bool trackingBeacon(Object@ obj) { 195 + for(uint i = 0, cnt = unused.length; i < cnt; ++i) { 196 + if(unused[i] is obj) 197 + return true; 198 + } 199 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 200 + if(tracked[i].obj is obj) 201 + return true; 202 + } 203 + return false; 204 + } 205 + 206 + bool shouldHaveBeacon(Region@ reg, bool always = false) { 207 + if(military.getBase(reg) !is null) 208 + return true; 209 + if(development.isDevelopingIn(reg)) 210 + return true; 211 + return false; 212 + } 213 + 214 + void focusTick(double time) override { 215 + //Manage unused beacons list 216 + for(uint i = 0, cnt = unused.length; i < cnt; ++i) { 217 + Object@ obj = unused[i]; 218 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 219 + unused.removeAt(i); 220 + --i; --cnt; 221 + } 222 + } 223 + 224 + if(ai.behavior.forbidConstruction) return; 225 + 226 + //Detect new beacons 227 + auto@ data = ai.empire.getFlingBeacons(); 228 + Object@ obj; 229 + while(receive(data, obj)) { 230 + if(obj is null) 231 + continue; 232 + if(!trackingBeacon(obj)) 233 + unused.insertLast(obj); 234 + } 235 + 236 + //Update existing beacons for staging bases 237 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 238 + auto@ reg = tracked[i]; 239 + bool checkAlways = false; 240 + if(reg.obj !is null) { 241 + if(!reg.obj.valid || reg.obj.owner !is ai.empire || reg.obj.region !is reg.region) { 242 + @reg.obj = null; 243 + checkAlways = true; 244 + } 245 + } 246 + if(!shouldHaveBeacon(reg.region, checkAlways)) { 247 + remove(tracked[i]); 248 + --i; --cnt; 249 + } 250 + } 251 + 252 + //Detect new staging bases to build beacons at 253 + for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) { 254 + auto@ base = military.stagingBases[i]; 255 + if(base.occupiedTime < FLING_MIN_TIMER) 256 + continue; 257 + 258 + if(get(base.region) is null) { 259 + FlingRegion@ closest = getClosestRegion(base.region.position); 260 + if(closest !is null && closest.region.position.distanceTo(base.region.position) < FLING_MIN_DISTANCE_STAGE) 261 + continue; 262 + 263 + FlingRegion gt; 264 + @gt.region = base.region; 265 + tracked.insertLast(gt); 266 + break; 267 + } 268 + } 269 + 270 + //Detect new important planets to build beacons at 271 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 272 + auto@ focus = development.focuses[i]; 273 + Region@ reg = focus.obj.region; 274 + if(reg is null) 275 + continue; 276 + 277 + if(get(reg) is null) { 278 + FlingRegion@ closest = getClosestRegion(reg.position); 279 + if(closest !is null && closest.region.position.distanceTo(reg.position) < FLING_MIN_DISTANCE_DEVELOP) 280 + continue; 281 + 282 + FlingRegion gt; 283 + @gt.region = reg; 284 + tracked.insertLast(gt); 285 + break; 286 + } 287 + } 288 + 289 + //Destroy beacons if we're having ftl trouble 290 + if(ai.empire.FTLShortage && !ai.behavior.forbidScuttle) { 291 + Orbital@ leastImportant; 292 + double leastWeight = INFINITY; 293 + 294 + for(uint i = 0, cnt = unused.length; i < cnt; ++i) { 295 + Orbital@ obj = cast<Orbital>(unused[i]); 296 + if(obj is null || !obj.valid) 297 + continue; 298 + 299 + @leastImportant = obj; 300 + leastWeight = 0.0; 301 + break; 302 + } 303 + 304 + if(leastImportant !is null) { 305 + if(log) 306 + ai.print("Scuttle unused beacon for ftl", leastImportant.region); 307 + leastImportant.scuttle(); 308 + } 309 + else { 310 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 311 + Orbital@ obj = cast<Orbital>(tracked[i].obj); 312 + if(obj is null || !obj.valid) 313 + continue; 314 + 315 + double weight = 1.0; 316 + auto@ base = military.getBase(tracked[i].region); 317 + if(base is null) { 318 + weight *= 5.0; 319 + } 320 + else if(base.idleTime >= 1) { 321 + weight *= 1.0 + (base.idleTime / 60.0); 322 + } 323 + else { 324 + weight /= 2.0; 325 + } 326 + 327 + if(weight < leastWeight) { 328 + @leastImportant = obj; 329 + leastWeight = weight; 330 + } 331 + } 332 + 333 + if(leastImportant !is null) { 334 + if(log) 335 + ai.print("Scuttle unimportant beacon for ftl", leastImportant.region); 336 + leastImportant.scuttle(); 337 + } 338 + } 339 + } 340 + 341 + //See if we should build a new gate 342 + if(buildFling !is null) { 343 + if(buildFling.completed) { 344 + @buildFling = null; 345 + nextBuildTry = gameTime + 60.0; 346 + } 347 + } 348 + wantToBuild = false; 349 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 350 + auto@ gt = tracked[i]; 351 + if(gt.obj is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) { 352 + Object@ found; 353 + for(uint n = 0, ncnt = unused.length; n < ncnt; ++n) { 354 + Object@ obj = unused[n]; 355 + if(obj.region is gt.region) { 356 + @found = obj; 357 + break; 358 + } 359 + } 360 + 361 + if(found !is null) { 362 + if(log) 363 + ai.print("Assign beacon to => "+gt.region.name, found.region); 364 + assignTo(gt, found); 365 + } else if(buildFling is null && gameTime > nextBuildTry && !ai.empire.isFTLShortage(0.15)) { 366 + if(ai.empire.FTLStored >= 250) { 367 + if(log) 368 + ai.print("Build beacon for this system", gt.region); 369 + 370 + @buildFling = construction.buildOrbital(getOrbitalModule(flingModule), military.getStationPosition(gt.region)); 371 + } 372 + else { 373 + wantToBuild = true; 374 + } 375 + } 376 + } 377 + } 378 + 379 + //Scuttle anything unused if we don't need beacons in those regions 380 + for(uint i = 0, cnt = unused.length; i < cnt; ++i) { 381 + if(get(unused[i].region) is null && unused[i].isOrbital) { 382 + cast<Orbital>(unused[i]).scuttle(); 383 + unused.removeAt(i); 384 + --i; --cnt; 385 + } 386 + } 387 + 388 + //Try to get enough ftl storage that we can fling our largest fleet and have some remaining 389 + double highestCost = 0.0; 390 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 391 + auto@ flAI = fleets.fleets[i]; 392 + if(flAI.fleetClass != FC_Combat) 393 + continue; 394 + highestCost = max(highestCost, double(flingCost(flAI.obj, vec3d()))); 395 + } 396 + development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal); 397 + } 398 +}; 399 + 400 +AIComponent@ createFling() { 401 + return Fling(); 402 +}
Added scripts/server/empire_ai/weasel/ftl/Gate.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.Movement; 3 +import empire_ai.weasel.Military; 4 +import empire_ai.weasel.Construction; 5 +import empire_ai.weasel.Designs; 6 +import empire_ai.weasel.Development; 7 +import empire_ai.weasel.Systems; 8 +import empire_ai.weasel.Budget; 9 + 10 +from statuses import getStatusID; 11 +from abilities import getAbilityID; 12 + 13 +const double GATE_MIN_DISTANCE_STAGE = 10000; 14 +const double GATE_MIN_DISTANCE_DEVELOP = 20000; 15 +const double GATE_MIN_DISTANCE_BORDER = 30000; 16 +const double GATE_MIN_TIMER = 3.0 * 60.0; 17 +const int GATE_BUILD_MOVE_HOPS = 5; 18 + 19 +int packAbility = -1; 20 +int unpackAbility = -1; 21 + 22 +int packedStatus = -1; 23 +int unpackedStatus = -1; 24 + 25 +void init() { 26 + packAbility = getAbilityID("GatePack"); 27 + unpackAbility = getAbilityID("GateUnpack"); 28 + 29 + packedStatus = getStatusID("GatePacked"); 30 + unpackedStatus = getStatusID("GateUnpacked"); 31 +} 32 + 33 +class GateRegion : Savable { 34 + Region@ region; 35 + Object@ gate; 36 + bool installed = false; 37 + vec3d destination; 38 + 39 + void save(SaveFile& file) { 40 + file << region; 41 + file << gate; 42 + file << installed; 43 + file << destination; 44 + } 45 + 46 + void load(SaveFile& file) { 47 + file >> region; 48 + file >> gate; 49 + file >> installed; 50 + file >> destination; 51 + } 52 +}; 53 + 54 +class Gate : FTL { 55 + Military@ military; 56 + Designs@ designs; 57 + Construction@ construction; 58 + Development@ development; 59 + Systems@ systems; 60 + Budget@ budget; 61 + 62 + DesignTarget@ gateDesign; 63 + 64 + array<GateRegion@> tracked; 65 + array<Object@> unassigned; 66 + 67 + BuildStation@ buildGate; 68 + double nextBuildTry = 15.0 * 60.0; 69 + 70 + void create() override { 71 + @military = cast<Military>(ai.military); 72 + @designs = cast<Designs>(ai.designs); 73 + @construction = cast<Construction>(ai.construction); 74 + @development = cast<Development>(ai.development); 75 + @systems = cast<Systems>(ai.systems); 76 + @budget = cast<Budget>(ai.budget); 77 + } 78 + 79 + void save(SaveFile& file) override { 80 + designs.saveDesign(file, gateDesign); 81 + construction.saveConstruction(file, buildGate); 82 + file << nextBuildTry; 83 + 84 + uint cnt = tracked.length; 85 + file << cnt; 86 + for(uint i = 0; i < cnt; ++i) 87 + file << tracked[i]; 88 + 89 + cnt = unassigned.length; 90 + file << cnt; 91 + for(uint i = 0; i < cnt; ++i) 92 + file << unassigned[i]; 93 + } 94 + 95 + void load(SaveFile& file) override { 96 + @gateDesign = designs.loadDesign(file); 97 + @buildGate = cast<BuildStation>(construction.loadConstruction(file)); 98 + file >> nextBuildTry; 99 + 100 + uint cnt = 0; 101 + file >> cnt; 102 + for(uint i = 0; i < cnt; ++i) { 103 + GateRegion gt; 104 + file >> gt; 105 + tracked.insertLast(gt); 106 + } 107 + 108 + file >> cnt; 109 + for(uint i = 0; i < cnt; ++i) { 110 + Object@ obj; 111 + file >> obj; 112 + if(obj !is null) 113 + unassigned.insertLast(obj); 114 + } 115 + } 116 + 117 + GateRegion@ get(Region@ reg) { 118 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 119 + if(tracked[i].region is reg) 120 + return tracked[i]; 121 + } 122 + return null; 123 + } 124 + 125 + void remove(GateRegion@ gt) { 126 + if(gt.gate !is null && gt.gate.valid && gt.gate.owner is ai.empire) 127 + unassigned.insertLast(gt.gate); 128 + tracked.remove(gt); 129 + } 130 + 131 + Object@ getClosestGate(const vec3d& position) { 132 + Object@ closest; 133 + double minDist = INFINITY; 134 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 135 + Object@ gate = tracked[i].gate; 136 + if(gate is null) 137 + continue; 138 + if(!tracked[i].installed) 139 + continue; 140 + double d = gate.position.distanceTo(position); 141 + if(d < minDist) { 142 + minDist = d; 143 + @closest = gate; 144 + } 145 + } 146 + return closest; 147 + } 148 + 149 + GateRegion@ getClosestGateRegion(const vec3d& position) { 150 + GateRegion@ closest; 151 + double minDist = INFINITY; 152 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 153 + double d = tracked[i].region.position.distanceTo(position); 154 + if(d < minDist) { 155 + minDist = d; 156 + @closest = tracked[i]; 157 + } 158 + } 159 + return closest; 160 + } 161 + 162 + void assignTo(GateRegion@ gt, Object@ closest) { 163 + unassigned.remove(closest); 164 + @gt.gate = closest; 165 + gt.installed = false; 166 + 167 + if(closest.region is gt.region) { 168 + if(closest.hasStatusEffect(unpackedStatus)) { 169 + gt.installed = true; 170 + } 171 + } 172 + 173 + if(!gt.installed) { 174 + gt.destination = military.getStationPosition(gt.region); 175 + closest.activateAbilityTypeFor(ai.empire, packAbility); 176 + closest.addMoveOrder(gt.destination); 177 + } 178 + } 179 + 180 + bool trackingGate(Object@ obj) { 181 + for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) { 182 + if(unassigned[i] is obj) 183 + return true; 184 + } 185 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 186 + if(tracked[i].gate is obj) 187 + return true; 188 + } 189 + return false; 190 + } 191 + 192 + bool shouldHaveGate(Region@ reg, bool always = false) { 193 + if(military.getBase(reg) !is null) 194 + return true; 195 + if(development.isDevelopingIn(reg)) 196 + return true; 197 + if(!always) { 198 + auto@ sys = systems.getAI(reg); 199 + if(sys !is null) { 200 + if(sys.border && sys.bordersEmpires) 201 + return true; 202 + } 203 + } 204 + return false; 205 + } 206 + 207 + void turn() override { 208 + if(gateDesign !is null && gateDesign.active !is null) { 209 + int newSize = round(double(budget.spendable(BT_Military)) * 0.5 * ai.behavior.shipSizePerMoney / 64.0) * 64; 210 + if(newSize < 128) 211 + newSize = 128; 212 + if(newSize != gateDesign.targetSize) { 213 + @gateDesign = designs.design(DP_Gate, newSize); 214 + gateDesign.customName = "Gate"; 215 + } 216 + } 217 + } 218 + 219 + void focusTick(double time) override { 220 + if(ai.behavior.forbidConstruction) return; 221 + 222 + //Design a gate 223 + if(gateDesign is null) { 224 + @gateDesign = designs.design(DP_Gate, 128); 225 + gateDesign.customName = "Gate"; 226 + } 227 + 228 + //Manage unassigned gates list 229 + for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) { 230 + Object@ obj = unassigned[i]; 231 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 232 + unassigned.removeAt(i); 233 + --i; --cnt; 234 + } 235 + } 236 + 237 + //Detect new gates 238 + auto@ data = ai.empire.getStargates(); 239 + Object@ obj; 240 + while(receive(data, obj)) { 241 + if(obj is null) 242 + continue; 243 + if(!trackingGate(obj)) 244 + unassigned.insertLast(obj); 245 + } 246 + 247 + //Update existing gates for staging bases 248 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 249 + auto@ gt = tracked[i]; 250 + bool checkAlways = false; 251 + if(gt.gate !is null) { 252 + if(!gt.gate.valid || gt.gate.owner !is ai.empire || (gt.installed && gt.gate.region !is gt.region)) { 253 + @gt.gate = null; 254 + gt.installed = false; 255 + checkAlways = true; 256 + } 257 + else if(!gt.installed && !gt.gate.hasOrders) { 258 + if(gt.destination.distanceTo(gt.gate.position) < 10.0) { 259 + gt.gate.activateAbilityTypeFor(ai.empire, unpackAbility, gt.destination); 260 + gt.installed = true; 261 + } 262 + else { 263 + gt.gate.activateAbilityTypeFor(ai.empire, packAbility); 264 + gt.gate.addMoveOrder(gt.destination); 265 + } 266 + } 267 + } 268 + if(!shouldHaveGate(gt.region, checkAlways)) { 269 + remove(tracked[i]); 270 + --i; --cnt; 271 + } 272 + } 273 + 274 + //Detect new staging bases to build gates at 275 + for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) { 276 + auto@ base = military.stagingBases[i]; 277 + if(base.occupiedTime < GATE_MIN_TIMER) 278 + continue; 279 + 280 + if(get(base.region) is null) { 281 + GateRegion@ closest = getClosestGateRegion(base.region.position); 282 + if(closest !is null && closest.region.position.distanceTo(base.region.position) < GATE_MIN_DISTANCE_STAGE) 283 + continue; 284 + 285 + GateRegion gt; 286 + @gt.region = base.region; 287 + tracked.insertLast(gt); 288 + break; 289 + } 290 + } 291 + 292 + //Detect new important planets to build gates at 293 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 294 + auto@ focus = development.focuses[i]; 295 + Region@ reg = focus.obj.region; 296 + if(reg is null) 297 + continue; 298 + 299 + if(get(reg) is null) { 300 + GateRegion@ closest = getClosestGateRegion(reg.position); 301 + if(closest !is null && closest.region.position.distanceTo(reg.position) < GATE_MIN_DISTANCE_DEVELOP) 302 + continue; 303 + 304 + GateRegion gt; 305 + @gt.region = reg; 306 + tracked.insertLast(gt); 307 + break; 308 + } 309 + } 310 + 311 + //Detect new border systems to build gates at 312 + uint offset = randomi(0, systems.border.length-1); 313 + for(uint i = 0, cnt = systems.border.length; i < cnt; ++i) { 314 + auto@ sys = systems.border[(i+offset)%cnt]; 315 + Region@ reg = sys.obj; 316 + if(reg is null) 317 + continue; 318 + if(!sys.bordersEmpires) 319 + continue; 320 + 321 + if(get(reg) is null) { 322 + GateRegion@ closest = getClosestGateRegion(reg.position); 323 + if(closest !is null && closest.region.position.distanceTo(reg.position) < GATE_MIN_DISTANCE_DEVELOP) 324 + continue; 325 + 326 + GateRegion gt; 327 + @gt.region = reg; 328 + tracked.insertLast(gt); 329 + break; 330 + } 331 + } 332 + 333 + //Destroy gates if we're having ftl trouble 334 + if(ai.empire.FTLShortage && !ai.behavior.forbidScuttle) { 335 + Ship@ leastImportant; 336 + double leastWeight = INFINITY; 337 + 338 + for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) { 339 + Ship@ ship = cast<Ship>(unassigned[i]); 340 + if(ship is null || !ship.valid) 341 + continue; 342 + 343 + double weight = ship.blueprint.design.size; 344 + weight *= 10.0; 345 + 346 + if(weight < leastWeight) { 347 + @leastImportant = ship; 348 + leastWeight = weight; 349 + } 350 + } 351 + 352 + if(leastImportant !is null) { 353 + if(log) 354 + ai.print("Scuttle unassigned gate for ftl", leastImportant.region); 355 + leastImportant.scuttle(); 356 + } 357 + else { 358 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 359 + Ship@ ship = cast<Ship>(tracked[i].gate); 360 + if(ship is null || !ship.valid) 361 + continue; 362 + 363 + double weight = ship.blueprint.design.size; 364 + auto@ base = military.getBase(tracked[i].region); 365 + if(base is null) { 366 + weight *= 5.0; 367 + } 368 + else if(base.idleTime >= 1) { 369 + weight *= 1.0 + (base.idleTime / 60.0); 370 + } 371 + else { 372 + weight /= 2.0; 373 + } 374 + 375 + if(weight < leastWeight) { 376 + @leastImportant = ship; 377 + leastWeight = weight; 378 + } 379 + } 380 + 381 + if(leastImportant !is null) { 382 + if(log) 383 + ai.print("Scuttle unimportant gate for ftl", leastImportant.region); 384 + leastImportant.scuttle(); 385 + } 386 + } 387 + } 388 + 389 + //See if we should build a new gate 390 + if(buildGate !is null) { 391 + if(buildGate.completed) { 392 + @buildGate = null; 393 + nextBuildTry = gameTime + 60.0; 394 + } 395 + } 396 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 397 + auto@ gt = tracked[i]; 398 + if(gt.gate is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) { 399 + Object@ closest; 400 + double closestDist = INFINITY; 401 + for(uint n = 0, ncnt = unassigned.length; n < ncnt; ++n) { 402 + Object@ obj = unassigned[n]; 403 + if(obj.region is gt.region) { 404 + @closest = obj; 405 + break; 406 + } 407 + if(!obj.hasMover) 408 + continue; 409 + if(buildGate is null && gameTime > nextBuildTry) { 410 + double d = obj.position.distanceTo(gt.region.position); 411 + if(d < closestDist) { 412 + closestDist = d; 413 + @closest = obj; 414 + } 415 + } 416 + } 417 + 418 + if(closest !is null) { 419 + if(log) 420 + ai.print("Assign gate to => "+gt.region.name, closest.region); 421 + assignTo(gt, closest); 422 + } else if(buildGate is null && gameTime > nextBuildTry && !ai.empire.isFTLShortage(0.15)) { 423 + if(log) 424 + ai.print("Build gate for this system", gt.region); 425 + 426 + bool buildLocal = true; 427 + auto@ factory = construction.primaryFactory; 428 + if(factory !is null) { 429 + Region@ factRegion = factory.obj.region; 430 + if(factRegion !is null && systems.hopDistance(gt.region, factRegion) < GATE_BUILD_MOVE_HOPS) 431 + buildLocal = false; 432 + } 433 + 434 + if(buildLocal) 435 + @buildGate = construction.buildLocalStation(gateDesign); 436 + else 437 + @buildGate = construction.buildStation(gateDesign, military.getStationPosition(gt.region)); 438 + } 439 + } 440 + } 441 + } 442 +}; 443 + 444 +AIComponent@ createGate() { 445 + return Gate(); 446 +}
Added scripts/server/empire_ai/weasel/ftl/Hyperdrive.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.Movement; 3 +import empire_ai.weasel.Development; 4 +import empire_ai.weasel.Fleets; 5 + 6 +import ftl; 7 + 8 +from orders import OrderType; 9 + 10 +const double REJUMP_MIN_DIST = 8000.0; 11 +const double STORAGE_AIM_DISTANCE = 40000; 12 + 13 +class Hyperdrive : FTL { 14 + Development@ development; 15 + Fleets@ fleets; 16 + 17 + void create() override { 18 + @development = cast<Development>(ai.development); 19 + @fleets = cast<Fleets>(ai.fleets); 20 + } 21 + 22 + double hdETA(Object& obj, const vec3d& position) { 23 + double charge = HYPERDRIVE_CHARGE_TIME; 24 + if(obj.owner.HyperdriveNeedCharge == 0) 25 + charge = 0.0; 26 + double dist = position.distanceTo(obj.position); 27 + double speed = hyperdriveMaxSpeed(obj); 28 + return charge + dist / speed; 29 + } 30 + 31 + double subETA(Object& obj, const vec3d& position) { 32 + return newtonArrivalTime(obj.maxAcceleration, position - obj.position, vec3d()); 33 + } 34 + 35 + bool shouldHD(Object& obj, const vec3d& position, uint priority) { 36 + //This makes me sad 37 + if(position.distanceTo(obj.position) < 3000) 38 + return false; 39 + double pathDist = cast<Movement>(ai.movement).getPathDistance(obj.position, position, obj.maxAcceleration); 40 + double straightDist = position.distanceTo(obj.position); 41 + return pathDist >= straightDist * 0.6; 42 + } 43 + 44 + uint order(MoveOrder& ord) override { 45 + if(!canHyperdrive(ord.obj)) 46 + return F_Pass; 47 + 48 + double avail = usableFTL(ai, ord); 49 + if(avail > 0) { 50 + vec3d toPosition; 51 + if(targetPosition(ord, toPosition)) { 52 + if(shouldHD(ord.obj, toPosition, ord.priority)) { 53 + double cost = hyperdriveCost(ord.obj, toPosition); 54 + if(avail >= cost) { 55 + ord.obj.addHyperdriveOrder(toPosition); 56 + return F_Continue; 57 + } 58 + } 59 + } 60 + } 61 + 62 + return F_Pass; 63 + } 64 + 65 + uint tick(MoveOrder& ord, double time) { 66 + if(ord.priority == MP_Critical && canHyperdrive(ord.obj) && ord.obj.firstOrderType != OT_Hyperdrive) { 67 + vec3d toPosition; 68 + if(targetPosition(ord, toPosition)) { 69 + double dist = ord.obj.position.distanceToSQ(toPosition); 70 + if(dist > REJUMP_MIN_DIST * REJUMP_MIN_DIST) { 71 + double avail = usableFTL(ai, ord); 72 + double cost = hyperdriveCost(ord.obj, toPosition); 73 + if(avail >= cost && shouldHD(ord.obj, toPosition, ord.priority)) { 74 + cast<Movement>(ai.movement).order(ord); 75 + return F_Continue; 76 + } 77 + } 78 + } 79 + } 80 + return F_Pass; 81 + } 82 + 83 + void focusTick(double time) override { 84 + //Try to get enough ftl storage that we can ftl our largest fleet a fair distance and have some remaining 85 + double highestCost = 0.0; 86 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 87 + auto@ flAI = fleets.fleets[i]; 88 + if(flAI.fleetClass != FC_Combat) 89 + continue; 90 + vec3d toPosition = flAI.obj.position + vec3d(0, 0, STORAGE_AIM_DISTANCE); 91 + highestCost = max(highestCost, double(hyperdriveCost(flAI.obj, toPosition))); 92 + } 93 + development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal); 94 + } 95 +}; 96 + 97 +AIComponent@ createHyperdrive() { 98 + return Hyperdrive(); 99 +}
Added scripts/server/empire_ai/weasel/ftl/Jumpdrive.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.Movement; 3 +import empire_ai.weasel.Development; 4 +import empire_ai.weasel.Fleets; 5 + 6 +import ftl; 7 +import system_flags; 8 +import regions.regions; 9 +import systems; 10 + 11 +from orders import OrderType; 12 + 13 +const double REJUMP_MIN_DIST = 8000.0; 14 + 15 +class Jumpdrive : FTL { 16 + Development@ development; 17 + Fleets@ fleets; 18 + 19 + int safetyFlag = -1; 20 + array<Region@> safeRegions; 21 + 22 + void create() override { 23 + @development = cast<Development>(ai.development); 24 + @fleets = cast<Fleets>(ai.fleets); 25 + 26 + safetyFlag = getSystemFlag("JumpdriveSafety"); 27 + } 28 + 29 + void save(SaveFile& file) { 30 + uint cnt = safeRegions.length; 31 + file << cnt; 32 + for(uint i = 0; i < cnt; ++i) 33 + file << safeRegions[i]; 34 + } 35 + 36 + void load(SaveFile& file) { 37 + uint cnt = 0; 38 + file >> cnt; 39 + safeRegions.length = cnt; 40 + for(uint i = 0; i < cnt; ++i) 41 + file >> safeRegions[i]; 42 + } 43 + 44 + double jdETA(Object& obj, const vec3d& position) { 45 + double charge = JUMPDRIVE_CHARGE_TIME; 46 + return charge; 47 + } 48 + 49 + double subETA(Object& obj, const vec3d& position) { 50 + return newtonArrivalTime(obj.maxAcceleration, position - obj.position, vec3d()); 51 + } 52 + 53 + bool shouldJD(Object& obj, const vec3d& position, uint priority) { 54 + //This makes me sad 55 + if(position.distanceTo(obj.position) < 3000) 56 + return false; 57 + return true; 58 + 59 + /*double factor = 0.8;*/ 60 + /*if(priority == MP_Critical)*/ 61 + /* factor = 1.0;*/ 62 + /*return jdETA(obj, position) <= factor * subETA(obj, position);*/ 63 + } 64 + 65 + uint order(MoveOrder& ord) override { 66 + return order(ord, ord.obj.position, false); 67 + } 68 + 69 + uint order(MoveOrder& ord, const vec3d& fromPos, bool secondary) { 70 + if(!canJumpdrive(ord.obj)) 71 + return F_Pass; 72 + 73 + double avail = usableFTL(ai, ord); 74 + if(avail > 0) { 75 + vec3d toPosition; 76 + if(targetPosition(ord, toPosition)) { 77 + double maxRange = jumpdriveRange(ord.obj); 78 + double dist = toPosition.distanceTo(fromPos); 79 + 80 + bool isSafe = false; 81 + Region@ reg = getRegion(toPosition); 82 + if(reg !is null) 83 + isSafe = reg.getSystemFlag(ai.empire, safetyFlag); 84 + 85 + if(dist > maxRange && !isSafe) { 86 + //See if we should jump to a safe region first 87 + if(!secondary) { 88 + double bestHop = INFINITY; 89 + Region@ hopRegion; 90 + vec3d bestPos; 91 + for(uint i = 0, cnt = safeRegions.length; i < cnt; ++i) { 92 + if(!safeRegions[i].getSystemFlag(ai.empire, safetyFlag)) 93 + continue; 94 + vec3d hopPos = safeRegions[i].position; 95 + hopPos = hopPos + (fromPos-hopPos).normalized(safeRegions[i].radius * 0.85); 96 + double d = hopPos.distanceTo(toPosition); 97 + if(d < bestHop) { 98 + bestHop = d; 99 + @hopRegion = safeRegions[i]; 100 + bestPos = hopPos; 101 + } 102 + } 103 + 104 + if(bestHop < dist * 0.8) { 105 + double cost = jumpdriveCost(ord.obj, fromPos, bestPos); 106 + if(avail >= cost) { 107 + ord.obj.addJumpdriveOrder(bestPos); 108 + order(ord, bestPos, true); 109 + return F_Continue; 110 + } 111 + } 112 + } 113 + 114 + //Shorten our jump 115 + if(ord.priority < MP_Normal) 116 + return F_Pass; 117 + toPosition = fromPos + (toPosition - fromPos).normalized(maxRange); 118 + } 119 + 120 + if(shouldJD(ord.obj, toPosition, ord.priority)) { 121 + double cost = jumpdriveCost(ord.obj, toPosition); 122 + if(avail >= cost) { 123 + ord.obj.addJumpdriveOrder(toPosition, append=secondary); 124 + return F_Continue; 125 + } 126 + } 127 + } 128 + } 129 + 130 + return F_Pass; 131 + } 132 + 133 + uint tick(MoveOrder& ord, double time) { 134 + if(ord.priority == MP_Critical && canJumpdrive(ord.obj) && ord.obj.firstOrderType != OT_Jumpdrive) { 135 + vec3d toPosition; 136 + if(targetPosition(ord, toPosition)) { 137 + double dist = ord.obj.position.distanceToSQ(toPosition); 138 + if(dist > REJUMP_MIN_DIST * REJUMP_MIN_DIST) { 139 + double maxRange = jumpdriveRange(ord.obj); 140 + dist = sqrt(dist); 141 + 142 + bool isSafe = false; 143 + Region@ reg = getRegion(toPosition); 144 + if(reg !is null) 145 + isSafe = reg.getSystemFlag(ai.empire, safetyFlag); 146 + 147 + if(dist > maxRange && !isSafe) 148 + toPosition = ord.obj.position + (toPosition - ord.obj.position).normalized(maxRange); 149 + 150 + if(shouldJD(ord.obj, toPosition, ord.priority)) { 151 + double avail = usableFTL(ai, ord); 152 + double cost = jumpdriveCost(ord.obj, toPosition); 153 + if(avail >= cost) { 154 + cast<Movement>(ai.movement).order(ord); 155 + return F_Continue; 156 + } 157 + } 158 + } 159 + } 160 + } 161 + return F_Pass; 162 + } 163 + 164 + uint sysChk = 0; 165 + void start() { 166 + for(uint i = 0, cnt = systemCount; i < cnt; ++i) { 167 + Region@ reg = getSystem(i).object; 168 + if(reg.getSystemFlag(ai.empire, safetyFlag)) 169 + safeRegions.insertLast(reg); 170 + } 171 + } 172 + 173 + void focusTick(double time) override { 174 + //Try to get enough ftl storage that we can ftl our largest fleet a fair distance and have some remaining 175 + double highestCost = 0.0; 176 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 177 + auto@ flAI = fleets.fleets[i]; 178 + if(flAI.fleetClass != FC_Combat) 179 + continue; 180 + double dist = jumpdriveRange(flAI.obj); 181 + vec3d toPosition = flAI.obj.position + vec3d(0, 0, dist); 182 + highestCost = max(highestCost, double(jumpdriveCost(flAI.obj, toPosition))); 183 + } 184 + development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal); 185 + 186 + //Disable systems that are no longer safe 187 + for(uint i = 0, cnt = safeRegions.length; i < cnt; ++i) { 188 + if(!safeRegions[i].getSystemFlag(ai.empire, safetyFlag)) { 189 + safeRegions.removeAt(i); 190 + --i; --cnt; 191 + } 192 + } 193 + 194 + //Try to find regions that are safe for us 195 + { 196 + sysChk = (sysChk+1) % systemCount; 197 + auto@ reg = getSystem(sysChk).object; 198 + if(reg.getSystemFlag(ai.empire, safetyFlag)) { 199 + if(safeRegions.find(reg) == -1) 200 + safeRegions.insertLast(reg); 201 + } 202 + } 203 + } 204 +}; 205 + 206 +AIComponent@ createJumpdrive() { 207 + return Jumpdrive(); 208 +}
Added scripts/server/empire_ai/weasel/ftl/Slipstream.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.Movement; 3 +import empire_ai.weasel.Military; 4 +import empire_ai.weasel.Construction; 5 +import empire_ai.weasel.Designs; 6 +import empire_ai.weasel.Development; 7 +import empire_ai.weasel.Systems; 8 +import empire_ai.weasel.Budget; 9 +import empire_ai.weasel.Fleets; 10 + 11 +from statuses import getStatusID; 12 +from abilities import getAbilityID; 13 + 14 +from oddity_navigation import hasOddityLink; 15 + 16 +const double SS_MIN_DISTANCE_STAGE = 0; 17 +const double SS_MIN_DISTANCE_DEVELOP = 10000; 18 +const double SS_MIN_TIMER = 3.0 * 60.0; 19 +const double SS_MAX_DISTANCE = 3000.0; 20 + 21 +class SSRegion : Savable { 22 + Region@ region; 23 + Object@ obj; 24 + bool arrived = false; 25 + vec3d destination; 26 + 27 + void save(SaveFile& file) { 28 + file << region; 29 + file << obj; 30 + file << arrived; 31 + file << destination; 32 + } 33 + 34 + void load(SaveFile& file) { 35 + file >> region; 36 + file >> obj; 37 + file >> arrived; 38 + file >> destination; 39 + } 40 +}; 41 + 42 +class Slipstream : FTL { 43 + Military@ military; 44 + Designs@ designs; 45 + Construction@ construction; 46 + Development@ development; 47 + Systems@ systems; 48 + Budget@ budget; 49 + Fleets@ fleets; 50 + 51 + DesignTarget@ ssDesign; 52 + 53 + array<SSRegion@> tracked; 54 + array<Object@> unassigned; 55 + 56 + BuildFlagship@ buildSS; 57 + double nextBuildTry = 15.0 * 60.0; 58 + 59 + void create() override { 60 + @military = cast<Military>(ai.military); 61 + @designs = cast<Designs>(ai.designs); 62 + @construction = cast<Construction>(ai.construction); 63 + @development = cast<Development>(ai.development); 64 + @systems = cast<Systems>(ai.systems); 65 + @budget = cast<Budget>(ai.budget); 66 + @fleets = cast<Fleets>(ai.fleets); 67 + } 68 + 69 + void save(SaveFile& file) override { 70 + designs.saveDesign(file, ssDesign); 71 + construction.saveConstruction(file, buildSS); 72 + file << nextBuildTry; 73 + 74 + uint cnt = tracked.length; 75 + file << cnt; 76 + for(uint i = 0; i < cnt; ++i) 77 + file << tracked[i]; 78 + 79 + cnt = unassigned.length; 80 + file << cnt; 81 + for(uint i = 0; i < cnt; ++i) 82 + file << unassigned[i]; 83 + } 84 + 85 + void load(SaveFile& file) override { 86 + @ssDesign = designs.loadDesign(file); 87 + @buildSS = cast<BuildFlagship>(construction.loadConstruction(file)); 88 + file >> nextBuildTry; 89 + 90 + uint cnt = 0; 91 + file >> cnt; 92 + for(uint i = 0; i < cnt; ++i) { 93 + SSRegion gt; 94 + file >> gt; 95 + tracked.insertLast(gt); 96 + } 97 + 98 + file >> cnt; 99 + for(uint i = 0; i < cnt; ++i) { 100 + Object@ obj; 101 + file >> obj; 102 + if(obj !is null) 103 + unassigned.insertLast(obj); 104 + } 105 + } 106 + 107 + uint order(MoveOrder& ord) override { 108 + //Find the position to fling to 109 + vec3d toPosition; 110 + if(!targetPosition(ord, toPosition)) 111 + return F_Pass; 112 + 113 + //Check if we have a slipstream generator in this region 114 + auto@ gt = get(ord.obj.region); 115 + if(gt is null || gt.obj is null || !gt.arrived) 116 + return F_Pass; 117 + 118 + //Make sure our generator is usable 119 + Object@ ssGen = gt.obj; 120 + if(!canSlipstream(ssGen)) 121 + return F_Pass; 122 + 123 + //Check if we already have a link 124 + if(hasOddityLink(gt.region, toPosition, SS_MAX_DISTANCE, minDuration=60.0)) 125 + return F_Pass; 126 + 127 + //See if we have the FTL to make a link 128 + double avail = usableFTL(ai, ord); 129 + if(!canSlipstreamTo(ssGen, toPosition)) 130 + return F_Pass; 131 + if(slipstreamCost(ssGen, 0, toPosition.distanceTo(ssGen.position)) >= avail) 132 + return F_Pass; 133 + 134 + ssGen.addSlipstreamOrder(toPosition, append=true); 135 + if(ssGen !is ord.obj) { 136 + ord.obj.addWaitOrder(ssGen, moveTo=true); 137 + ssGen.addSecondaryToSlipstream(ord.obj); 138 + } 139 + else { 140 + ord.obj.addMoveOrder(toPosition, append=true); 141 + } 142 + 143 + return F_Continue; 144 + } 145 + 146 + SSRegion@ get(Region@ reg) { 147 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 148 + if(tracked[i].region is reg) 149 + return tracked[i]; 150 + } 151 + return null; 152 + } 153 + 154 + void remove(SSRegion@ gt) { 155 + if(gt.obj !is null && gt.obj.valid && gt.obj.owner is ai.empire) 156 + unassigned.insertLast(gt.obj); 157 + tracked.remove(gt); 158 + } 159 + 160 + Object@ getClosest(const vec3d& position) { 161 + Object@ closest; 162 + double minDist = INFINITY; 163 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 164 + Object@ obj = tracked[i].obj; 165 + if(obj is null) 166 + continue; 167 + if(!tracked[i].arrived) 168 + continue; 169 + double d = obj.position.distanceTo(position); 170 + if(d < minDist) { 171 + minDist = d; 172 + @closest = obj; 173 + } 174 + } 175 + return closest; 176 + } 177 + 178 + SSRegion@ getClosestRegion(const vec3d& position) { 179 + SSRegion@ closest; 180 + double minDist = INFINITY; 181 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 182 + double d = tracked[i].region.position.distanceTo(position); 183 + if(d < minDist) { 184 + minDist = d; 185 + @closest = tracked[i]; 186 + } 187 + } 188 + return closest; 189 + } 190 + 191 + void assignTo(SSRegion@ gt, Object@ closest) { 192 + unassigned.remove(closest); 193 + @gt.obj = closest; 194 + gt.arrived = false; 195 + military.stationFleet(fleets.getAI(closest), gt.region); 196 + 197 + if(closest.region is gt.region) 198 + gt.arrived = true; 199 + 200 + if(!gt.arrived) { 201 + gt.destination = military.getStationPosition(gt.region); 202 + closest.addMoveOrder(gt.destination); 203 + } 204 + } 205 + 206 + bool trackingGen(Object@ obj) { 207 + for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) { 208 + if(unassigned[i] is obj) 209 + return true; 210 + } 211 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 212 + if(tracked[i].obj is obj) 213 + return true; 214 + } 215 + return false; 216 + } 217 + 218 + bool shouldHaveGen(Region@ reg, bool always = false) { 219 + if(military.getBase(reg) !is null) 220 + return true; 221 + if(development.isDevelopingIn(reg)) 222 + return true; 223 + return false; 224 + } 225 + 226 + void turn() override { 227 + if(ssDesign !is null && ssDesign.active !is null) { 228 + int newSize = round(double(budget.spendable(BT_Military)) * 0.2 * ai.behavior.shipSizePerMoney / 64.0) * 64; 229 + if(newSize < 128) 230 + newSize = 128; 231 + if(newSize != ssDesign.targetSize) { 232 + @ssDesign = designs.design(DP_Slipstream, newSize); 233 + ssDesign.customName = "Slipstream"; 234 + } 235 + } 236 + } 237 + 238 + void focusTick(double time) override { 239 + if(ai.behavior.forbidConstruction) return; 240 + 241 + //Design a generator 242 + if(ssDesign is null) { 243 + @ssDesign = designs.design(DP_Slipstream, 128); 244 + ssDesign.customName = "Slipstream"; 245 + } 246 + 247 + //Manage unassigned gens list 248 + for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) { 249 + Object@ obj = unassigned[i]; 250 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 251 + unassigned.removeAt(i); 252 + --i; --cnt; 253 + } 254 + } 255 + 256 + //Detect new gens 257 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 258 + auto@ flAI = fleets.fleets[i]; 259 + if(flAI.fleetClass != FC_Slipstream) 260 + continue; 261 + if(!trackingGen(flAI.obj)) 262 + unassigned.insertLast(flAI.obj); 263 + } 264 + 265 + //Update existing gens for staging bases 266 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 267 + auto@ gt = tracked[i]; 268 + bool checkAlways = false; 269 + if(gt.obj !is null) { 270 + if(!gt.obj.valid || gt.obj.owner !is ai.empire || (gt.arrived && gt.obj.region !is gt.region)) { 271 + @gt.obj = null; 272 + gt.arrived = false; 273 + checkAlways = true; 274 + } 275 + else if(!gt.arrived && !gt.obj.hasOrders) { 276 + if(gt.destination.distanceTo(gt.obj.position) < 10.0) 277 + gt.arrived = true; 278 + else 279 + assignTo(gt, gt.obj); 280 + } 281 + } 282 + if(!shouldHaveGen(gt.region, checkAlways)) { 283 + remove(tracked[i]); 284 + --i; --cnt; 285 + } 286 + } 287 + 288 + //Detect new staging bases to build gens at 289 + for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) { 290 + auto@ base = military.stagingBases[i]; 291 + if(base.occupiedTime < SS_MIN_TIMER) 292 + continue; 293 + 294 + if(get(base.region) is null) { 295 + SSRegion@ closest = getClosestRegion(base.region.position); 296 + if(closest !is null && closest.region.position.distanceTo(base.region.position) < SS_MIN_DISTANCE_STAGE) 297 + continue; 298 + 299 + SSRegion gt; 300 + @gt.region = base.region; 301 + tracked.insertLast(gt); 302 + break; 303 + } 304 + } 305 + 306 + //Detect new important planets to build generator at 307 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 308 + auto@ focus = development.focuses[i]; 309 + Region@ reg = focus.obj.region; 310 + if(reg is null) 311 + continue; 312 + 313 + if(get(reg) is null) { 314 + SSRegion@ closest = getClosestRegion(reg.position); 315 + if(closest !is null && closest.region.position.distanceTo(reg.position) < SS_MIN_DISTANCE_DEVELOP) 316 + continue; 317 + 318 + SSRegion gt; 319 + @gt.region = reg; 320 + tracked.insertLast(gt); 321 + break; 322 + } 323 + } 324 + 325 + //See if we should build a new generator 326 + if(buildSS !is null) { 327 + if(buildSS.completed) { 328 + @buildSS = null; 329 + nextBuildTry = gameTime + 60.0; 330 + } 331 + } 332 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 333 + auto@ gt = tracked[i]; 334 + if(gt.obj is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) { 335 + Object@ closest; 336 + double closestDist = INFINITY; 337 + for(uint n = 0, ncnt = unassigned.length; n < ncnt; ++n) { 338 + Object@ obj = unassigned[n]; 339 + if(obj.region is gt.region) { 340 + @closest = obj; 341 + break; 342 + } 343 + if(!obj.hasMover) 344 + continue; 345 + if(buildSS is null && gameTime > nextBuildTry) { 346 + double d = obj.position.distanceTo(gt.region.position); 347 + if(d < closestDist) { 348 + closestDist = d; 349 + @closest = obj; 350 + } 351 + } 352 + } 353 + 354 + if(closest !is null) { 355 + if(log) 356 + ai.print("Assign slipstream gen to => "+gt.region.name, closest.region); 357 + assignTo(gt, closest); 358 + } else if(buildSS is null && gameTime > nextBuildTry) { 359 + if(log) 360 + ai.print("Build slipstream gen for this system", gt.region); 361 + 362 + @buildSS = construction.buildFlagship(ssDesign); 363 + } 364 + } 365 + } 366 + 367 + //Try to get enough ftl storage that we can permanently open a slipstream with each of generators 368 + double mostCost = 0.0; 369 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 370 + Ship@ obj = cast<Ship>(tracked[i].obj); 371 + if(obj is null) 372 + continue; 373 + 374 + double baseCost = obj.blueprint.design.average(SV_SlipstreamCost); 375 + double duration = obj.blueprint.design.average(SV_SlipstreamDuration); 376 + mostCost += baseCost / duration; 377 + } 378 + development.aimFTLStorage = mostCost; 379 + } 380 +}; 381 + 382 +AIComponent@ createSlipstream() { 383 + return Slipstream(); 384 +}
Added scripts/server/empire_ai/weasel/misc/Invasion.as.
1 +import empire_ai.weasel.WeaselAI; 2 + 3 +import empire_ai.weasel.Fleets; 4 +import empire_ai.weasel.Systems; 5 +import empire_ai.weasel.Movement; 6 +import empire_ai.weasel.searches; 7 + 8 +import systems; 9 +from empire import Pirates; 10 + 11 +class InvasionDefendMission : Mission { 12 + FleetAI@ fleet; 13 + Region@ targRegion; 14 + MoveOrder@ move; 15 + bool pending = false; 16 + 17 + Object@ eliminate; 18 + 19 + void save(Fleets& fleets, SaveFile& file) override { 20 + fleets.saveAI(file, fleet); 21 + file << targRegion; 22 + fleets.movement.saveMoveOrder(file, move); 23 + file << pending; 24 + } 25 + 26 + void load(Fleets& fleets, SaveFile& file) override { 27 + @fleet = fleets.loadAI(file); 28 + file >> targRegion; 29 + @move = fleets.movement.loadMoveOrder(file); 30 + file >> pending; 31 + } 32 + 33 + bool get_isActive() override { 34 + return targRegion !is null; 35 + } 36 + 37 + void tick(AI& ai, FleetAI& fleet, double time) override { 38 + if(targRegion is null) 39 + return; 40 + if(move !is null) 41 + return; 42 + 43 + //Find stuff to fight 44 + if(eliminate is null) 45 + @eliminate = findEnemy(targRegion, null, ai.empire.hostileMask); 46 + 47 + if(eliminate !is null) { 48 + if(!eliminate.valid) { 49 + @eliminate = null; 50 + } 51 + else { 52 + if(!fleet.obj.hasOrders) 53 + fleet.obj.addAttackOrder(eliminate); 54 + if((fleet.filled < 0.3 || fleet.supplies < 0.3 || fleet.flagshipHealth < 0.5) 55 + && eliminate.getFleetStrength() * 2.0 > fleet.strength 56 + && !pending) { 57 + @targRegion = null; 58 + @eliminate = null; 59 + @move = cast<Fleets>(ai.fleets).returnToBase(fleet, MP_Critical); 60 + } 61 + } 62 + } 63 + } 64 + 65 + void update(AI& ai, Invasion& invasion) { 66 + //Manage movement 67 + if(move !is null) { 68 + if(move.failed || move.completed) 69 + @move = null; 70 + } 71 + 72 + //Find new regions to go to 73 + if(targRegion is null || (!pending && move is null && !invasion.isFighting(targRegion))) { 74 + bool ready = fleet.actionableState && move is null; 75 + 76 + DefendSystem@ bestDef; 77 + double bestWeight = 0.0; 78 + 79 + for(uint i = 0, cnt = invasion.defending.length; i < cnt; ++i) { 80 + auto@ def = invasion.defending[i]; 81 + double w = randomd(0.9, 1.1); 82 + if(!def.fighting) { 83 + if(!ready) 84 + continue; 85 + else 86 + w *= 0.1; 87 + } 88 + 89 + if(!def.winning) { 90 + w *= 10.0; 91 + } 92 + else { 93 + if(!ready) 94 + continue; 95 + } 96 + 97 + if(def.obj is targRegion) 98 + w *= 1.5; 99 + 100 + if(w > bestWeight) { 101 + bestWeight = w; 102 + @bestDef = def; 103 + } 104 + } 105 + 106 + if(bestDef !is null && fleet.supplies >= 0.25 && fleet.filled >= 0.2 && fleet.fleetHealth >= 0.2) { 107 + @targRegion = bestDef.obj; 108 + invasion.pend(targRegion, fleet); 109 + pending = true; 110 + } 111 + } 112 + 113 + //Move to the region we want to go to 114 + if(targRegion !is null) { 115 + if(move is null) { 116 + if(fleet.obj.region !is targRegion) { 117 + @eliminate = findEnemy(targRegion, null, ai.empire.hostileMask); 118 + if(eliminate is null) { 119 + vec3d targPos = targRegion.position; 120 + targPos += (targRegion.position - ai.empire.HomeSystem.position).normalized(targRegion.radius * 0.85); 121 + 122 + @move = invasion.movement.move(fleet.obj, targPos, MP_Critical); 123 + } 124 + else { 125 + @move = invasion.movement.move(fleet.obj, eliminate, MP_Critical, nearOnly=true); 126 + } 127 + } 128 + else { 129 + //Remove from pending list 130 + if(pending) { 131 + invasion.unpend(targRegion, fleet); 132 + pending = false; 133 + } 134 + 135 + //See if we should return to base 136 + if(!invasion.isFighting(targRegion) && (fleet.supplies < 0.25 || fleet.filled < 0.5)) { 137 + @targRegion = null; 138 + @move = invasion.fleets.returnToBase(fleet, MP_Critical); 139 + } 140 + } 141 + } 142 + } 143 + } 144 +}; 145 + 146 +class DefendSystem { 147 + Region@ obj; 148 + array<FleetAI@> pending; 149 + 150 + double enemyStrength = 0.0; 151 + double ourStrength = 0.0; 152 + double remnantStrength = 0.0; 153 + double pendingStrength = 0.0; 154 + 155 + void save(Invasion& invasion, SaveFile& file) { 156 + file << obj; 157 + 158 + uint cnt = pending.length; 159 + file << cnt; 160 + for(uint i = 0; i < cnt; ++i) 161 + invasion.fleets.saveAI(file, pending[i]); 162 + 163 + file << enemyStrength; 164 + file << ourStrength; 165 + file << remnantStrength; 166 + file << pendingStrength; 167 + } 168 + 169 + void load(Invasion& invasion, SaveFile& file) { 170 + file >> obj; 171 + 172 + uint cnt = 0; 173 + file >> cnt; 174 + for(uint i = 0; i < cnt; ++i) { 175 + auto@ fleet = invasion.fleets.loadAI(file); 176 + if(fleet !is null && fleet.obj !is null) 177 + pending.insertLast(fleet); 178 + } 179 + 180 + file >> enemyStrength; 181 + file >> ourStrength; 182 + file >> remnantStrength; 183 + file >> pendingStrength; 184 + } 185 + 186 + void update(AI& ai, Invasion& invasion) { 187 + enemyStrength = getTotalFleetStrength(obj, ai.empire.hostileMask); 188 + 189 + ourStrength = getTotalFleetStrength(obj, ai.mask); 190 + remnantStrength = getTotalFleetStrength(obj, Pirates.mask); 191 + if(gameTime < 10.0 * 60.0) 192 + ourStrength += remnantStrength; 193 + else if(gameTime < 30.0 * 60.0) 194 + ourStrength += remnantStrength * 0.5; 195 + 196 + pendingStrength = 0.0; 197 + for(uint i = 0, cnt = pending.length; i < cnt; ++i) 198 + pendingStrength += sqrt(pending[i].strength); 199 + pendingStrength *= pendingStrength; 200 + 201 + if(obj.PlanetsMask & ai.empire.mask != 0) 202 + ai.empire.setDefending(obj, true); 203 + } 204 + 205 + bool get_fighting() { 206 + return enemyStrength > 0; 207 + } 208 + 209 + bool get_winning() { 210 + return ourStrength + pendingStrength > enemyStrength; 211 + } 212 +}; 213 + 214 +class Invasion : AIComponent { 215 + Fleets@ fleets; 216 + Movement@ movement; 217 + 218 + array<DefendSystem@> defending; 219 + array<InvasionDefendMission@> tracked; 220 + 221 + void create() { 222 + @fleets = cast<Fleets>(ai.fleets); 223 + @movement = cast<Movement>(ai.movement); 224 + 225 + ai.behavior.maintenancePerShipSize = 0.0; 226 + } 227 + 228 + void save(SaveFile& file) { 229 + uint cnt = defending.length; 230 + file << cnt; 231 + for(uint i = 0; i < cnt; ++i) 232 + defending[i].save(this, file); 233 + 234 + cnt = tracked.length; 235 + file << cnt; 236 + for(uint i = 0; i < cnt; ++i) 237 + fleets.saveMission(file, tracked[i]); 238 + } 239 + 240 + void load(SaveFile& file) { 241 + uint cnt = 0; 242 + 243 + file >> cnt; 244 + for(uint i = 0; i < cnt; ++i) { 245 + DefendSystem def; 246 + def.load(this, file); 247 + defending.insertLast(def); 248 + } 249 + 250 + file >> cnt; 251 + for(uint i = 0; i < cnt; ++i) { 252 + InvasionDefendMission@ miss = cast<InvasionDefendMission>(fleets.loadMission(file)); 253 + if(miss !is null) 254 + tracked.insertLast(miss); 255 + } 256 + } 257 + 258 + void start() { 259 + //Find systems to defend 260 + Region@ home = ai.empire.HomeSystem; 261 + const SystemDesc@ sys = getSystem(home); 262 + for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) { 263 + auto@ otherSys = getSystem(sys.adjacent[i]); 264 + if(findEnemy(otherSys.object, null, Pirates.mask, fleets=false, stations=true) !is null) { 265 + DefendSystem def; 266 + @def.obj = otherSys.object; 267 + defending.insertLast(def); 268 + } 269 + } 270 + } 271 + 272 + bool isManaging(FleetAI& fleet) { 273 + if(fleet.mission is null) 274 + return false; 275 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 276 + if(tracked[i] is fleet.mission) 277 + return true; 278 + } 279 + return false; 280 + } 281 + 282 + void manage(FleetAI& fleet) { 283 + InvasionDefendMission miss; 284 + @miss.fleet = fleet; 285 + 286 + fleets.performMission(fleet, miss); 287 + tracked.insertLast(miss); 288 + } 289 + 290 + void pend(Region@ region, FleetAI& fleet) { 291 + for(uint i = 0, cnt = defending.length; i < cnt; ++i ){ 292 + if(defending[i].obj is region) { 293 + defending[i].pending.insertLast(fleet); 294 + break; 295 + } 296 + } 297 + } 298 + 299 + void unpend(Region@ region, FleetAI& fleet) { 300 + for(uint i = 0, cnt = defending.length; i < cnt; ++i ){ 301 + if(defending[i].obj is region) { 302 + defending[i].pending.remove(fleet); 303 + break; 304 + } 305 + } 306 + } 307 + 308 + DefendSystem@ getDefending(Region@ region) { 309 + for(uint i = 0, cnt = defending.length; i < cnt; ++i ){ 310 + if(defending[i].obj is region) 311 + return defending[i]; 312 + } 313 + return null; 314 + } 315 + 316 + bool isFighting(Region@ region) { 317 + for(uint i = 0, cnt = defending.length; i < cnt; ++i ){ 318 + if(defending[i].obj is region) 319 + return defending[i].fighting; 320 + } 321 + return false; 322 + } 323 + 324 + uint sysUpd = 0; 325 + void focusTick(double time) { 326 + //All your fleets are belong to us 327 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 328 + auto@ flAI = fleets.fleets[i]; 329 + if(flAI.fleetClass != FC_Combat) 330 + continue; 331 + if(!isManaging(flAI)) 332 + manage(flAI); 333 + } 334 + 335 + //Update systems we're defending 336 + if(defending.length != 0) { 337 + sysUpd = (sysUpd+1) % defending.length; 338 + defending[sysUpd].update(ai, this); 339 + } 340 + 341 + //Make sure our fleets are in the right places 342 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) 343 + tracked[i].update(ai, this); 344 + } 345 +}; 346 + 347 +AIComponent@ createInvasion() { 348 + return Invasion(); 349 +}
Added scripts/server/empire_ai/weasel/race/Ancient.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.race.Race; 3 + 4 +import empire_ai.weasel.Colonization; 5 +import empire_ai.weasel.Construction; 6 +import empire_ai.weasel.Resources; 7 +import empire_ai.weasel.Development; 8 +import empire_ai.weasel.Movement; 9 +import empire_ai.weasel.Planets; 10 +import empire_ai.weasel.Orbitals; 11 + 12 +from orbitals import getOrbitalModule, OrbitalModule; 13 +from buildings import getBuildingType, BuildingType; 14 +from resources import ResourceType, getResource, getResourceID; 15 +from statuses import getStatusID; 16 +from biomes import getBiomeID; 17 + 18 +enum PlanetClass { 19 + PC_Empty, 20 + PC_Core, 21 + PC_Mine, 22 + PC_Transmute, 23 +} 24 + 25 +class TrackReplicator { 26 + Object@ obj; 27 + Planet@ target; 28 + bool arrived = false; 29 + MoveOrder@ move; 30 + BuildingRequest@ build; 31 + uint intention = PC_Empty; 32 + 33 + bool get_busy() { 34 + if(target is null) 35 + return false; 36 + if(!arrived || move !is null || build !is null) 37 + return true; 38 + return false; 39 + } 40 + 41 + void save(Ancient& ancient, SaveFile& file) { 42 + file << obj; 43 + file << target; 44 + file << arrived; 45 + ancient.movement.saveMoveOrder(file, move); 46 + ancient.planets.saveBuildingRequest(file, build); 47 + file << intention; 48 + } 49 + 50 + void load(Ancient& ancient, SaveFile& file) { 51 + file >> obj; 52 + file >> target; 53 + file >> arrived; 54 + @move = ancient.movement.loadMoveOrder(file); 55 + @build = ancient.planets.loadBuildingRequest(file); 56 + file >> intention; 57 + } 58 +}; 59 + 60 +class Ancient : Race, RaceResources, RaceColonization { 61 + Colonization@ colonization; 62 + Construction@ construction; 63 + Resources@ resources; 64 + Planets@ planets; 65 + Development@ development; 66 + Movement@ movement; 67 + Orbitals@ orbitals; 68 + 69 + array<TrackReplicator@> replicators; 70 + 71 + const OrbitalModule@ replicatorMod; 72 + 73 + const BuildingType@ core; 74 + const BuildingType@ miner; 75 + const BuildingType@ transmuter; 76 + 77 + const BuildingType@ foundry; 78 + 79 + const BuildingType@ depot; 80 + const BuildingType@ refinery; 81 + const BuildingType@ reinforcer; 82 + const BuildingType@ developer; 83 + const BuildingType@ compressor; 84 + 85 + int claimStatus = -1; 86 + int replicatorStatus = -1; 87 + 88 + int mountainsBiome = -1; 89 + 90 + int oreResource = -1; 91 + int baseMatResource = -1; 92 + 93 + bool foundFirstT2 = false; 94 + 95 + void create() { 96 + @colonization = cast<Colonization>(ai.colonization); 97 + colonization.performColonization = false; 98 + 99 + @resources = cast<Resources>(ai.resources); 100 + @construction = cast<Construction>(ai.construction); 101 + @movement = cast<Movement>(ai.movement); 102 + @planets = cast<Planets>(ai.planets); 103 + @orbitals = cast<Orbitals>(ai.orbitals); 104 + @planets = cast<Planets>(ai.planets); 105 + 106 + @development = cast<Development>(ai.development); 107 + development.managePlanetPressure = false; 108 + development.buildBuildings = false; 109 + development.colonizeResources = false; 110 + 111 + @replicatorMod = getOrbitalModule("AncientReplicator"); 112 + 113 + @transmuter = getBuildingType("AncientTransmuter"); 114 + @miner = getBuildingType("AncientMiner"); 115 + @core = getBuildingType("AncientCore"); 116 + 117 + @foundry = getBuildingType("AncientFoundry"); 118 + 119 + @depot = getBuildingType("AncientDepot"); 120 + @refinery = getBuildingType("AncientRefinery"); 121 + @reinforcer = getBuildingType("AncientReinforcer"); 122 + @developer = getBuildingType("AncientDeveloper"); 123 + @compressor = getBuildingType("Compressor"); 124 + 125 + claimStatus = getStatusID("AncientClaim"); 126 + replicatorStatus = getStatusID("AncientReplicator"); 127 + 128 + mountainsBiome = getBiomeID("Mountains"); 129 + 130 + oreResource = getResourceID("OreRate"); 131 + baseMatResource = getResourceID("BaseMaterial"); 132 + 133 + @ai.defs.Factory = null; 134 + @ai.defs.LaborStorage = null; 135 + } 136 + 137 + void save(SaveFile& file) override { 138 + file << foundFirstT2; 139 + uint cnt = replicators.length; 140 + file << cnt; 141 + for(uint i = 0; i < cnt; ++i) 142 + replicators[i].save(this, file); 143 + } 144 + 145 + void load(SaveFile& file) override { 146 + file >> foundFirstT2; 147 + uint cnt = 0; 148 + file >> cnt; 149 + for(uint i = 0; i < cnt; ++i) { 150 + TrackReplicator t; 151 + t.load(this, file); 152 + if(t.obj !is null) 153 + replicators.insertLast(t); 154 + } 155 + } 156 + 157 + void levelRequirements(Object& obj, int targetLevel, array<ResourceSpec@>& specs) { 158 + //YOLO 159 + specs.length = 0; 160 + } 161 + 162 + bool orderColonization(ColonizeData& data, Planet@ sourcePlanet) { 163 + return true; 164 + } 165 + 166 + double getGenericUsefulness(const ResourceType@ type) { 167 + return 1.0; 168 + } 169 + 170 + bool hasReplicator(Planet& pl) { 171 + for(uint i = 0, cnt = replicators.length; i < cnt; ++i) { 172 + if(replicators[i].target is pl) 173 + return true; 174 + } 175 + return false; 176 + } 177 + 178 + bool isTracking(Object& obj) { 179 + for(uint i = 0, cnt = replicators.length; i < cnt; ++i) { 180 + if(replicators[i].obj is obj) 181 + return true; 182 + } 183 + return false; 184 + } 185 + 186 + void trackReplicator(Object& obj) { 187 + TrackReplicator t; 188 + @t.obj = obj; 189 + 190 + replicators.insertLast(t); 191 + } 192 + 193 + void updateRequests(Planet& pl) { 194 + //Handle requests for base materials 195 + uint baseMatReqs = 0; 196 + baseMatReqs += pl.getBuildingCount(depot.id); 197 + baseMatReqs += pl.getBuildingCount(refinery.id); 198 + baseMatReqs += pl.getBuildingCount(reinforcer.id); 199 + baseMatReqs += pl.getBuildingCount(developer.id); 200 + baseMatReqs += pl.getBuildingCount(compressor.id); 201 + 202 + array<ImportData@> curBaseMat; 203 + resources.getImportsOf(curBaseMat, baseMatResource, pl); 204 + 205 + if(curBaseMat.length < baseMatReqs) { 206 + for(uint i = curBaseMat.length, cnt = baseMatReqs; i < cnt; ++i) { 207 + ResourceSpec spec; 208 + spec.type = RST_Specific; 209 + @spec.resource = getResource(baseMatResource); 210 + 211 + resources.requestResource(pl, spec); 212 + } 213 + } 214 + else if(curBaseMat.length > baseMatReqs) { 215 + for(uint i = baseMatReqs, cnt = curBaseMat.length; i < cnt; ++i) 216 + resources.cancelRequest(curBaseMat[i]); 217 + } 218 + 219 + //Handle requests for ore 220 + uint oreReqs = 0; 221 + oreReqs += pl.getBuildingCount(foundry.id); 222 + 223 + array<ImportData@> curOre; 224 + resources.getImportsOf(curOre, oreResource, pl); 225 + 226 + if(curOre.length < oreReqs) { 227 + for(uint i = curOre.length, cnt = oreReqs; i < cnt; ++i) { 228 + ResourceSpec spec; 229 + spec.type = RST_Specific; 230 + @spec.resource = getResource(oreResource); 231 + 232 + resources.requestResource(pl, spec); 233 + } 234 + } 235 + else if(curOre.length > oreReqs) { 236 + for(uint i = oreReqs, cnt = curOre.length; i < cnt; ++i) 237 + resources.cancelRequest(curOre[i]); 238 + } 239 + } 240 + 241 + uint plInd = 0; 242 + void focusTick(double time) { 243 + if(ai.behavior.forbidColonization) return; 244 + 245 + //Find new replicators 246 + for(uint i = 0, cnt = orbitals.orbitals.length; i < cnt; ++i) { 247 + auto@ orb = cast<Orbital>(orbitals.orbitals[i].obj); 248 + if(orb.coreModule == replicatorMod.id) { 249 + if(!isTracking(orb)) 250 + trackReplicator(orb); 251 + } 252 + } 253 + 254 + //Update requests for planets 255 + if(planets.planets.length != 0) { 256 + for(uint n = 0, ncnt = min(planets.planets.length, 10); n < ncnt; ++n) { 257 + plInd = (plInd+1) % planets.planets.length; 258 + Planet@ pl = planets.planets[plInd].obj; 259 + 260 + if(classify(pl) == PC_Core) 261 + updateRequests(pl); 262 + } 263 + } 264 + 265 + //Manage existing replicators 266 + for(uint i = 0, cnt = replicators.length; i < cnt; ++i) { 267 + auto@ t = replicators[i]; 268 + if(t.obj is null || !t.obj.valid || t.obj.owner !is ai.empire) { 269 + replicators.removeAt(i); 270 + --i; --cnt; 271 + continue; 272 + } 273 + 274 + if(t.target !is null) { 275 + if(!t.target.valid) { 276 + @t.target = null; 277 + if(!t.arrived) 278 + t.obj.stopMoving(); 279 + t.arrived = false; 280 + } 281 + else if(t.target.owner !is ai.empire && t.target.owner.valid) { 282 + @t.target = null; 283 + if(!t.arrived) 284 + t.obj.stopMoving(); 285 + t.arrived = false; 286 + } 287 + } 288 + 289 + if(t.move !is null) { 290 + if(t.move.failed) { 291 + @t.move = null; 292 + t.arrived = false; 293 + } 294 + else if(t.move.completed) { 295 + if(t.obj.isOrbitingAround(t.target)) { 296 + @t.move = null; 297 + t.arrived = true; 298 + } 299 + else if(t.obj.inOrbit) { 300 + @t.move = null; 301 + t.arrived = false; 302 + @t.target = null; 303 + } 304 + } 305 + } 306 + else if(t.target !is null && !t.arrived) { 307 + @t.move = movement.move(t.obj, t.target); 308 + } 309 + 310 + if(t.build !is null) { 311 + if(t.build.canceled) { 312 + //A build failed, give up on this planet 313 + if(log) 314 + ai.print("Failed building build", t.target); 315 + @t.target = null; 316 + @t.build = null; 317 + t.arrived = false; 318 + } 319 + else if(t.build.built) { 320 + float progress = t.build.getProgress(); 321 + if(progress >= 1.f) { 322 + if(log) 323 + ai.print("Completed building build", t.target); 324 + @t.build = null; 325 + } 326 + else if(progress < -0.5f) { 327 + if(log) 328 + ai.print("Failed building build location "+t.build.builtAt, t.target); 329 + @t.build = null; 330 + @t.target = null; 331 + t.arrived = false; 332 + } 333 + } 334 + } 335 + 336 + if(t.arrived || t.target is null) { 337 + if(!t.busy) 338 + useReplicator(t); 339 + } 340 + } 341 + } 342 + 343 + uint classify(Planet& pl) { 344 + int resType = pl.primaryResourceType; 345 + if(resType == oreResource) 346 + return PC_Mine; 347 + if(resType == baseMatResource) 348 + return PC_Transmute; 349 + uint claims = pl.getStatusStackCountAny(claimStatus); 350 + if(claims <= 1) 351 + return PC_Empty; 352 + if(pl.getBuildingCount(core.id) >= 1) 353 + return PC_Core; 354 + if(pl.getBuildingCount(transmuter.id) >= 1) 355 + return PC_Transmute; 356 + if(pl.getBuildingCount(miner.id) >= 1) 357 + return PC_Mine; 358 + return PC_Empty; 359 + } 360 + 361 + bool shouldBeCore(const ResourceType@ type) { 362 + if(type.level >= 1) 363 + return true; 364 + if(type.totalPressure >= 8) 365 + return true; 366 + return false; 367 + } 368 + 369 + int openOreRequests(TrackReplicator@ discount = null) { 370 + int reqs = 0; 371 + for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) { 372 + auto@ req = resources.requested[i]; 373 + if(req.beingMet) 374 + continue; 375 + if(req.spec.type != RST_Specific) 376 + continue; 377 + if(req.spec.resource.id != uint(oreResource)) 378 + continue; 379 + reqs += 1; 380 + } 381 + for(uint i = 0, cnt = replicators.length; i < cnt; ++i) { 382 + auto@ t = replicators[i]; 383 + if(t is discount) 384 + continue; 385 + if(t.target is null) 386 + continue; 387 + if(t.intention == PC_Mine && (t.build is null || t.build.type is miner)) 388 + reqs -= 1; 389 + } 390 + return reqs; 391 + } 392 + 393 + int openBaseMatRequests(TrackReplicator@ discount = null) { 394 + int reqs = 0; 395 + for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) { 396 + auto@ req = resources.requested[i]; 397 + if(req.beingMet) 398 + continue; 399 + if(req.spec.type != RST_Specific) 400 + continue; 401 + if(req.spec.resource.id != uint(baseMatResource)) 402 + continue; 403 + reqs += 1; 404 + } 405 + for(uint i = 0, cnt = replicators.length; i < cnt; ++i) { 406 + auto@ t = replicators[i]; 407 + if(t is discount) 408 + continue; 409 + if(t.target is null) 410 + continue; 411 + if(t.intention == PC_Transmute && (t.build is null || t.build.type is transmuter)) 412 + reqs -= 1; 413 + } 414 + return reqs; 415 + } 416 + 417 + void build(TrackReplicator& t, const BuildingType@ building) { 418 + auto@ plAI = planets.getAI(t.target); 419 + if(plAI is null) 420 + return; 421 + if(!t.target.hasStatusEffect(replicatorStatus)) 422 + return; 423 + 424 + //bool scatter = building is miner || building is transmuter; 425 + bool scatter = false; 426 + @t.build = planets.requestBuilding(plAI, building, scatter=scatter, moneyType=BT_Colonization); 427 + 428 + if(log) 429 + ai.print("Build "+building.name, t.target); 430 + } 431 + 432 + void useReplicator(TrackReplicator& t) { 433 + if(t.target !is null) { 434 + uint type = classify(t.target); 435 + switch(type) { 436 + case PC_Empty: { 437 + const ResourceType@ res = getResource(t.target.primaryResourceType); 438 + if(res is null) { 439 + @t.target = null; 440 + t.arrived = false; 441 + return; 442 + } 443 + 444 + if(shouldBeCore(res)) { 445 + build(t, core); 446 + } 447 + else if(openBaseMatRequests(t) >= openOreRequests(t) || gameTime < 6.0 * 60.0 || !t.target.hasBiome(mountainsBiome)) { 448 + build(t, transmuter); 449 + } 450 + else { 451 + build(t, miner); 452 + } 453 + return; 454 + } 455 + case PC_Transmute: 456 + @t.target = null; 457 + t.arrived = false; 458 + break; 459 + case PC_Mine: 460 + @t.target = null; 461 + t.arrived = false; 462 + break; 463 + case PC_Core: 464 + build(t, refinery); 465 + return; 466 + } 467 + } 468 + 469 + //Find a new planet to colonize 470 + PotentialColonize@ best; 471 + double bestWeight = 0.0; 472 + 473 + uint getType = PC_Core; 474 + if(openBaseMatRequests() >= 1) 475 + getType = PC_Transmute; 476 + else if(openOreRequests() >= 1 && gameTime > 6.0 * 60.0) 477 + getType = PC_Mine; 478 + 479 + auto@ potentials = colonization.getPotentialColonize(); 480 + for(uint i = 0, cnt = potentials.length; i < cnt; ++i) { 481 + PotentialColonize@ p = potentials[i]; 482 + if(hasReplicator(p.pl)) 483 + continue; 484 + 485 + double w = p.weight; 486 + if(!foundFirstT2 && p.resource.level >= 2) 487 + w *= 100.0; 488 + else if((getType == PC_Core) != shouldBeCore(p.resource)) 489 + w *= 0.6; 490 + if(getType == PC_Core && p.resource.level >= 2) 491 + w *= 4.0; 492 + if(getType == PC_Core && p.resource.level >= 3) 493 + w *= 6.0; 494 + if(getType == PC_Mine && !p.pl.hasBiome(mountainsBiome)) 495 + w *= 0.1; 496 + if(getType == PC_Core) 497 + w *= double(p.pl.totalSurfaceTiles) / 100.0; 498 + w /= p.pl.position.distanceTo(t.obj.position)/1000.0; 499 + 500 + if(w > bestWeight) { 501 + bestWeight = w; 502 + @best = p; 503 + } 504 + } 505 + 506 + if(best !is null) { 507 + @t.target = best.pl; 508 + t.intention = shouldBeCore(best.resource) ? uint(PC_Core) : getType; 509 + t.arrived = false; 510 + if(!foundFirstT2) { 511 + if(best.resource.level == 2) 512 + foundFirstT2 = true; 513 + } 514 + } 515 + } 516 +}; 517 + 518 +AIComponent@ createAncient() { 519 + return Ancient(); 520 +}
Added scripts/server/empire_ai/weasel/race/Devout.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.race.Race; 3 + 4 +import empire_ai.weasel.Development; 5 +import empire_ai.weasel.Planets; 6 +import empire_ai.weasel.Budget; 7 + 8 +import resources; 9 +import buildings; 10 +import attributes; 11 + 12 +class Devout : Race, RaceDevelopment { 13 + Development@ development; 14 + Planets@ planets; 15 + Budget@ budget; 16 + 17 + const ResourceType@ altarResource; 18 + const BuildingType@ altar; 19 + 20 + int coverAttrib = -1; 21 + 22 + BuildingRequest@ altarBuild; 23 + Planet@ focusAltar; 24 + 25 + double considerTimer = 0.0; 26 + 27 + void save(SaveFile& file) { 28 + planets.saveBuildingRequest(file, altarBuild); 29 + file << focusAltar; 30 + file << considerTimer; 31 + } 32 + 33 + void load(SaveFile& file) { 34 + @altarBuild = planets.loadBuildingRequest(file); 35 + file >> focusAltar; 36 + file >> considerTimer; 37 + } 38 + 39 + void create() { 40 + @planets = cast<Planets>(ai.planets); 41 + @development = cast<Development>(ai.development); 42 + @budget = cast<Budget>(ai.budget); 43 + 44 + @altarResource = getResource("Altar"); 45 + 46 + @altar = getBuildingType("Altar"); 47 + 48 + coverAttrib = getEmpAttribute("AltarSupportedPopulation"); 49 + } 50 + 51 + void start() { 52 + auto@ data = ai.empire.getPlanets(); 53 + Object@ obj; 54 + while(receive(data, obj)) { 55 + Planet@ pl = cast<Planet>(obj); 56 + if(pl !is null){ 57 + if(pl.primaryResourceType == altarResource.id) { 58 + @focusAltar = pl; 59 + break; 60 + } 61 + } 62 + } 63 + } 64 + 65 + bool shouldBeFocus(Planet& pl, const ResourceType@ resource) override { 66 + if(resource is altarResource) 67 + return true; 68 + return false; 69 + } 70 + 71 + void focusTick(double time) override { 72 + if(ai.behavior.forbidConstruction) return; 73 + 74 + //Handle our current altar build 75 + if(altarBuild !is null) { 76 + if(altarBuild.built) { 77 + @focusAltar = altarBuild.plAI.obj; 78 + @altarBuild = null; 79 + } 80 + else if(altarBuild.canceled) { 81 + @altarBuild = null; 82 + } 83 + } 84 + 85 + //Handle our focused altar 86 + if(focusAltar !is null) { 87 + if(!focusAltar.valid || focusAltar.owner !is ai.empire || focusAltar.primaryResourceType != altarResource.id) { 88 + @focusAltar = null; 89 + } 90 + } 91 + 92 + //If we aren't covering our entire population, find new planets to make into altars 93 + double coverage = ai.empire.getAttribute(coverAttrib); 94 + double population = ai.empire.TotalPopulation; 95 + 96 + if(coverage >= population || altarBuild !is null) 97 + return; 98 + 99 + bool makeNewAltar = true; 100 + if(focusAltar !is null) { 101 + auto@ foc = development.getFocus(focusAltar); 102 + if(foc !is null && int(foc.obj.level) >= foc.targetLevel) { 103 + foc.targetLevel += 1; 104 + considerTimer = gameTime + 180.0; 105 + makeNewAltar = false; 106 + } 107 + else { 108 + makeNewAltar = gameTime > considerTimer; 109 + } 110 + } 111 + 112 + if(makeNewAltar) { 113 + if(budget.canSpend(BT_Development, 300)) { 114 + //Turn our most suitable planet into an altar 115 + PlanetAI@ bestBuild; 116 + double bestWeight = 0.0; 117 + 118 + for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) { 119 + auto@ plAI = planets.planets[i]; 120 + double w = randomd(0.9, 1.1); 121 + 122 + if(plAI.resources !is null && plAI.resources.length != 0) { 123 + auto@ res = plAI.resources[0].resource; 124 + if(res.level == 0 && !res.limitlessLevel) 125 + w *= 5.0; 126 + if(res.cls !is null) 127 + w *= 0.5; 128 + if(res.level > 0) 129 + w /= pow(2.0, res.level); 130 + } 131 + else { 132 + w *= 100.0; 133 + } 134 + 135 + if(w > bestWeight) { 136 + bestWeight = w; 137 + @bestBuild = plAI; 138 + } 139 + } 140 + 141 + if(bestBuild !is null) { 142 + @altarBuild = planets.requestBuilding(bestBuild, altar, expire=60.0); 143 + considerTimer = gameTime + 120.0; 144 + } 145 + } 146 + } 147 + } 148 +}; 149 + 150 +AIComponent@ createDevout() { 151 + return Devout(); 152 +}
Added scripts/server/empire_ai/weasel/race/Extragalactic.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.race.Race; 3 + 4 +import empire_ai.weasel.Colonization; 5 +import empire_ai.weasel.Construction; 6 +import empire_ai.weasel.Resources; 7 +import empire_ai.weasel.Scouting; 8 +import empire_ai.weasel.Orbitals; 9 +import empire_ai.weasel.Budget; 10 + 11 +from orbitals import getOrbitalModuleID; 12 +from constructions import ConstructionType, getConstructionType; 13 + 14 +class Extragalactic : Race { 15 + Colonization@ colonization; 16 + Construction@ construction; 17 + Scouting@ scouting; 18 + Orbitals@ orbitals; 19 + Resources@ resources; 20 + Budget@ budget; 21 + 22 + array<OrbitalAI@> beacons; 23 + OrbitalAI@ masterBeacon; 24 + 25 + int beaconMod = -1; 26 + 27 + array<ImportData@> imports; 28 + array<const ConstructionType@> beaconBuilds; 29 + 30 + void create() { 31 + @colonization = cast<Colonization>(ai.colonization); 32 + colonization.performColonization = false; 33 + colonization.queueColonization = false; 34 + 35 + @scouting = cast<Scouting>(ai.scouting); 36 + scouting.buildScouts = false; 37 + 38 + @orbitals = cast<Orbitals>(ai.orbitals); 39 + beaconMod = getOrbitalModuleID("Beacon"); 40 + 41 + @construction = cast<Construction>(ai.construction); 42 + @resources = cast<Resources>(ai.resources); 43 + @budget = cast<Budget>(ai.budget); 44 + 45 + beaconBuilds.insertLast(getConstructionType("BeaconHealth")); 46 + beaconBuilds.insertLast(getConstructionType("BeaconWeapons")); 47 + beaconBuilds.insertLast(getConstructionType("BeaconLabor")); 48 + } 49 + 50 + void save(SaveFile& file) override { 51 + uint cnt = beacons.length; 52 + file << cnt; 53 + for(uint i = 0; i < cnt; ++i) 54 + orbitals.saveAI(file, beacons[i]); 55 + orbitals.saveAI(file, masterBeacon); 56 + 57 + cnt = imports.length; 58 + file << cnt; 59 + for(uint i = 0; i < cnt; ++i) 60 + resources.saveImport(file, imports[i]); 61 + } 62 + 63 + void load(SaveFile& file) override { 64 + uint cnt = 0; 65 + file >> cnt; 66 + for(uint i = 0; i < cnt; ++i) { 67 + auto@ b = orbitals.loadAI(file); 68 + if(b !is null && b.obj !is null) 69 + beacons.insertLast(b); 70 + } 71 + @masterBeacon = orbitals.loadAI(file); 72 + 73 + file >> cnt; 74 + for(uint i = 0; i < cnt; ++i) { 75 + auto@ imp = resources.loadImport(file); 76 + if(imp !is null) 77 + imports.insertLast(imp); 78 + } 79 + } 80 + 81 + uint prevBeacons = 0; 82 + void focusTick(double time) { 83 + if(ai.behavior.forbidConstruction) return; 84 + 85 + //Find our beacons 86 + for(uint i = 0, cnt = beacons.length; i < cnt; ++i) { 87 + auto@ b = beacons[i]; 88 + if(b is null || b.obj is null || !b.obj.valid || b.obj.owner !is ai.empire) { 89 + if(b.obj !is null) 90 + resources.killImportsTo(b.obj); 91 + beacons.removeAt(i); 92 + --i; --cnt; 93 + } 94 + } 95 + 96 + for(uint i = 0, cnt = orbitals.orbitals.length; i < cnt; ++i) { 97 + auto@ orb = orbitals.orbitals[i]; 98 + Orbital@ obj = cast<Orbital>(orb.obj); 99 + if(obj !is null && obj.coreModule == uint(beaconMod)) { 100 + if(beacons.find(orb) == -1) 101 + beacons.insertLast(orb); 102 + } 103 + } 104 + 105 + //Find our master beacon 106 + if(masterBeacon !is null) { 107 + Orbital@ obj = cast<Orbital>(masterBeacon.obj); 108 + if(obj is null || !obj.valid || obj.owner !is ai.empire || obj.hasMaster()) 109 + @masterBeacon = null; 110 + } 111 + else { 112 + for(uint i = 0, cnt = beacons.length; i < cnt; ++i) { 113 + auto@ b = beacons[i]; 114 + Orbital@ obj = cast<Orbital>(b.obj); 115 + if(!obj.hasMaster()) { 116 + @masterBeacon = b; 117 + ai.empire.setDefending(obj, true); 118 + break; 119 + } 120 + } 121 + } 122 + 123 + scouting.buildScouts = gameTime > 5.0 * 60.0; 124 + if(prevBeacons < beacons.length && masterBeacon !is null && gameTime > 10.0) { 125 + for(int i = beacons.length-1; i >= int(prevBeacons); --i) { 126 + //Make sure we order a scout at each beacon 127 + if(!scouting.buildScouts) { 128 + BuildFlagshipSourced build(scouting.scoutDesign); 129 + build.moneyType = BT_Military; 130 + @build.buildAt = masterBeacon.obj; 131 + if(beacons[i] !is masterBeacon) 132 + @build.buildFrom = beacons[i].obj; 133 + 134 + construction.build(build, force=true); 135 + } 136 + 137 + //Set the beacon to fill up other stuff 138 + beacons[i].obj.allowFillFrom = true; 139 + } 140 + prevBeacons = beacons.length; 141 + } 142 + 143 + //Handle with importing labor and defense to our master beacon 144 + if(masterBeacon !is null) { 145 + if(imports.length == 0) { 146 + //Request labor and defense at our beacon 147 + { 148 + ResourceSpec spec; 149 + spec.type = RST_Pressure_Type; 150 + spec.pressureType = TR_Labor; 151 + 152 + imports.insertLast(resources.requestResource(masterBeacon.obj, spec)); 153 + } 154 + { 155 + ResourceSpec spec; 156 + spec.type = RST_Pressure_Type; 157 + spec.pressureType = TR_Defense; 158 + 159 + imports.insertLast(resources.requestResource(masterBeacon.obj, spec)); 160 + } 161 + { 162 + ResourceSpec spec; 163 + spec.type = RST_Pressure_Level0; 164 + spec.pressureType = TR_Research; 165 + 166 + imports.insertLast(resources.requestResource(masterBeacon.obj, spec)); 167 + } 168 + } 169 + else { 170 + //When our requests are met, make more requests! 171 + for(uint i = 0, cnt = imports.length; i < cnt; ++i) { 172 + if(imports[i].beingMet || imports[i].obj !is masterBeacon.obj) { 173 + ResourceSpec spec; 174 + spec = imports[i].spec; 175 + @imports[i] = resources.requestResource(masterBeacon.obj, spec); 176 + } 177 + } 178 + } 179 + 180 + //Build stuff on our beacon if we have enough stuff 181 + if(budget.canSpend(BT_Development, 300)) { 182 + uint offset = randomi(0, beaconBuilds.length-1); 183 + for(uint i = 0, cnt = beaconBuilds.length; i < cnt; ++i) { 184 + uint ind = (i+offset) % cnt; 185 + auto@ type = beaconBuilds[ind]; 186 + if(type is null) 187 + continue; 188 + 189 + if(type.canBuild(masterBeacon.obj, ignoreCost=false)) { 190 + masterBeacon.obj.buildConstruction(type.id); 191 + break; 192 + } 193 + } 194 + } 195 + } 196 + } 197 +}; 198 + 199 +AIComponent@ createExtragalactic() { 200 + return Extragalactic(); 201 +}
Added scripts/server/empire_ai/weasel/race/Linked.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.race.Race; 3 + 4 +import empire_ai.weasel.Movement; 5 +import empire_ai.weasel.Military; 6 +import empire_ai.weasel.Construction; 7 +import empire_ai.weasel.Designs; 8 +import empire_ai.weasel.Development; 9 +import empire_ai.weasel.Systems; 10 +import empire_ai.weasel.Budget; 11 + 12 +from orbitals import getOrbitalModuleID; 13 + 14 +const double MAINFRAME_MIN_DISTANCE_STAGE = 15000; 15 +const double MAINFRAME_MIN_DISTANCE_DEVELOP = 20000; 16 +const double MAINFRAME_MIN_TIMER = 3.0 * 60.0; 17 +const int MAINFRAME_BUILD_MOVE_HOPS = 5; 18 + 19 +class LinkRegion : Savable { 20 + Region@ region; 21 + Object@ obj; 22 + bool arrived = false; 23 + vec3d destination; 24 + 25 + void save(SaveFile& file) { 26 + file << region; 27 + file << obj; 28 + file << arrived; 29 + file << destination; 30 + } 31 + 32 + void load(SaveFile& file) { 33 + file >> region; 34 + file >> obj; 35 + file >> arrived; 36 + file >> destination; 37 + } 38 +}; 39 + 40 +class Linked : Race { 41 + Military@ military; 42 + Designs@ designs; 43 + Construction@ construction; 44 + Development@ development; 45 + Systems@ systems; 46 + Budget@ budget; 47 + 48 + array<LinkRegion@> tracked; 49 + array<Object@> unassigned; 50 + 51 + BuildOrbital@ buildMainframe; 52 + int mainframeId = -1; 53 + 54 + double nextBuildTry = 15.0 * 60.0; 55 + 56 + void create() override { 57 + @military = cast<Military>(ai.military); 58 + @designs = cast<Designs>(ai.designs); 59 + @construction = cast<Construction>(ai.construction); 60 + @development = cast<Development>(ai.development); 61 + @systems = cast<Systems>(ai.systems); 62 + @budget = cast<Budget>(ai.budget); 63 + 64 + mainframeId = getOrbitalModuleID("Mainframe"); 65 + } 66 + 67 + void save(SaveFile& file) override { 68 + construction.saveConstruction(file, buildMainframe); 69 + file << nextBuildTry; 70 + 71 + uint cnt = tracked.length; 72 + file << cnt; 73 + for(uint i = 0; i < cnt; ++i) 74 + file << tracked[i]; 75 + 76 + cnt = unassigned.length; 77 + file << cnt; 78 + for(uint i = 0; i < cnt; ++i) 79 + file << unassigned[i]; 80 + } 81 + 82 + void load(SaveFile& file) override { 83 + @buildMainframe = cast<BuildOrbital>(construction.loadConstruction(file)); 84 + file >> nextBuildTry; 85 + 86 + uint cnt = 0; 87 + file >> cnt; 88 + for(uint i = 0; i < cnt; ++i) { 89 + LinkRegion gt; 90 + file >> gt; 91 + tracked.insertLast(gt); 92 + } 93 + 94 + file >> cnt; 95 + for(uint i = 0; i < cnt; ++i) { 96 + Object@ obj; 97 + file >> obj; 98 + if(obj !is null) 99 + unassigned.insertLast(obj); 100 + } 101 + } 102 + 103 + LinkRegion@ get(Region@ reg) { 104 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 105 + if(tracked[i].region is reg) 106 + return tracked[i]; 107 + } 108 + return null; 109 + } 110 + 111 + void remove(LinkRegion@ gt) { 112 + if(gt.obj !is null && gt.obj.valid && gt.obj.owner is ai.empire) 113 + unassigned.insertLast(gt.obj); 114 + tracked.remove(gt); 115 + } 116 + 117 + Object@ getClosestMainframe(const vec3d& position) { 118 + Object@ closest; 119 + double minDist = INFINITY; 120 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 121 + Object@ obj = tracked[i].obj; 122 + if(obj is null) 123 + continue; 124 + if(!tracked[i].arrived) 125 + continue; 126 + double d = obj.position.distanceTo(position); 127 + if(d < minDist) { 128 + minDist = d; 129 + @closest = obj; 130 + } 131 + } 132 + return closest; 133 + } 134 + 135 + LinkRegion@ getClosestLinkRegion(const vec3d& position) { 136 + LinkRegion@ closest; 137 + double minDist = INFINITY; 138 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 139 + double d = tracked[i].region.position.distanceTo(position); 140 + if(d < minDist) { 141 + minDist = d; 142 + @closest = tracked[i]; 143 + } 144 + } 145 + return closest; 146 + } 147 + 148 + void assignTo(LinkRegion@ gt, Object@ closest) { 149 + unassigned.remove(closest); 150 + @gt.obj = closest; 151 + gt.arrived = false; 152 + 153 + if(closest.region is gt.region) 154 + gt.arrived = true; 155 + if(!gt.arrived) { 156 + gt.destination = military.getStationPosition(gt.region); 157 + closest.addMoveOrder(gt.destination); 158 + } 159 + } 160 + 161 + bool trackingMainframe(Object@ obj) { 162 + for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) { 163 + if(unassigned[i] is obj) 164 + return true; 165 + } 166 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 167 + if(tracked[i].obj is obj) 168 + return true; 169 + } 170 + return false; 171 + } 172 + 173 + bool shouldHaveMainframe(Region@ reg, bool always = false) { 174 + if(military.getBase(reg) !is null) 175 + return true; 176 + if(development.isDevelopingIn(reg)) 177 + return true; 178 + return false; 179 + } 180 + 181 + void focusTick(double time) override { 182 + if(ai.behavior.forbidConstruction) return; 183 + 184 + //Manage unassigned mainframes list 185 + for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) { 186 + Object@ obj = unassigned[i]; 187 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 188 + unassigned.removeAt(i); 189 + --i; --cnt; 190 + } 191 + } 192 + 193 + //Detect new gates 194 + auto@ data = ai.empire.getOrbitals(); 195 + Object@ obj; 196 + while(receive(data, obj)) { 197 + if(obj is null) 198 + continue; 199 + Orbital@ orb = cast<Orbital>(obj); 200 + if(orb is null || orb.coreModule != uint(mainframeId)) 201 + continue; 202 + if(!trackingMainframe(obj)) 203 + unassigned.insertLast(obj); 204 + } 205 + 206 + //Update existing gates for staging bases 207 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 208 + auto@ gt = tracked[i]; 209 + bool checkAlways = false; 210 + if(gt.obj !is null) { 211 + if(!gt.obj.valid || gt.obj.owner !is ai.empire || (gt.arrived && gt.obj.region !is gt.region)) { 212 + @gt.obj = null; 213 + gt.arrived = false; 214 + checkAlways = true; 215 + } 216 + else if(!gt.arrived && !gt.obj.hasOrders) { 217 + if(gt.destination.distanceTo(gt.obj.position) < 10.0) 218 + gt.arrived = true; 219 + else 220 + gt.obj.addMoveOrder(gt.destination); 221 + } 222 + } 223 + if(!shouldHaveMainframe(gt.region, checkAlways)) { 224 + remove(tracked[i]); 225 + --i; --cnt; 226 + } 227 + } 228 + 229 + //Detect new staging bases to build mainframes at 230 + for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) { 231 + auto@ base = military.stagingBases[i]; 232 + if(base.occupiedTime < MAINFRAME_MIN_TIMER) 233 + continue; 234 + 235 + if(get(base.region) is null) { 236 + LinkRegion@ closest = getClosestLinkRegion(base.region.position); 237 + if(closest !is null && closest.region.position.distanceTo(base.region.position) < MAINFRAME_MIN_DISTANCE_STAGE) 238 + continue; 239 + 240 + LinkRegion gt; 241 + @gt.region = base.region; 242 + tracked.insertLast(gt); 243 + break; 244 + } 245 + } 246 + 247 + //Detect new important planets to build mainframes at 248 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) { 249 + auto@ focus = development.focuses[i]; 250 + Region@ reg = focus.obj.region; 251 + if(reg is null) 252 + continue; 253 + 254 + if(get(reg) is null) { 255 + LinkRegion@ closest = getClosestLinkRegion(reg.position); 256 + if(closest !is null && closest.region.position.distanceTo(reg.position) < MAINFRAME_MIN_DISTANCE_DEVELOP) 257 + continue; 258 + 259 + LinkRegion gt; 260 + @gt.region = reg; 261 + tracked.insertLast(gt); 262 + break; 263 + } 264 + } 265 + 266 + //See if we should build a new mainframe 267 + if(buildMainframe !is null) { 268 + if(buildMainframe.completed) { 269 + @buildMainframe = null; 270 + nextBuildTry = gameTime + 60.0; 271 + } 272 + } 273 + for(uint i = 0, cnt = tracked.length; i < cnt; ++i) { 274 + auto@ gt = tracked[i]; 275 + if(gt.obj is null) { 276 + Object@ closest; 277 + double closestDist = INFINITY; 278 + for(uint n = 0, ncnt = unassigned.length; n < ncnt; ++n) { 279 + Object@ obj = unassigned[n]; 280 + if(obj.region is gt.region) { 281 + @closest = obj; 282 + break; 283 + } 284 + if(!obj.hasMover) 285 + continue; 286 + if(buildMainframe is null && gameTime > nextBuildTry) { 287 + double d = obj.position.distanceTo(gt.region.position); 288 + if(d < closestDist) { 289 + closestDist = d; 290 + @closest = obj; 291 + } 292 + } 293 + } 294 + 295 + if(closest !is null) { 296 + if(log) 297 + ai.print("Assign mainframe to => "+gt.region.name, closest.region); 298 + assignTo(gt, closest); 299 + } else if(buildMainframe is null && gameTime > nextBuildTry) { 300 + if(log) 301 + ai.print("Build mainframe for this system", gt.region); 302 + 303 + bool buildLocal = true; 304 + auto@ factory = construction.primaryFactory; 305 + if(factory !is null) { 306 + Region@ factRegion = factory.obj.region; 307 + if(factRegion !is null && systems.hopDistance(gt.region, factRegion) < MAINFRAME_BUILD_MOVE_HOPS) 308 + buildLocal = false; 309 + } 310 + 311 + if(buildLocal) 312 + @buildMainframe = construction.buildLocalOrbital(getOrbitalModule(mainframeId)); 313 + else 314 + @buildMainframe = construction.buildOrbital(getOrbitalModule(mainframeId), military.getStationPosition(gt.region)); 315 + } 316 + } 317 + } 318 + } 319 +}; 320 + 321 +AIComponent@ createLinked() { 322 + return Linked(); 323 +}
Added scripts/server/empire_ai/weasel/race/Mechanoid.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.race.Race; 3 + 4 +import empire_ai.weasel.Resources; 5 +import empire_ai.weasel.Colonization; 6 +import empire_ai.weasel.Construction; 7 +import empire_ai.weasel.Movement; 8 +import empire_ai.weasel.Planets; 9 +import empire_ai.weasel.Budget; 10 + 11 +import resources; 12 +import abilities; 13 +import planet_levels; 14 +from constructions import getConstructionType, ConstructionType; 15 +from abilities import getAbilityID; 16 +import oddity_navigation; 17 + 18 +const double MAX_POP_BUILDTIME = 3.0 * 60.0; 19 + 20 +class Mechanoid : Race, RaceResources, RaceColonization { 21 + Colonization@ colonization; 22 + Construction@ construction; 23 + Movement@ movement; 24 + Budget@ budget; 25 + Planets@ planets; 26 + 27 + const ResourceType@ unobtanium; 28 + const ResourceType@ crystals; 29 + int unobtaniumAbl = -1; 30 + 31 + const ResourceClass@ foodClass; 32 + const ResourceClass@ waterClass; 33 + const ResourceClass@ scalableClass; 34 + const ConstructionType@ buildPop; 35 + 36 + int colonizeAbl = -1; 37 + 38 + array<Planet@> popRequests; 39 + array<Planet@> popSources; 40 + array<Planet@> popFactories; 41 + 42 + void create() { 43 + @colonization = cast<Colonization>(ai.colonization); 44 + @construction = cast<Construction>(ai.construction); 45 + @movement = cast<Movement>(ai.movement); 46 + @planets = cast<Planets>(ai.planets); 47 + @budget = cast<Budget>(ai.budget); 48 + 49 + @ai.defs.Shipyard = null; 50 + 51 + @crystals = getResource("FTL"); 52 + @unobtanium = getResource("Unobtanium"); 53 + unobtaniumAbl = getAbilityID("UnobtaniumMorph"); 54 + 55 + @foodClass = getResourceClass("Food"); 56 + @waterClass = getResourceClass("WaterType"); 57 + @scalableClass = getResourceClass("Scalable"); 58 + 59 + colonizeAbl = getAbilityID("MechanoidColonize"); 60 + colonization.performColonization = false; 61 + 62 + @buildPop = getConstructionType("MechanoidPopulation"); 63 + } 64 + 65 + void start() { 66 + //Oh yes please can we have some ftl crystals sir 67 + if(crystals !is null) { 68 + ResourceSpec spec; 69 + spec.type = RST_Specific; 70 + @spec.resource = crystals; 71 + spec.isLevelRequirement = false; 72 + spec.isForImport = false; 73 + 74 + colonization.queueColonize(spec); 75 + } 76 + } 77 + 78 + void levelRequirements(Object& obj, int targetLevel, array<ResourceSpec@>& specs) override { 79 + //Remove all food and water resources 80 + if(obj.levelChain != baseLevelChain.id) 81 + return; 82 + for(int i = specs.length-1; i >= 0; --i) { 83 + auto@ spec = specs[i]; 84 + if(spec.type == RST_Class && (spec.cls is foodClass || spec.cls is waterClass)) 85 + specs.removeAt(i); 86 + } 87 + } 88 + 89 + double transferCost(double dist) { 90 + return 20 + dist * 0.002; 91 + } 92 + 93 + bool orderColonization(ColonizeData& data, Planet@ sourcePlanet) { 94 + return false; 95 + } 96 + 97 + double getGenericUsefulness(const ResourceType@ type) override { 98 + if(type.cls is foodClass || type.cls is waterClass) 99 + return 0.00001; 100 + if(type.level == 1) 101 + return 100.0; 102 + return 1.0; 103 + } 104 + 105 + bool canBuildPopulation(Planet& pl, double factor=1.0) { 106 + if(buildPop is null) 107 + return false; 108 + if(!buildPop.canBuild(pl, ignoreCost=true)) 109 + return false; 110 + auto@ primFact = construction.primaryFactory; 111 + if(primFact !is null && pl is primFact.obj) 112 + return true; 113 + 114 + double laborCost = buildPop.getLaborCost(pl); 115 + double laborIncome = pl.laborIncome; 116 + return laborCost < laborIncome * MAX_POP_BUILDTIME * factor; 117 + } 118 + 119 + bool requiresPopulation(Planet& pl, double mod = 0.0) { 120 + double curPop = pl.population + mod; 121 + double maxPop = pl.maxPopulation; 122 + return curPop < maxPop; 123 + } 124 + 125 + bool canSendPopulation(Planet& pl, double mod = 0.0) { 126 + double curPop = pl.population + mod; 127 + double maxPop = pl.maxPopulation; 128 + if(curPop >= maxPop + 1) 129 + return true; 130 + //auto@ primFact = construction.primaryFactory; 131 + //if(primFact !is null && pl is primFact.obj) { 132 + // uint minFacts = 2; 133 + // if(popFactories.find(pl) == -1) 134 + // minFacts -= 1; 135 + // if(popFactories.length >= minFacts) 136 + // return false; 137 + //} 138 + //if(canBuildPopulation(pl)) { 139 + // if(curPop >= maxPop) 140 + // return true; 141 + //} 142 + return false; 143 + } 144 + 145 + uint chkInd = 0; 146 + array<Planet@> availSources; 147 + void focusTick(double time) override { 148 + if(ai.behavior.forbidColonization) return; 149 + 150 + //Check existing lists 151 + for(uint i = 0, cnt = popFactories.length; i < cnt; ++i) { 152 + auto@ obj = popFactories[i]; 153 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 154 + popFactories.removeAt(i); 155 + --i; --cnt; 156 + continue; 157 + } 158 + if(!canBuildPopulation(popFactories[i])) { 159 + popFactories.removeAt(i); 160 + --i; --cnt; 161 + continue; 162 + } 163 + } 164 + 165 + for(uint i = 0, cnt = popSources.length; i < cnt; ++i) { 166 + auto@ obj = popSources[i]; 167 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 168 + popSources.removeAt(i); 169 + --i; --cnt; 170 + continue; 171 + } 172 + if(!canSendPopulation(popSources[i])) { 173 + popSources.removeAt(i); 174 + --i; --cnt; 175 + continue; 176 + } 177 + } 178 + 179 + for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) { 180 + auto@ obj = popRequests[i]; 181 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 182 + popRequests.removeAt(i); 183 + --i; --cnt; 184 + continue; 185 + } 186 + if(!requiresPopulation(popRequests[i])) { 187 + popRequests.removeAt(i); 188 + --i; --cnt; 189 + continue; 190 + } 191 + } 192 + 193 + //Find new planets to add to our lists 194 + bool checkMorph = false; 195 + Planet@ hw = ai.empire.Homeworld; 196 + if(hw !is null && hw.valid && hw.owner is ai.empire && unobtanium !is null) { 197 + if(hw.primaryResourceType == unobtanium.id) 198 + checkMorph = true; 199 + } 200 + 201 + uint plCnt = planets.planets.length; 202 + for(uint n = 0, cnt = min(15, plCnt); n < cnt; ++n) { 203 + chkInd = (chkInd+1) % plCnt; 204 + auto@ plAI = planets.planets[chkInd]; 205 + 206 + //Find planets that can build population reliably 207 + if(canBuildPopulation(plAI.obj)) { 208 + if(popFactories.find(plAI.obj) == -1) 209 + popFactories.insertLast(plAI.obj); 210 + } 211 + 212 + //Find planets that need population 213 + if(requiresPopulation(plAI.obj)) { 214 + if(popRequests.find(plAI.obj) == -1) 215 + popRequests.insertLast(plAI.obj); 216 + } 217 + 218 + //Find planets that have extra population 219 + if(canSendPopulation(plAI.obj)) { 220 + if(popSources.find(plAI.obj) == -1) 221 + popSources.insertLast(plAI.obj); 222 + } 223 + 224 + if(plAI.resources !is null && plAI.resources.length != 0) { 225 + auto@ res = plAI.resources[0]; 226 + 227 + //Get rid of food and water we don't need 228 + if(res.resource.cls is foodClass || res.resource.cls is waterClass) { 229 + if(res.request is null && !ai.behavior.forbidScuttle) { 230 + Region@ reg = res.obj.region; 231 + if(reg !is null && reg.getPlanetCount(ai.empire) >= 2) { 232 + plAI.obj.abandon(); 233 + } 234 + } 235 + } 236 + 237 + //See if we have anything useful to morph our homeworld too 238 + if(checkMorph) { 239 + bool morph = false; 240 + if(res.resource is crystals) 241 + morph = true; 242 + else if(res.resource.level >= 2 && res.resource.tilePressure[TR_Labor] >= 5) 243 + morph = true; 244 + else if(res.resource.level >= 3 && res.resource.totalPressure > 10) 245 + morph = true; 246 + else if(res.resource.cls is scalableClass && gameTime > 30.0 * 60.0) 247 + morph = true; 248 + else if(res.resource.level >= 2 && res.resource.totalPressure >= 5 && gameTime > 60.0 * 60.0) 249 + morph = true; 250 + 251 + if(morph) { 252 + if(log) 253 + ai.print("Morph homeworld to "+res.resource.name+" from "+res.obj.name, hw); 254 + hw.activateAbilityTypeFor(ai.empire, unobtaniumAbl, plAI.obj); 255 + } 256 + } 257 + } 258 + } 259 + 260 + //See if we can find something to send population to 261 + availSources = popSources; 262 + 263 + for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) { 264 + Planet@ dest = popRequests[i]; 265 + if(canBuildPopulation(dest, factor=(availSources.length == 0 ? 2.5 : 1.5))) { 266 + Factory@ f = construction.get(dest); 267 + if(f !is null) { 268 + if(f.active is null) { 269 + auto@ build = construction.buildConstruction(buildPop); 270 + construction.buildNow(build, f); 271 + if(log) 272 + ai.print("Build population", f.obj); 273 + continue; 274 + } 275 + else { 276 + auto@ cons = cast<BuildConstruction>(f.active); 277 + if(cons !is null && cons.consType is buildPop) { 278 + if(double(dest.maxPopulation) <= dest.population + 0.0) 279 + continue; 280 + } 281 + } 282 + } 283 + } 284 + transferBest(dest, availSources); 285 + } 286 + 287 + if(availSources.length != 0) { 288 + //If we have any population left, do stuff from our colonization queue 289 + for(uint i = 0, cnt = colonization.awaitingSource.length; i < cnt && availSources.length != 0; ++i) { 290 + Planet@ dest = colonization.awaitingSource[i].target; 291 + Planet@ source = transferBest(dest, availSources); 292 + if(source !is null) { 293 + @colonization.awaitingSource[i].colonizeFrom = source; 294 + colonization.awaitingSource.removeAt(i); 295 + --i; --cnt; 296 + } 297 + } 298 + } 299 + 300 + //Build population on idle planets 301 + if(budget.canSpend(BT_Development, 100)) { 302 + for(int i = popFactories.length-1; i >= 0; --i) { 303 + Planet@ dest = popFactories[i]; 304 + Factory@ f = construction.get(dest); 305 + if(f is null || f.active !is null) 306 + continue; 307 + if(dest.population >= double(dest.maxPopulation) + 1.0) 308 + continue; 309 + 310 + auto@ build = construction.buildConstruction(buildPop); 311 + construction.buildNow(build, f); 312 + if(log) 313 + ai.print("Build population for idle", f.obj); 314 + break; 315 + } 316 + } 317 + } 318 + 319 + Planet@ transferBest(Planet& dest, array<Planet@>& availSources) { 320 + //Find closest source 321 + Planet@ bestSource; 322 + double bestDist = INFINITY; 323 + for(uint j = 0, jcnt = availSources.length; j < jcnt; ++j) { 324 + double d = movement.getPathDistance(availSources[j].position, dest.position); 325 + if(d < bestDist) { 326 + bestDist = d; 327 + @bestSource = availSources[j]; 328 + } 329 + } 330 + 331 + if(bestSource !is null) { 332 + double cost = transferCost(bestDist); 333 + if(cost <= ai.empire.FTLStored) { 334 + if(log) 335 + ai.print("Transfering population to "+dest.name, bestSource); 336 + availSources.remove(bestSource); 337 + bestSource.activateAbilityTypeFor(ai.empire, colonizeAbl, dest); 338 + return bestSource; 339 + } 340 + } 341 + return null; 342 + } 343 + 344 + void tick(double time) override { 345 + } 346 +}; 347 + 348 +AIComponent@ createMechanoid() { 349 + return Mechanoid(); 350 +}
Added scripts/server/empire_ai/weasel/race/Race.as.
1 +import empire_ai.weasel.WeaselAI; 2 + 3 +class Race : AIComponent { 4 +};
Added scripts/server/empire_ai/weasel/race/StarChildren.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.race.Race; 3 + 4 +import empire_ai.weasel.Colonization; 5 +import empire_ai.weasel.Resources; 6 +import empire_ai.weasel.Construction; 7 +import empire_ai.weasel.Development; 8 +import empire_ai.weasel.Fleets; 9 +import empire_ai.weasel.Movement; 10 +import empire_ai.weasel.Planets; 11 +import empire_ai.weasel.Designs; 12 + 13 +import oddity_navigation; 14 +from abilities import getAbilityID; 15 +from statuses import getStatusID; 16 + 17 +class HabitatMission : Mission { 18 + Planet@ target; 19 + MoveOrder@ move; 20 + double timer = 0.0; 21 + 22 + void save(Fleets& fleets, SaveFile& file) override { 23 + file << target; 24 + file << timer; 25 + fleets.movement.saveMoveOrder(file, move); 26 + } 27 + 28 + void load(Fleets& fleets, SaveFile& file) override { 29 + file >> target; 30 + file >> timer; 31 + @move = fleets.movement.loadMoveOrder(file); 32 + } 33 + 34 + void start(AI& ai, FleetAI& fleet) override { 35 + uint prior = MP_Normal; 36 + if(gameTime < 30.0 * 60.0) 37 + prior = MP_Critical; 38 + @move = cast<Movement>(ai.movement).move(fleet.obj, target, prior); 39 + } 40 + 41 + void tick(AI& ai, FleetAI& fleet, double time) override { 42 + if(move !is null) { 43 + if(move.failed) { 44 + canceled = true; 45 + return; 46 + } 47 + if(move.completed) { 48 + int ablId = cast<StarChildren>(ai.race).habitatAbl; 49 + fleet.obj.activateAbilityTypeFor(ai.empire, ablId, target); 50 + 51 + @move = null; 52 + timer = gameTime + 60.0; 53 + } 54 + } 55 + else { 56 + if(target is null || !target.valid || target.quarantined 57 + || (target.owner !is ai.empire && target.owner.valid) 58 + || target.inCombat) { 59 + canceled = true; 60 + return; 61 + } 62 + 63 + double maxPop = max(double(target.maxPopulation), double(getPlanetLevel(target, target.primaryResourceLevel).population)); 64 + double curPop = target.population; 65 + if(curPop >= maxPop) { 66 + completed = true; 67 + return; 68 + } 69 + 70 + if(gameTime >= timer) { 71 + int popStatus = cast<StarChildren>(ai.race).popStatus; 72 + if(target.getStatusStackCountAny(popStatus) >= 5) { 73 + canceled = true; 74 + return; 75 + } 76 + } 77 + } 78 + } 79 +}; 80 + 81 +class LaborMission : Mission { 82 + Planet@ target; 83 + MoveOrder@ move; 84 + double timer = 0.0; 85 + 86 + void save(Fleets& fleets, SaveFile& file) override { 87 + file << target; 88 + file << timer; 89 + fleets.movement.saveMoveOrder(file, move); 90 + } 91 + 92 + void load(Fleets& fleets, SaveFile& file) override { 93 + file >> target; 94 + file >> timer; 95 + @move = fleets.movement.loadMoveOrder(file); 96 + } 97 + 98 + void start(AI& ai, FleetAI& fleet) override { 99 + @move = cast<Movement>(ai.movement).move(fleet.obj, target); 100 + } 101 + 102 + void tick(AI& ai, FleetAI& fleet, double time) override { 103 + if(move !is null) { 104 + if(move.failed) { 105 + canceled = true; 106 + return; 107 + } 108 + if(move.completed) { 109 + @move = null; 110 + timer = gameTime + 10.0; 111 + } 112 + } 113 + else { 114 + if(target is null || !target.valid || target.quarantined 115 + || target.owner !is ai.empire) { 116 + canceled = true; 117 + return; 118 + } 119 + 120 + if(gameTime >= timer) { 121 + int popStatus = cast<StarChildren>(ai.race).popStatus; 122 + timer = gameTime + 10.0; 123 + if(target.getStatusStackCountAny(popStatus) >= 10) { 124 + completed = true; 125 + return; 126 + } 127 + } 128 + } 129 + } 130 +}; 131 + 132 +class StarChildren : Race { 133 + Colonization@ colonization; 134 + Construction@ construction; 135 + Development@ development; 136 + Movement@ movement; 137 + Planets@ planets; 138 + Fleets@ fleets; 139 + Designs@ designs; 140 + 141 + DesignTarget@ mothershipDesign; 142 + double idleSince = 0; 143 + 144 + array<FleetAI@> motherships; 145 + 146 + int habitatAbl = -1; 147 + int popStatus = -1; 148 + 149 + array<Planet@> popRequests; 150 + array<Planet@> laborPlanets; 151 + 152 + BuildFlagship@ mcBuild; 153 + BuildOrbital@ yardBuild; 154 + 155 + void save(SaveFile& file) override { 156 + designs.saveDesign(file, mothershipDesign); 157 + file << idleSince; 158 + construction.saveConstruction(file, mcBuild); 159 + construction.saveConstruction(file, yardBuild); 160 + 161 + uint cnt = motherships.length; 162 + file << cnt; 163 + for(uint i = 0; i < cnt; ++i) 164 + fleets.saveAI(file, motherships[i]); 165 + } 166 + 167 + void load(SaveFile& file) override { 168 + @mothershipDesign = designs.loadDesign(file); 169 + file >> idleSince; 170 + @mcBuild = cast<BuildFlagship>(construction.loadConstruction(file)); 171 + @yardBuild = cast<BuildOrbital>(construction.loadConstruction(file)); 172 + 173 + uint cnt = 0; 174 + file >> cnt; 175 + for(uint i = 0; i < cnt; ++i) { 176 + auto@ flAI = fleets.loadAI(file); 177 + if(flAI !is null) 178 + motherships.insertLast(flAI); 179 + } 180 + } 181 + 182 + void create() override { 183 + @colonization = cast<Colonization>(ai.colonization); 184 + colonization.performColonization = false; 185 + 186 + @development = cast<Development>(ai.development); 187 + development.managePlanetPressure = false; 188 + development.buildBuildings = false; 189 + 190 + @fleets = cast<Fleets>(ai.fleets); 191 + @construction = cast<Construction>(ai.construction); 192 + @planets = cast<Planets>(ai.planets); 193 + @designs = cast<Designs>(ai.designs); 194 + @movement = cast<Movement>(ai.movement); 195 + 196 + @ai.defs.Factory = null; 197 + @ai.defs.LaborStorage = null; 198 + 199 + habitatAbl = getAbilityID("MothershipColonize"); 200 + popStatus = getStatusID("MothershipPopulation"); 201 + } 202 + 203 + void start() override { 204 + //Get the Tier 1 in our home system 205 + { 206 + ResourceSpec spec; 207 + spec.type = RST_Level_Specific; 208 + spec.level = 1; 209 + spec.isForImport = false; 210 + spec.isLevelRequirement = false; 211 + 212 + colonization.queueColonize(spec); 213 + } 214 + 215 + //Then find a Tier 2 to get 216 + { 217 + ResourceSpec spec; 218 + spec.type = RST_Level_Specific; 219 + spec.level = 2; 220 + spec.isForImport = false; 221 + spec.isLevelRequirement = false; 222 + 223 + colonization.queueColonize(spec); 224 + } 225 + 226 + //Design a mothership 227 + @mothershipDesign = designs.design(DP_Mothership, 500); 228 + mothershipDesign.targetMaintenance = 300; 229 + mothershipDesign.targetLaborCost = 110; 230 + mothershipDesign.customName = "Mothership"; 231 + } 232 + 233 + bool requiresPopulation(Planet& target) { 234 + double maxPop = max(double(target.maxPopulation), double(getPlanetLevel(target, target.primaryResourceLevel).population)); 235 + double curPop = target.population; 236 + return curPop < maxPop; 237 + } 238 + 239 + uint chkInd = 0; 240 + void focusTick(double time) override { 241 + if(ai.behavior.forbidColonization) return; 242 + 243 + //Detect motherships 244 + for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) { 245 + auto@ flAI = fleets.fleets[i]; 246 + if(flAI.fleetClass != FC_Mothership) 247 + continue; 248 + 249 + if(motherships.find(flAI) == -1) { 250 + //Add to our tracking list 251 + flAI.obj.autoFillSupports = false; 252 + flAI.obj.allowFillFrom = true; 253 + motherships.insertLast(flAI); 254 + 255 + //Add as a factory 256 + construction.registerFactory(flAI.obj); 257 + } 258 + } 259 + 260 + for(uint i = 0, cnt = motherships.length; i < cnt; ++i) { 261 + Object@ obj = motherships[i].obj; 262 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 263 + motherships.removeAt(i); 264 + --i; --cnt; 265 + } 266 + } 267 + 268 + //Detect planets that require more population 269 + for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) { 270 + auto@ obj = popRequests[i]; 271 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 272 + popRequests.removeAt(i); 273 + --i; --cnt; 274 + continue; 275 + } 276 + if(!requiresPopulation(obj)) { 277 + popRequests.removeAt(i); 278 + --i; --cnt; 279 + continue; 280 + } 281 + } 282 + 283 + for(uint i = 0, cnt = laborPlanets.length; i < cnt; ++i) { 284 + auto@ obj = laborPlanets[i]; 285 + if(obj is null || !obj.valid || obj.owner !is ai.empire) { 286 + laborPlanets.removeAt(i); 287 + --i; --cnt; 288 + continue; 289 + } 290 + if(obj.laborIncome < 3.0/60.0) { 291 + laborPlanets.removeAt(i); 292 + --i; --cnt; 293 + continue; 294 + } 295 + } 296 + 297 + uint plCnt = planets.planets.length; 298 + for(uint n = 0, cnt = min(15, plCnt); n < cnt; ++n) { 299 + chkInd = (chkInd+1) % plCnt; 300 + auto@ plAI = planets.planets[chkInd]; 301 + 302 + //Find planets that need population 303 + if(requiresPopulation(plAI.obj)) { 304 + if(popRequests.find(plAI.obj) == -1) 305 + popRequests.insertLast(plAI.obj); 306 + } 307 + 308 + //Find planets that have labor 309 + if(plAI.obj.laborIncome >= 3.0/60.0) { 310 + if(laborPlanets.find(plAI.obj) == -1) 311 + laborPlanets.insertLast(plAI.obj); 312 + } 313 + } 314 + 315 + //Send motherships to do colonization 316 + uint totalCount = popRequests.length + colonization.awaitingSource.length; 317 + uint motherCount = idleMothershipCount(); 318 + 319 + /*if(motherCount > totalCount) {*/ 320 + for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) { 321 + Planet@ dest = popRequests[i]; 322 + if(isColonizing(dest)) 323 + continue; 324 + if(dest.inCombat) 325 + continue; 326 + 327 + colonizeBest(dest); 328 + } 329 + 330 + for(uint i = 0, cnt = colonization.awaitingSource.length; i < cnt; ++i) { 331 + Planet@ dest = colonization.awaitingSource[i].target; 332 + if(isColonizing(dest)) 333 + continue; 334 + 335 + colonizeBest(dest); 336 + } 337 + /*}*/ 338 + /*else {*/ 339 + /* for(uint i = 0, cnt = motherships.length; i < cnt; ++i) {*/ 340 + /* auto@ flAI = motherships[i];*/ 341 + /* if(flAI.mission !is null)*/ 342 + /* continue;*/ 343 + /* if(isBuildingWithLabor(flAI))*/ 344 + /* continue;*/ 345 + 346 + /* colonizeBest(flAI);*/ 347 + /* }*/ 348 + /*}*/ 349 + 350 + if(totalCount != 0) 351 + idleSince = gameTime; 352 + 353 + //See if we should build new motherships 354 + uint haveMC = motherships.length; 355 + uint wantMC = 1; 356 + if(gameTime > 20.0 * 60.0) 357 + wantMC += 1; 358 + wantMC = max(wantMC, min(laborPlanets.length, uint(gameTime/(30.0*60.0)))); 359 + 360 + if(mcBuild !is null && mcBuild.completed) 361 + @mcBuild = null; 362 + if(wantMC > haveMC && mcBuild is null) 363 + @mcBuild = construction.buildFlagship(mothershipDesign, force=true); 364 + 365 + if(yardBuild is null && haveMC > 0 && gameTime > 60 && gameTime < 180 && ai.defs.Shipyard !is null) { 366 + Region@ reg = motherships[0].obj.region; 367 + if(reg !is null) { 368 + vec3d pos = reg.position; 369 + vec2d offset = random2d(reg.radius * 0.4, reg.radius * 0.8); 370 + pos.x += offset.x; 371 + pos.z += offset.y; 372 + 373 + @yardBuild = construction.buildOrbital(ai.defs.Shipyard, pos); 374 + } 375 + } 376 + 377 + if(motherships.length == 1) 378 + @colonization.colonizeWeightObj = motherships[0].obj; 379 + else 380 + @colonization.colonizeWeightObj = null; 381 + 382 + //Idle motherships should be sent to go collect labor from labor planets 383 + if(laborPlanets.length != 0) { 384 + for(uint i = 0, cnt = motherships.length; i < cnt; ++i) { 385 + auto@ flAI = motherships[i]; 386 + if(flAI.mission !is null) 387 + continue; 388 + if(isAtLaborPlanet(flAI)) 389 + continue; 390 + if(i == 0 && idleSince < gameTime-60.0) 391 + continue; 392 + 393 + double bestDist = INFINITY; 394 + Planet@ best; 395 + for(uint n = 0, ncnt = laborPlanets.length; n < ncnt; ++n) { 396 + Planet@ check = laborPlanets[n]; 397 + if(hasMothershipAt(check)) 398 + continue; 399 + 400 + double d = movement.getPathDistance(flAI.obj.position, check.position); 401 + if(d < bestDist) { 402 + @best = check; 403 + bestDist = d; 404 + } 405 + } 406 + 407 + if(best !is null) { 408 + LaborMission miss; 409 + @miss.target = best; 410 + 411 + fleets.performMission(flAI, miss); 412 + } 413 + } 414 + } 415 + } 416 + 417 + bool isAtLaborPlanet(FleetAI& flAI) { 418 + auto@ miss = cast<LaborMission>(flAI); 419 + if(miss !is null) 420 + return true; 421 + 422 + for(uint i = 0, cnt = laborPlanets.length; i < cnt; ++i) { 423 + if(flAI.obj.isLockedOrbit(laborPlanets[i])) 424 + return true; 425 + } 426 + return false; 427 + } 428 + 429 + bool isBuildingWithLabor(FleetAI& flAI) { 430 + auto@ f = construction.get(flAI.obj); 431 + if(f !is null && f.active !is null) 432 + return false; 433 + if(isAtLaborPlanet(flAI)) 434 + return true; 435 + return false; 436 + } 437 + 438 + bool hasMothershipAt(Planet& pl) { 439 + for(uint i = 0, cnt = motherships.length; i < cnt; ++i) { 440 + auto@ flAI = motherships[i]; 441 + 442 + auto@ miss = cast<LaborMission>(flAI); 443 + if(miss !is null && miss.target is pl) 444 + return true; 445 + 446 + if(flAI.obj.isLockedOrbit(pl)) 447 + return true; 448 + } 449 + return false; 450 + } 451 + 452 + uint idleMothershipCount() { 453 + uint count = 0; 454 + for(uint i = 0, cnt = motherships.length; i < cnt; ++i) { 455 + if(motherships[i].mission is null) 456 + count += 1; 457 + } 458 + return count; 459 + } 460 + 461 + bool isColonizing(Planet& dest) { 462 + for(uint i = 0, cnt = motherships.length; i < cnt; ++i) { 463 + auto@ flAI = motherships[i]; 464 + auto@ miss = cast<HabitatMission>(flAI.mission); 465 + if(miss !is null && miss.target is dest) 466 + return true; 467 + } 468 + return false; 469 + } 470 + 471 + Planet@ colonizeBest(FleetAI& flAI) { 472 + Planet@ best; 473 + double bestDist = INFINITY; 474 + for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) { 475 + Planet@ dest = popRequests[i]; 476 + if(isColonizing(dest)) 477 + continue; 478 + if(dest.inCombat) 479 + continue; 480 + 481 + double d = movement.getPathDistance(flAI.obj.position, dest.position); 482 + if(d < bestDist) { 483 + @best = dest; 484 + bestDist = d; 485 + } 486 + } 487 + 488 + if(best is null) { 489 + for(uint i = 0, cnt = colonization.awaitingSource.length; i < cnt; ++i) { 490 + Planet@ dest = colonization.awaitingSource[i].target; 491 + if(isColonizing(dest)) 492 + continue; 493 + 494 + double d = movement.getPathDistance(flAI.obj.position, dest.position); 495 + if(d < bestDist) { 496 + @best = dest; 497 + bestDist = d; 498 + } 499 + } 500 + } 501 + 502 + if(best !is null) { 503 + HabitatMission miss; 504 + @miss.target = best; 505 + 506 + fleets.performMission(flAI, miss); 507 + } 508 + return best; 509 + } 510 + 511 + FleetAI@ colonizeBest(Planet& dest) { 512 + FleetAI@ best; 513 + double bestDist = INFINITY; 514 + for(uint i = 0, cnt = motherships.length; i < cnt; ++i) { 515 + auto@ flAI = motherships[i]; 516 + if(flAI.mission !is null) 517 + continue; 518 + if(isBuildingWithLabor(flAI)) 519 + continue; 520 + 521 + double d = movement.getPathDistance(flAI.obj.position, dest.position); 522 + if(d < bestDist) { 523 + @best = flAI; 524 + bestDist = d; 525 + } 526 + } 527 + 528 + if(best !is null) { 529 + HabitatMission miss; 530 + @miss.target = dest; 531 + 532 + fleets.performMission(best, miss); 533 + } 534 + return best; 535 + } 536 +}; 537 + 538 +AIComponent@ createStarChildren() { 539 + return StarChildren(); 540 +}
Added scripts/server/empire_ai/weasel/race/Verdant.as.
1 +import empire_ai.weasel.WeaselAI; 2 +import empire_ai.weasel.race.Race; 3 + 4 +import empire_ai.weasel.Designs; 5 +import empire_ai.weasel.Development; 6 +import empire_ai.weasel.Planets; 7 + 8 +import buildings; 9 + 10 +class Verdant : Race, RaceDesigns { 11 + Designs@ designs; 12 + Development@ development; 13 + Planets@ planets; 14 + 15 + array<const Design@> defaultDesigns; 16 + array<uint> defaultGoals; 17 + 18 + const SubsystemDef@ sinewSubsystem; 19 + const SubsystemDef@ supportSinewSubsystem; 20 + 21 + const BuildingType@ stalk; 22 + 23 + void create() override { 24 + @designs = cast<Designs>(ai.designs); 25 + @development = cast<Development>(ai.development); 26 + @planets = cast<Planets>(ai.planets); 27 + 28 + @sinewSubsystem = getSubsystemDef("VerdantSinew"); 29 + @supportSinewSubsystem = getSubsystemDef("VerdantSupportSinew"); 30 + @stalk = getBuildingType("Stalk"); 31 + } 32 + 33 + void start() override { 34 + ReadLock lock(ai.empire.designMutex); 35 + for(uint i = 0, cnt = ai.empire.designCount; i < cnt; ++i) { 36 + const Design@ dsg = ai.empire.getDesign(i); 37 + if(dsg.newer !is null) 38 + continue; 39 + if(dsg.updated !is null) 40 + continue; 41 + 42 + uint goal = designs.classify(dsg, DP_Unknown); 43 + if(goal == DP_Unknown) 44 + continue; 45 + 46 + defaultDesigns.insertLast(dsg); 47 + defaultGoals.insertLast(goal); 48 + } 49 + } 50 + 51 + void save(SaveFile& file) override { 52 + uint cnt = defaultDesigns.length; 53 + file << cnt; 54 + for(uint i = 0; i < cnt; ++i) { 55 + file << defaultDesigns[i]; 56 + file << defaultGoals[i]; 57 + } 58 + } 59 + 60 + void load(SaveFile& file) override { 61 + uint cnt = 0; 62 + file >> cnt; 63 + defaultDesigns.length = cnt; 64 + defaultGoals.length = cnt; 65 + for(uint i = 0; i < cnt; ++i) { 66 + file >> defaultDesigns[i]; 67 + file >> defaultGoals[i]; 68 + } 69 + } 70 + 71 + uint plCheck = 0; 72 + void focusTick(double time) override { 73 + if(ai.behavior.forbidConstruction) return; 74 + 75 + //Check if we need to build stalks anywhere 76 + for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) 77 + checkForStalk(development.focuses[i].plAI); 78 + 79 + uint plCnt = planets.planets.length; 80 + if(plCnt != 0) { 81 + for(uint n = 0; n < min(plCnt, 5); ++n) { 82 + plCheck = (plCheck+1) % plCnt; 83 + auto@ plAI = planets.planets[plCheck]; 84 + checkForStalk(plAI); 85 + } 86 + } 87 + } 88 + 89 + void checkForStalk(PlanetAI@ plAI) { 90 + if(plAI is null) 91 + return; 92 + Planet@ pl = plAI.obj; 93 + if(pl.pressureCap <= 0 && pl.totalPressure >= 1) { 94 + if(planets.isBuilding(plAI.obj, stalk)) 95 + return; 96 + if(pl.getBuildingCount(stalk.id) != 0) 97 + return; 98 + 99 + planets.requestBuilding(plAI, stalk, expire=180.0); 100 + } 101 + } 102 + 103 + bool preCompose(DesignTarget@ target) override { 104 + return false; 105 + } 106 + 107 + bool postCompose(DesignTarget@ target) override { 108 + // auto@ d = target.designer; 109 + 110 + // //Add an extra engine 111 + // if(target.purpose == DP_Combat) 112 + // d.composition.insertAt(0, Exhaust(tag("Engine") & tag("GivesThrust"), 0.25, 0.35)); 113 + 114 + // //Remove armor layers we don't need 115 + // for(uint i = 0, cnt = d.composition.length; i < cnt; ++i) { 116 + // if(cast<ArmorLayer>(d.composition[i]) !is null) { 117 + // d.composition.removeAt(i); 118 + // --i; --cnt; 119 + // } 120 + // } 121 + 122 + return false; 123 + } 124 + 125 + bool design(DesignTarget@ target, int size, const Design@& output) { 126 + //All designs are rescales of default designs 127 + const Design@ baseDesign; 128 + uint possible = 0; 129 + for(uint i = 0, cnt = defaultDesigns.length; i < cnt; ++i) { 130 + if(defaultGoals[i] == target.purpose) { 131 + possible += 1; 132 + if(randomd() < 1.0 / double(possible)) 133 + @baseDesign = defaultDesigns[i]; 134 + } 135 + } 136 + 137 + if(baseDesign is null) 138 + return false; 139 + 140 + //if(target.designer !is null) { 141 + // @target.designer.baseOffDesign = baseDesign; 142 + // if(target.purpose != DP_Support) 143 + // @target.designer.baseOffSubsystem = sinewSubsystem; 144 + // else 145 + // @target.designer.baseOffSubsystem = supportSinewSubsystem; 146 + // @output = target.designer.design(); 147 + //} 148 + 149 + if(output is null) 150 + @output = scaleDesign(baseDesign, size); 151 + return true; 152 + } 153 +}; 154 + 155 +AIComponent@ createVerdant() { 156 + return Verdant(); 157 +}
Added scripts/server/empire_ai/weasel/searches.as.
1 +Object@ findEnemy(Region@ region, Empire@ emp, uint empireMask, bool fleets = true, bool stations = true, bool planets = false) { 2 + array<Object@>@ objs = findInBox(region.position - vec3d(region.radius), region.position + vec3d(region.radius), empireMask); 3 + uint offset = randomi(0, objs.length-1); 4 + uint cnt = objs.length; 5 + for(uint i = 0; i < cnt; ++i) { 6 + Object@ obj = objs[(i+offset)%cnt]; 7 + Empire@ owner = obj.owner; 8 + 9 + if(!obj.valid) { 10 + continue; 11 + } 12 + else if(owner is null || owner.mask & empireMask == 0) { 13 + continue; 14 + } 15 + else if(emp !is null && !obj.isVisibleTo(emp)) { 16 + continue; 17 + } 18 + else if(obj.region !is region) { 19 + continue; 20 + } 21 + else { 22 + uint type = obj.type; 23 + switch(type) { 24 + case OT_Ship: 25 + if(!obj.hasLeaderAI) 26 + continue; 27 + if(cast<Ship>(obj).isStation) { 28 + if(!stations) 29 + continue; 30 + } 31 + else { 32 + if(!fleets) 33 + continue; 34 + } 35 + if(obj.getFleetStrength() < 100.0) 36 + continue; 37 + break; 38 + case OT_Orbital: 39 + if(!stations) 40 + continue; 41 + break; 42 + case OT_Planet: 43 + if(!planets) 44 + continue; 45 + break; 46 + default: 47 + continue; 48 + } 49 + } 50 + 51 + return obj; 52 + } 53 + return null; 54 +} 55 + 56 +array<Object@>@ findEnemies(Region@ region, Empire@ emp, uint empireMask, bool fleets = true, bool stations = true, bool planets = false) { 57 + array<Object@>@ objs = findInBox(region.position - vec3d(region.radius), region.position + vec3d(region.radius), empireMask); 58 + array<Object@> outObjs; 59 + for(int i = objs.length-1; i >= 0; --i) { 60 + Object@ obj = objs[i]; 61 + Empire@ owner = obj.owner; 62 + 63 + bool remove = false; 64 + if(!obj.valid) { 65 + remove = true; 66 + } 67 + else if(owner is null || owner.mask & empireMask == 0) { 68 + remove = true; 69 + } 70 + else if(emp !is null && !obj.isVisibleTo(emp)) { 71 + remove = true; 72 + } 73 + else if(obj.region !is region) { 74 + remove = true; 75 + } 76 + else { 77 + uint type = obj.type; 78 + switch(type) { 79 + case OT_Ship: 80 + if(!obj.hasLeaderAI) 81 + remove = true; 82 + if(cast<Ship>(obj).isStation) { 83 + if(!stations) 84 + remove = true; 85 + } 86 + else { 87 + if(!fleets) 88 + remove = true; 89 + } 90 + if(obj.getFleetStrength() < 100.0) 91 + remove = true; 92 + break; 93 + case OT_Orbital: 94 + if(!stations) 95 + remove = true; 96 + break; 97 + case OT_Planet: 98 + if(!planets) 99 + remove = true; 100 + break; 101 + default: 102 + remove = true; 103 + } 104 + } 105 + 106 + if(!remove) 107 + outObjs.insertLast(obj); 108 + } 109 + return outObjs; 110 +} 111 + 112 +array<Object@>@ findType(Region@ region, Empire@ emp, uint objectType, uint empireMask = ~0) { 113 + // Specialized for safe object buckets 114 + array<Object@>@ objs; 115 + DataList@ data; 116 + switch(objectType) 117 + { 118 + case OT_Planet: 119 + @data = region.getPlanets(); 120 + break; 121 + case OT_Pickup: 122 + @data = region.getPickups(); 123 + break; 124 + case OT_Anomaly: 125 + @data = region.getAnomalies(); 126 + break; 127 + case OT_Artifact: 128 + @data = region.getArtifacts(); 129 + break; 130 + case OT_Asteroid: 131 + @data = region.getAsteroids(); 132 + break; 133 + } 134 + 135 + if(data !is null) 136 + { 137 + @objs = array<Object@>(); 138 + Object@ obj; 139 + while(receive(data, obj)) { 140 + if(obj !is null) 141 + objs.insertLast(obj); 142 + } 143 + } 144 + else { 145 + // No object bucket retrieval mechanism, do a full physics search 146 + @objs = findInBox(region.position - vec3d(region.radius), region.position + vec3d(region.radius), empireMask); 147 + } 148 + 149 + // Generic search using physics system 150 + array<Object@> outObjs; 151 + for(int i = objs.length-1; i >= 0; --i) { 152 + Object@ obj = objs[i]; 153 + Empire@ owner = obj.owner; 154 + 155 + bool remove = false; 156 + if(!obj.valid) { 157 + remove = true; 158 + } 159 + else if(owner is null || owner.mask & empireMask == 0) { 160 + remove = true; 161 + } 162 + else if(emp !is null && !obj.isVisibleTo(emp)) { 163 + remove = true; 164 + } 165 + else if(obj.region !is region) { 166 + remove = true; 167 + } 168 + else { 169 + uint type = obj.type; 170 + if(type != objectType) 171 + remove = true; 172 + } 173 + 174 + if(!remove) 175 + outObjs.insertLast(obj); 176 + } 177 + return outObjs; 178 +} 179 + 180 +array<Object@>@ findAll(Region@ region, uint empireMask = ~0) { 181 + return findInBox(region.position - vec3d(region.radius), region.position + vec3d(region.radius), empireMask); 182 +} 183 + 184 +double getTotalFleetStrength(Region@ region, uint empireMask, bool fleets = true, bool stations = true, bool planets = true) { 185 + auto@ objs = findAll(region, empireMask); 186 + double str = 0.0; 187 + for(uint i = 0, cnt = objs.length; i < cnt; ++i) { 188 + Object@ obj = objs[i]; 189 + Empire@ owner = obj.owner; 190 + if(!obj.valid) 191 + continue; 192 + if(owner is null || owner.mask & empireMask == 0) 193 + continue; 194 + if(obj.region !is region) 195 + continue; 196 + 197 + uint type = obj.type; 198 + switch(type) { 199 + case OT_Ship: 200 + if(!obj.hasLeaderAI) 201 + continue; 202 + if(cast<Ship>(obj).isStation) { 203 + if(!stations) 204 + continue; 205 + } 206 + else { 207 + if(!fleets) 208 + continue; 209 + } 210 + if(obj.getFleetStrength() < 100.0) 211 + continue; 212 + break; 213 + case OT_Orbital: 214 + if(!stations) 215 + continue; 216 + break; 217 + case OT_Planet: 218 + if(!planets) 219 + continue; 220 + break; 221 + default: 222 + continue; 223 + } 224 + 225 + str += sqrt(obj.getFleetStrength()); 226 + } 227 + return str * str; 228 +}
Added scripts/server/game_start.as.
1 +#priority init 1000 2 +#priority sync 10 3 +import empire_ai.EmpireAI; 4 +import settings.map_lib; 5 +import settings.game_settings; 6 +import maps; 7 +import map_systems; 8 +import regions.regions; 9 +import artifacts; 10 +from map_generation import generatedSystems, generatedGalaxyGas, GasData; 11 +from empire import Creeps, majorEmpireCount, initEmpireDesigns, sendChatMessage; 12 + 13 +import void createWormhole(SystemDesc@ from, SystemDesc@ to) from "objects.Oddity"; 14 +import Artifact@ makeArtifact(SystemDesc@ system, uint type = uint(-1)) from "map_effects"; 15 + 16 +//Galaxy positioning 17 +Map@[] galaxies; 18 + 19 +vec3d mapLeft; 20 +vec3d mapRight; 21 +double galaxyRadius = 0; 22 + 23 +const double GALAXY_MIN_SPACING = 60000.0; 24 +const double GALAXY_MAX_SPACING = 120000.0; 25 +const double GALAXY_HEIGHT_MARGIN = 50000.0; 26 + 27 +bool overlaps(Map@ from, vec3d point, Map@ to) { 28 + return point.distanceTo(from.origin) < GALAXY_MIN_SPACING + from.radius + to.radius; 29 +} 30 + 31 +//Homeworld searches 32 +class HomeworldSearch { 33 + ScriptThread@ thread; 34 + vec3d goal; 35 + SystemData@ result; 36 + Map@ map; 37 + Empire@ emp; 38 +}; 39 + 40 +double findHomeworld(double time, ScriptThread& thread) { 41 + HomeworldSearch@ search; 42 + thread.getObject(@search); 43 + 44 + @search.result = search.map.findHomeworld(search.emp, search.goal); 45 + thread.stop(); 46 + return 0; 47 +} 48 + 49 +class QualityCalculation { 50 + array<Map@> galaxies; 51 + array<SystemData@>@ homeworlds; 52 +} 53 + 54 +void calculateQuality(QualityCalculation@ data) { 55 + uint homeworldCount = data.homeworlds.length; 56 + array<double> dists(homeworldCount); 57 + 58 + for(uint g = 0, gcnt = data.galaxies.length; g < gcnt; ++g) { 59 + Map@ mp = data.galaxies[g]; 60 + mp.calculateHomeworldDistances(); 61 + 62 + for(uint i = 0, end = mp.systemData.length; i < end; ++i) { 63 + SystemData@ system = mp.systemData[i]; 64 + mp.calculateQuality(system, data.homeworlds, dists); 65 + } 66 + } 67 +} 68 + 69 +void init() { 70 + soundScale = 500.f; 71 + if(isLoadedSave) 72 + return; 73 + 74 + double start = getExactTime(), end = start; 75 + uint hwGalaxies = 0; 76 + 77 + //Create galaxy map instances 78 + for(uint i = 0, cnt = gameSettings.galaxies.length; i < cnt; ++i) { 79 + Map@ desc = getMap(gameSettings.galaxies[i].map_id); 80 + 81 + if(desc !is null) { 82 + for(uint n = 0; n < gameSettings.galaxies[i].galaxyCount; ++n) { 83 + Map@ mp = cast<Map>(desc.create()); 84 + @mp.settings = gameSettings.galaxies[i]; 85 + mp.allowHomeworlds = gameSettings.galaxies[i].allowHomeworlds; 86 + if(mp.allowHomeworlds) 87 + hwGalaxies += 1; 88 + 89 + galaxies.insertLast(mp); 90 + } 91 + } 92 + else { 93 + error("Error: Could not find map "+gameSettings.galaxies[i].map_id); 94 + } 95 + } 96 + 97 + if(galaxies.length == 0) { 98 + auto@ _map = cast<Map>(getMap("Spiral.SpiralMap").create()); 99 + @_map.settings = MapSettings(); 100 + galaxies.insertLast(_map); 101 + } 102 + 103 + if(hwGalaxies == 0) { 104 + hwGalaxies += 1; 105 + galaxies[0].allowHomeworlds = true; 106 + } 107 + 108 + //Place all the systems 109 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 110 + galaxies[i].preInit(); 111 + if(galaxies[i].allowHomeworlds) 112 + galaxies[i].estPlayerCount = ceil(double(majorEmpireCount) / double(hwGalaxies)); 113 + else 114 + galaxies[i].estPlayerCount = 0; 115 + galaxies[i].universePlayerCount = majorEmpireCount; 116 + galaxies[i].preGenerate(); 117 + } 118 + 119 + //Place the galaxies 120 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 121 + vec3d origin; 122 + 123 + if(i != 0) { 124 + double startRad = galaxies[0].radius + galaxies[i].radius + GALAXY_MIN_SPACING; 125 + double endRad = startRad - GALAXY_MIN_SPACING + GALAXY_MAX_SPACING; 126 + 127 + bool overlap = false; 128 + do { 129 + vec2d pos = random2d(startRad, endRad); 130 + 131 + origin = vec3d(pos.x, randomd(-GALAXY_HEIGHT_MARGIN, GALAXY_HEIGHT_MARGIN), pos.y); 132 + overlap = false; 133 + 134 + for(uint j = 0; j < i; ++j) { 135 + if(overlaps(galaxies[j], origin, galaxies[i])) { 136 + overlap = true; 137 + endRad += GALAXY_MIN_SPACING; 138 + break; 139 + } 140 + } 141 + } 142 + while(overlap); 143 + } 144 + 145 + galaxies[i].setOrigin(origin); 146 + galaxyRadius = max(galaxyRadius, origin.length + galaxies[i].radius * 1.4); 147 + } 148 + 149 + //Search for homeworld starting positions in multiple threads (one per empire) 150 + array<SystemData@> globalHomeworlds; 151 + { 152 + array<TeamSorter> sortedEmps; 153 + for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { 154 + Empire@ emp = getEmpire(i); 155 + if(!emp.major) 156 + continue; 157 + sortedEmps.insertLast(TeamSorter(emp)); 158 + } 159 + sortedEmps.sortAsc(); 160 + 161 + array<HomeworldSearch> homeworlds(sortedEmps.length); 162 + uint mapCnt = galaxies.length; 163 + uint mapN = randomi(0, mapCnt - 1), mapC = 0; 164 + 165 + for(uint i = 0; i < homeworlds.length; ++i) { 166 + HomeworldSearch@ search = homeworlds[i]; 167 + Empire@ emp = sortedEmps[i].emp; 168 + 169 + //Find a galaxy willing to host this empire 170 + uint j = 0; 171 + do { 172 + @search.map = galaxies[(mapN + mapC) % mapCnt]; 173 + ++mapC; 174 + ++j; 175 + } 176 + while((!search.map.allowHomeworlds || !search.map.canHaveHomeworld(emp)) && j < mapCnt); 177 + 178 + if(mapC >= mapCnt) { 179 + mapN = randomi(0, mapCnt - 1); 180 + mapC = 0; 181 + } 182 + 183 + //Suggested place for this empire 184 + double angle = double(i) * twopi / double(majorEmpireCount); 185 + double rad = search.map.radius * 0.8; 186 + search.goal = vec3d(rad * cos(angle), 0, rad * sin(angle)); 187 + search.goal += search.map.origin; 188 + 189 + //Start the search 190 + @search.emp = emp; 191 + if(search.map.possibleHomeworlds.length == 0) 192 + @search.thread = ScriptThread("game_start::findHomeworld", @search); 193 + else 194 + @search.result = search.map.findHomeworld(search.emp, search.goal); 195 + } 196 + 197 + for(uint i = 0; i < homeworlds.length; ++i) { 198 + HomeworldSearch@ search = homeworlds[i]; 199 + while(search.thread !is null && search.thread.running) sleep(0); 200 + if(search.result !is null) { 201 + search.result.addHomeworld(search.emp); 202 + search.map.markHomeworld(search.result); 203 + } 204 + globalHomeworlds.insertLast(search.result); 205 + } 206 + } 207 + 208 + //Calculate system quality in threads 209 + { 210 + array<QualityCalculation> calcs(6); 211 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) 212 + galaxies[i].calculateGalaxyQuality(globalHomeworlds); 213 + 214 + uint n = 0, step = int(ceil(double(galaxies.length) / double(calcs.length))); 215 + for(uint i = 0; i < calcs.length; ++i) { 216 + QualityCalculation@ calc = calcs[i]; 217 + @calc.homeworlds = @globalHomeworlds; 218 + for(uint j = 0; j < step && n < galaxies.length; ++j) { 219 + calc.galaxies.insertLast(galaxies[n]); 220 + n += 1; 221 + } 222 + calculateQuality(calc); 223 + } 224 + } 225 + 226 + //Generate physics 227 + double gridSize = max(modSpacing(7500.0), (galaxyRadius * 2.0) / 150.0); 228 + int gridAmount = (galaxyRadius * 2.0) / gridSize; 229 + setupPhysics(gridSize, gridSize / 8.0, gridAmount); 230 + 231 + //Generate region objects 232 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) 233 + galaxies[i].generateRegions(); 234 + for(uint i = 0, cnt = generatedSystems.length; i < cnt; ++i) 235 + generatedSystems[i].object.finalizeCreation(); 236 + 237 + //Actually generate maps 238 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) 239 + galaxies[i].generate(); 240 + 241 + //Regenerate the region lookup tree with the actual sizes 242 + regenerateRegionGroups(); 243 + 244 + //Generate wormholes in case of multiple galaxies 245 + if(galaxies.length > 1) { 246 + uint totalSystems = 0; 247 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) 248 + totalSystems += galaxies[i].systems.length; 249 + uint wormholes = max(config::GALAXY_MIN_WORMHOLES * galaxies.length, 250 + totalSystems / config::SYSTEMS_PER_WORMHOLE); 251 + if(wormholes % 2 != 0) 252 + wormholes += 1; 253 + uint generated = 0; 254 + 255 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 256 + auto@ glx = galaxies[i]; 257 + 258 + //Figure out how many wormhole endpoints this galaxy should have 259 + double pct = double(glx.systems.length) / double(totalSystems); 260 + uint amount = max(uint(config::GALAXY_MIN_WORMHOLES), uint(round(pct * wormholes))); 261 + 262 + //Tell the galaxy to distribute them 263 + glx.placeWormholes(amount); 264 + 265 + generated += amount; 266 + } 267 + 268 + //Make a circle of wormhole endpoints 269 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 270 + auto@ glx = galaxies[i]; 271 + auto@ nextGlx = galaxies[(i+1)%cnt]; 272 + 273 + auto@ from = glx.getWormhole(); 274 + auto@ to = nextGlx.getWormhole(); 275 + if(from is null || to is null) 276 + continue; 277 + 278 + createWormhole(from, to); 279 + glx.addWormhole(from, to); 280 + nextGlx.addWormhole(to, from); 281 + } 282 + 283 + //Randomly spread the remaining wormholes 284 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { 285 + auto@ glx = galaxies[i], otherGlx; 286 + SystemDesc@ hole = glx.getWormhole(); 287 + SystemDesc@ other; 288 + while(hole !is null) { 289 + uint index = randomi(0, cnt - 1); 290 + for(uint n = 0; n < cnt; ++n) { 291 + @otherGlx = galaxies[n]; 292 + @other = otherGlx.getWormhole(); 293 + 294 + if(other !is null) 295 + break; 296 + } 297 + 298 + if(other !is null) { 299 + createWormhole(hole, other); 300 + glx.addWormhole(hole, other); 301 + otherGlx.addWormhole(other, hole); 302 + } 303 + 304 + @hole = glx.getWormhole(); 305 + @other = null; 306 + @otherGlx = null; 307 + } 308 + } 309 + } 310 + 311 + end = getExactTime(); 312 + info("Map generation: "+toString((end - start)*1000,1)+"ms"); 313 + start = end; 314 + 315 + end = getExactTime(); 316 + info("Link generation: "+toString((end - start)*1000,1)+"ms"); 317 + start = end; 318 + 319 + //Deal with generating unique spread artifacts 320 + if(generatedSystems.length > 1 && config::ENABLE_UNIQUE_SPREADS != 0) { 321 + for(uint i = 0, cnt = getArtifactTypeCount(); i < cnt; ++i) { 322 + auto@ type = getArtifactType(i); 323 + if(type.spreadVariable.length == 0) 324 + continue; 325 + if(config::get(type.spreadVariable) <= 0.0) 326 + continue; 327 + 328 + SystemDesc@ sys; 329 + if(type.requireContestation > 0) 330 + @sys = getRandomSystemAboveContestation(type.requireContestation); 331 + if(sys is null) 332 + @sys = getRandomSystem(); 333 + 334 + if(sys !is null) 335 + makeArtifact(sys, type.id); 336 + } 337 + } 338 + 339 + //Initialization for map code 340 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) 341 + galaxies[i].initDefs(); 342 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) 343 + galaxies[i].init(); 344 + 345 + //Explore entire map if indicated 346 + if(config::START_EXPLORED_MAP != 0.0) { 347 + for(uint i = 0, cnt = systemCount; i < cnt; ++i) 348 + getSystem(i).object.ExploredMask = int(~0); 349 + } 350 + 351 + //Assign already connected players to empires 352 + { 353 + if(playerEmpire !is null && playerEmpire.valid) 354 + CURRENT_PLAYER.linkEmpire(playerEmpire); 355 + uint empInd = 0, empCnt = getEmpireCount(); 356 + array<Player@>@ players = getPlayers(); 357 + 358 + //First pass: players into player empires 359 + for(uint i = 0, plCnt = players.length; i < plCnt && empInd < empCnt; ++i) { 360 + Player@ pl = players[i]; 361 + connectedPlayers.insertLast(pl); 362 + connectedSet.insert(pl.id); 363 + if(pl.emp is null) { 364 + for(; empInd < empCnt; ++empInd) { 365 + Empire@ emp = getEmpire(empInd); 366 + if(!emp.major) 367 + continue; 368 + if(emp.player !is null) 369 + continue; 370 + //if(emp.getAIType() != ET_Player) 371 + // continue; 372 + 373 + pl.linkEmpire(emp); 374 + ++empInd; 375 + break; 376 + } 377 + } 378 + } 379 + 380 + //Second pass: take over AIs 381 + empInd = 0; 382 + for(uint i = 0, plCnt = players.length; i < plCnt && empInd < empCnt; ++i) { 383 + Player@ pl = players[i]; 384 + if(pl.emp is null) { 385 + for(; empInd < empCnt; ++empInd) { 386 + Empire@ emp = getEmpire(empInd); 387 + if(!emp.major) 388 + continue; 389 + if(emp.player !is null) 390 + continue; 391 + 392 + pl.linkEmpire(emp); 393 + if(pl.name.length != 0) 394 + emp.name = pl.name; 395 + ++empInd; 396 + break; 397 + } 398 + } 399 + } 400 + } 401 +} 402 + 403 +class TeamSorter { 404 + Empire@ emp; 405 + TeamSorter() {} 406 + TeamSorter(Empire@ empire) { 407 + @emp = empire; 408 + } 409 + 410 + int opCmp(const TeamSorter& other) const { 411 + if(emp.team == -1) { 412 + if(other.emp.team == -1) 413 + return 0; 414 + return 1; 415 + } 416 + if(other.emp.team == -1) 417 + return -1; 418 + if(emp.team > other.emp.team) 419 + return 1; 420 + if(emp.team < other.emp.team) 421 + return 1; 422 + return 0; 423 + } 424 +}; 425 + 426 +uint get_systemCount() { 427 + return generatedSystems.length; 428 +} 429 + 430 +SystemDesc@ getSystem(uint index) { 431 + if(index >= generatedSystems.length) 432 + return null; 433 + return generatedSystems[index]; 434 +} 435 + 436 +SystemDesc@ getSystem(Region@ region) { 437 + if(region is null || region.SystemId == -1) 438 + return null; 439 + return generatedSystems[region.SystemId]; 440 +} 441 + 442 +SystemDesc@ getSystem(const string& name) { 443 + //TODO: Use dictionary 444 + uint cnt = systemCount; 445 + for(uint i = 0; i < cnt; ++i) { 446 + if(getSystem(i).name == name) 447 + return getSystem(i); 448 + } 449 + return null; 450 +} 451 + 452 +SystemDesc@ getRandomSystem() { 453 + return generatedSystems[randomi(0, generatedSystems.length-1)]; 454 +} 455 + 456 +SystemDesc@ getRandomSystemAboveContestation(double contest) { 457 + double roll = randomd(); 458 + double total = 0.0; 459 + SystemDesc@ chosen; 460 + for(uint i = 0, cnt = generatedSystems.length; i < cnt; ++i) { 461 + auto@ sys = generatedSystems[i]; 462 + if(sys.contestation < contest) 463 + continue; 464 + 465 + total += 1.0; 466 + double chance = 1.0 / total; 467 + if(roll < chance) { 468 + @chosen = sys; 469 + roll /= chance; 470 + } 471 + else { 472 + roll = (roll - chance) / (1.0 - chance); 473 + } 474 + } 475 + return chosen; 476 +} 477 + 478 +SystemDesc@ getClosestSystem(const vec3d& point) { 479 + SystemDesc@ closest; 480 + double dist = INFINITY; 481 + for(uint i = 0, cnt = generatedSystems.length; i < cnt; ++i) { 482 + double d = generatedSystems[i].position.distanceToSQ(point); 483 + if(d < dist) { 484 + dist = d; 485 + @closest = generatedSystems[i]; 486 + } 487 + } 488 + return closest; 489 +} 490 + 491 +void syncInitial(Message& msg) { 492 + uint cnt = generatedSystems.length; 493 + msg << cnt; 494 + for(uint i = 0; i < cnt; ++i) 495 + generatedSystems[i].write(msg); 496 + 497 + cnt = galaxies.length; 498 + msg << cnt; 499 + for(uint i = 0; i < cnt; ++i) 500 + msg << galaxies[i].id; 501 + 502 + cnt = generatedGalaxyGas.length; 503 + msg << cnt; 504 + for(uint i = 0; i < cnt; ++i) { 505 + GasData@ gas = generatedGalaxyGas[i]; 506 + 507 + msg.writeSmallVec3(gas.position); 508 + msg << float(gas.scale); 509 + 510 + if(gas.gdat.cullingNode !is null) { 511 + msg.write1(); 512 + msg.writeSmallVec3(gas.gdat.cullingNode.position); 513 + msg << float(gas.gdat.cullingNode.scale); 514 + } 515 + else { 516 + msg.write0(); 517 + } 518 + 519 + uint sCnt = gas.sprites.length; 520 + msg.writeSmall(sCnt); 521 + for(uint s = 0; s < sCnt; ++s) { 522 + GasSprite@ sprite = gas.sprites[s]; 523 + msg.writeSmallVec3(sprite.pos); 524 + msg << float(sprite.scale); 525 + msg << sprite.color; 526 + msg.writeBit(sprite.structured); 527 + } 528 + } 529 +} 530 + 531 +bool doSystemSync = false; 532 +bool sendPeriodic(Message& msg) { 533 + if(!doSystemSync) 534 + return false; 535 + 536 + doSystemSync = false; 537 + uint cnt = generatedSystems.length; 538 + msg << cnt; 539 + for(uint i = 0; i < cnt; ++i) 540 + generatedSystems[i].write(msg); 541 + return true; 542 +} 543 + 544 +array<Player@> connectedPlayers; 545 +set_int connectedSet; 546 +double timer = 0.0; 547 +void tick(double time) { 548 + for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) 549 + galaxies[i].tick(time); 550 + 551 + timer += time; 552 + if(timer >= 1.0) { 553 + timer = 0.0; 554 + array<Player@>@ players = getPlayers(); 555 + 556 + //Send connect events 557 + for(uint i = 0, cnt = players.length; i < cnt; ++i) { 558 + Player@ pl = players[i]; 559 + string name = pl.name; 560 + if(name.length == 0) 561 + continue; 562 + if(!connectedSet.contains(pl.id)) { 563 + string msg = format("[color=#aaa]"+locale::MP_CONNECT_EVENT+"[/color]", 564 + format("[b]$1[/b]", bbescape(name))); 565 + sendChatMessage(msg, offset=30); 566 + connectedPlayers.insertLast(pl); 567 + connectedSet.insert(pl.id); 568 + } 569 + } 570 + 571 + connectedSet.clear(); 572 + for(uint i = 0, cnt = players.length; i < cnt; ++i) 573 + connectedSet.insert(players[i].id); 574 + 575 + //Send disconnect events 576 + for(uint i = 0, cnt = connectedPlayers.length; i < cnt; ++i) { 577 + if(!connectedSet.contains(connectedPlayers[i].id)) { 578 + Color color; 579 + string name = connectedPlayers[i].name; 580 + Empire@ emp = connectedPlayers[i].emp; 581 + if(emp !is null) 582 + color = emp.color; 583 + 584 + string msg = format("[color=#aaa]"+locale::MP_DISCONNECT_EVENT+"[/color]", 585 + format("[b][color=$1]$2[/color][/b]", toString(color), bbescape(name))); 586 + sendChatMessage(msg, offset=30); 587 + connectedPlayers.removeAt(i); 588 + --i; --cnt; 589 + } 590 + } 591 + } 592 +} 593 + 594 +void getSystems() { 595 + uint cnt = generatedSystems.length; 596 + for(uint i = 0; i < cnt; ++i) 597 + yield(generatedSystems[i]); 598 +} 599 + 600 +void generateNewSystem(const vec3d& pos, double radius, const string& name = "", bool makeLinks = true) { 601 + generateNewSystem(pos, radius, null, name, makeLinks); 602 +} 603 + 604 +void generateNewSystem(const vec3d& pos, double radius, SystemGenerateHook@ hook, const string& name = "", bool makeLinks = true, const string& type = "") { 605 + //Because things access the generated systems list from outside of a locked context for performance, and 606 + //creating new systems is a very very rare thing, we just use an isolation hook here, which pauses the 607 + //execution of the entire game, runs the hook, then resumes. 608 + SystemGenerator sys; 609 + sys.position = pos; 610 + sys.radius = radius; 611 + sys.makeLinks = makeLinks; 612 + sys.makeType = type; 613 + sys.name = name; 614 + @sys.hook = hook; 615 + isolate_run(sys); 616 +} 617 + 618 +interface SystemGenerateHook { 619 + void call(SystemDesc@ desc); 620 +} 621 + 622 +class SystemGenerator : IsolateHook { 623 + vec3d position; 624 + double radius; 625 + string name; 626 + SystemGenerateHook@ hook; 627 + bool makeLinks = true; 628 + string makeType; 629 + 630 + void call() { 631 + if(name.length == 0) { 632 + NameGenerator sysNames; 633 + sysNames.read("data/system_names.txt"); 634 + name = sysNames.generate(); 635 + } 636 + 637 + ObjectDesc sysDesc; 638 + sysDesc.type = OT_Region; 639 + sysDesc.name = name; 640 + sysDesc.flags |= objNoPhysics; 641 + sysDesc.flags |= objNoDamage; 642 + sysDesc.delayedCreation = true; 643 + sysDesc.position = position; 644 + 645 + Region@ region = cast<Region>(makeObject(sysDesc)); 646 + region.alwaysVisible = true; 647 + region.InnerRadius = radius / 1.5; 648 + region.OuterRadius = radius; 649 + region.radius = region.OuterRadius; 650 + 651 + SystemData dat; 652 + dat.index = generatedSystems.length; 653 + dat.position = position; 654 + dat.quality = 100; 655 + @dat.systemCode = SystemCode(); 656 + 657 + SystemDesc desc; 658 + desc.index = generatedSystems.length; 659 + region.SystemId = desc.index; 660 + desc.name = region.name; 661 + desc.position = position; 662 + desc.radius = region.OuterRadius; 663 + @desc.object = region; 664 + 665 + generatedSystems.insertLast(desc); 666 + addRegion(desc.object); 667 + 668 + region.finalizeCreation(); 669 + 670 + //Run the type 671 + auto@ sysType = getSystemType(makeType); 672 + if(sysType !is null) { 673 + dat.systemType = sysType.id; 674 + 675 + sysType.generate(dat, desc); 676 + 677 + region.InnerRadius = desc.radius; 678 + region.OuterRadius = desc.radius * 1.5; 679 + region.radius = region.OuterRadius; 680 + desc.radius = region.OuterRadius; 681 + 682 + sysType.postGenerate(dat, desc); 683 + 684 + MapGeneration gen; 685 + gen.finalizeSystem(dat, desc); 686 + } 687 + 688 + //Make trade lines to nearby systems 689 + if(makeLinks) { 690 + SystemDesc@ closest; 691 + array<SystemDesc@> nearby; 692 + double closestDist = INFINITY; 693 + for(uint i = 0, cnt = generatedSystems.length; i < cnt; ++i) { 694 + double d = generatedSystems[i].position.distanceTo(desc.position); 695 + if(generatedSystems[i] is desc) 696 + continue; 697 + if(d < 13000.0) 698 + nearby.insertLast(generatedSystems[i]); 699 + if(d < closestDist) { 700 + closestDist = d; 701 + @closest = generatedSystems[i]; 702 + } 703 + } 704 + 705 + if(nearby.length == 0) { 706 + closest.adjacent.insertLast(desc.index); 707 + closest.adjacentDist.insertLast(closest.position.distanceTo(desc.position)); 708 + desc.adjacent.insertLast(closest.index); 709 + desc.adjacentDist.insertLast(closest.position.distanceTo(desc.position)); 710 + } 711 + else { 712 + for(uint i = 0, cnt = nearby.length; i < cnt; ++i) { 713 + nearby[i].adjacent.insertLast(desc.index); 714 + nearby[i].adjacentDist.insertLast(nearby[i].position.distanceTo(desc.position)); 715 + desc.adjacent.insertLast(nearby[i].index); 716 + desc.adjacentDist.insertLast(nearby[i].position.distanceTo(desc.position)); 717 + } 718 + } 719 + 720 + if(desc.adjacent.length == 0 || config::START_EXPLORED_MAP != 0.0) { 721 + desc.object.ExploredMask.value = int(~0); 722 + } 723 + else { 724 + for(uint i = 0, cnt = desc.adjacent.length; i < cnt; ++i) 725 + desc.object.ExploredMask |= getSystem(desc.adjacent[i]).object.SeenMask; 726 + } 727 + } 728 + 729 + //Create the system node 730 + Node@ snode = bindCullingNode(region, desc.position, 1000.0); 731 + snode.scale = region.radius + 128.0; 732 + snode.rebuildTransform(); 733 + 734 + calcGalaxyExtents(); 735 + 736 + if(hook !is null) 737 + hook.call(desc); 738 + 739 + //Notify clients of changes 740 + refreshClientSystems(CURRENT_PLAYER); 741 + doSystemSync = true; 742 + } 743 +}; 744 + 745 +void save(SaveFile& data) { 746 + data << uint(generatedSystems.length); 747 + for(uint i = 0; i < generatedSystems.length; ++i) 748 + generatedSystems[i].save(data); 749 + data << uint(generatedGalaxies.length); 750 + for(uint i = 0; i < generatedGalaxies.length; ++i) 751 + generatedGalaxies[i].save(data); 752 + data << uint(generatedGalaxyGas.length); 753 + for(uint i = 0; i < generatedGalaxyGas.length; ++i) 754 + generatedGalaxyGas[i].save(data); 755 + data << uint(galaxies.length); 756 + for(uint i = 0; i < galaxies.length; ++i) { 757 + data << galaxies[i].id; 758 + galaxies[i].save(data); 759 + } 760 +} 761 + 762 +void load(SaveFile& data) { 763 + uint count = 0; 764 + data >> count; 765 + generatedSystems.length = count; 766 + for(uint i = 0; i < generatedSystems.length; ++i) { 767 + SystemDesc desc; 768 + desc.load(data); 769 + @generatedSystems[i] = desc; 770 + } 771 + 772 + data >> count; 773 + generatedGalaxies.length = count; 774 + for(uint i = 0; i < count; ++i) { 775 + @generatedGalaxies[i] = GalaxyData(); 776 + generatedGalaxies[i].load(data); 777 + } 778 + 779 + data >> count; 780 + generatedGalaxyGas.length = count; 781 + for(uint i = 0; i < count; ++i) { 782 + @generatedGalaxyGas[i] = GasData(); 783 + generatedGalaxyGas[i].load(data); 784 + } 785 + 786 + if(data >= SV_0040) { 787 + data >> count; 788 + galaxies.length = count; 789 + 790 + for(uint i = 0; i < galaxies.length; ++i) { 791 + string ident; 792 + data >> ident; 793 + @galaxies[i] = getMap(ident).create(); 794 + galaxies[i].load(data); 795 + } 796 + } 797 +}