AI Empire  Check-in [28693ba5f2]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
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:28693ba5f2fef65a48df55bf51306291c0bd98ad
User & Date: pollen 2019-06-19 04:29:05
Context
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
Changes

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  +}