aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Morgan <sjm@sjm.io>2016-05-20 14:45:37 +0100
committerSimon Morgan <sjm@sjm.io>2016-05-20 14:45:37 +0100
commit80ab5e54b1a1eeaceb5ad1b9a2bd83936efab887 (patch)
tree99bdef6f433d1a5e485451932e3b2c084d0c4f0a
download7kaa-80ab5e54b1a1eeaceb5ad1b9a2bd83936efab887.tar.gz
7kaa-80ab5e54b1a1eeaceb5ad1b9a2bd83936efab887.tar.bz2
7kaa-80ab5e54b1a1eeaceb5ad1b9a2bd83936efab887.zip
first commitHEADmaster
-rw-r--r--7k.icobin0 -> 766 bytes
-rw-r--r--AM.cpp406
-rw-r--r--COPYING340
-rw-r--r--OAI_ACT.cpp746
-rw-r--r--OAI_ACT2.cpp496
-rw-r--r--OAI_ATTK.cpp923
-rw-r--r--OAI_BUIL.cpp343
-rw-r--r--OAI_CAP2.cpp621
-rw-r--r--OAI_CAPT.cpp603
-rw-r--r--OAI_DEFE.cpp96
-rw-r--r--OAI_DIPL.cpp1092
-rw-r--r--OAI_ECO.cpp131
-rw-r--r--OAI_GRAN.cpp1023
-rw-r--r--OAI_INFO.cpp441
-rw-r--r--OAI_MAIN.cpp671
-rw-r--r--OAI_MAR2.cpp452
-rw-r--r--OAI_MAR3.cpp446
-rw-r--r--OAI_MARI.cpp664
-rw-r--r--OAI_MILI.cpp188
-rw-r--r--OAI_MONS.cpp141
-rw-r--r--OAI_QUER.cpp108
-rw-r--r--OAI_SEEK.cpp755
-rw-r--r--OAI_SPY.cpp508
-rw-r--r--OAI_TALK.cpp1111
-rw-r--r--OAI_TOWN.cpp146
-rw-r--r--OAI_TRAD.cpp36
-rw-r--r--OAI_UNIT.cpp684
-rw-r--r--OANLINE.cpp524
-rw-r--r--OAUDIO.cpp2091
-rw-r--r--OBATTLE.cpp930
-rw-r--r--OBLOB.cpp62
-rw-r--r--OBOX.cpp624
-rw-r--r--OBULLET.cpp646
-rw-r--r--OBULLETA.cpp478
-rw-r--r--OBUTT3D.cpp367
-rw-r--r--OBUTTCUS.cpp369
-rw-r--r--OBUTTON.cpp594
-rw-r--r--OB_FLAME.cpp78
-rw-r--r--OB_HOMIN.cpp231
-rw-r--r--OB_PROJ.cpp158
-rw-r--r--OCOLTBL.cpp869
-rw-r--r--OCONFIG.cpp540
-rw-r--r--OCRC_STO.cpp288
-rw-r--r--ODATE.cpp471
-rw-r--r--ODB.cpp178
-rw-r--r--ODIR.cpp93
-rw-r--r--ODPLAY.cpp1166
-rw-r--r--ODYNARR.cpp752
-rw-r--r--ODYNARRB.cpp450
-rw-r--r--OEFFECT.cpp142
-rw-r--r--OERRCTRL.cpp727
-rw-r--r--OERROR.cpp235
-rw-r--r--OEXPMASK.cpp108
-rw-r--r--OFILE.cpp504
-rw-r--r--OFILETXT.cpp353
-rw-r--r--OFIRM.cpp3831
-rw-r--r--OFIRM2.cpp176
-rw-r--r--OFIRMA.cpp719
-rw-r--r--OFIRMAI.cpp552
-rw-r--r--OFIRMDIE.cpp451
-rw-r--r--OFIRMDRW.cpp653
-rw-r--r--OFIRMIF.cpp727
-rw-r--r--OFIRMIF2.cpp716
-rw-r--r--OFIRMIF3.cpp603
-rw-r--r--OFIRMRES.cpp628
-rw-r--r--OFLAME.cpp942
-rw-r--r--OFONT.cpp1369
-rw-r--r--OF_BASE.cpp635
-rw-r--r--OF_BASE2.cpp75
-rw-r--r--OF_CAMP.cpp1366
-rw-r--r--OF_CAMP2.cpp1514
-rw-r--r--OF_FACT.cpp608
-rw-r--r--OF_FACT2.cpp288
-rw-r--r--OF_HARB.cpp1269
-rw-r--r--OF_HARB2.cpp291
-rw-r--r--OF_INN.cpp681
-rw-r--r--OF_INN2.cpp257
-rw-r--r--OF_MARK.cpp990
-rw-r--r--OF_MARK2.cpp898
-rw-r--r--OF_MINE.cpp410
-rw-r--r--OF_MINE2.cpp189
-rw-r--r--OF_MONS.cpp1107
-rw-r--r--OF_RESE.cpp556
-rw-r--r--OF_RESE2.cpp127
-rw-r--r--OF_WAR.cpp809
-rw-r--r--OF_WAR2.cpp196
-rw-r--r--OGAMCRED.cpp287
-rw-r--r--OGAME.cpp433
-rw-r--r--OGAMEMP.cpp4889
-rw-r--r--OGAMENCY.cpp669
-rw-r--r--OGAMEND.cpp581
-rw-r--r--OGAMESET.cpp201
-rw-r--r--OGAMHALL.cpp211
-rw-r--r--OGAMMAIN.cpp862
-rw-r--r--OGAMMENU.cpp765
-rw-r--r--OGAMSCE2.cpp549
-rw-r--r--OGAMSCEN.cpp267
-rw-r--r--OGAMSING.cpp1130
-rw-r--r--OGENHILL.cpp632
-rw-r--r--OGENMAP.cpp822
-rw-r--r--OGETA.cpp612
-rw-r--r--OGFILE.cpp431
-rw-r--r--OGFILE2.cpp1445
-rw-r--r--OGFILE3.cpp1544
-rw-r--r--OGFILEA.cpp952
-rw-r--r--OGF_V1.cpp390
-rw-r--r--OGODRES.cpp237
-rw-r--r--OGRPSEL.cpp170
-rw-r--r--OHELP.cpp576
-rw-r--r--OHILLRES.cpp368
-rw-r--r--OIMGRES.cpp277
-rw-r--r--OINFO.cpp759
-rw-r--r--OINGMENU.cpp321
-rw-r--r--OLIGHTN.cpp298
-rw-r--r--OLIGHTN2.cpp134
-rw-r--r--OLOG.cpp130
-rw-r--r--OLONGLOG.cpp53
-rw-r--r--OLZW.cpp528
-rw-r--r--OMATRIX.cpp971
-rw-r--r--OMEM.cpp604
-rw-r--r--OMISC.cpp1472
-rw-r--r--OMONSRES.cpp325
-rw-r--r--OMOUSE.cpp1606
-rw-r--r--OMOUSECR.cpp504
-rw-r--r--OMOUSEFR.cpp1617
-rw-r--r--OMOUSEGE.cpp1655
-rw-r--r--OMOUSESP.cpp1625
-rw-r--r--OMP_CRC.cpp892
-rw-r--r--OMUSIC.cpp298
-rw-r--r--ONATIONA.cpp1029
-rw-r--r--ONATIONB.cpp2354
-rw-r--r--ONEWS.cpp479
-rw-r--r--ONEWS2.cpp1194
-rw-r--r--ONEWSENG.cpp1474
-rw-r--r--ONEWSFRE.cpp1657
-rw-r--r--ONEWSGER.cpp1685
-rw-r--r--ONEWSSPA.cpp1671
-rw-r--r--OOPTMENU.cpp560
-rw-r--r--OPLANT.cpp451
-rw-r--r--OPLASMA.cpp551
-rw-r--r--OPOWER.cpp2256
-rw-r--r--ORACERES.cpp493
-rw-r--r--ORAIN1.cpp107
-rw-r--r--ORAIN2.cpp185
-rw-r--r--ORAIN3.cpp99
-rw-r--r--ORAWRES.cpp291
-rw-r--r--OREBEL.cpp1115
-rw-r--r--OREGION.cpp281
-rw-r--r--OREGIONS.cpp338
-rw-r--r--OREMOTE.cpp322
-rw-r--r--OREMOTE2.cpp797
-rw-r--r--OREMOTEM.cpp2927
-rw-r--r--OREMOTEQ.cpp177
-rw-r--r--ORES.cpp223
-rw-r--r--ORESDB.cpp268
-rw-r--r--ORESX.cpp411
-rw-r--r--OROCK.cpp111
-rw-r--r--OROCKRES.cpp683
-rw-r--r--OR_AI.cpp221
-rw-r--r--OR_ECO.cpp261
-rw-r--r--OR_MIL.cpp510
-rw-r--r--OR_NAT.cpp1097
-rw-r--r--OR_NEWS.cpp122
-rw-r--r--OR_RANK.cpp586
-rw-r--r--OR_SPY.cpp293
-rw-r--r--OR_TECH.cpp393
-rw-r--r--OR_TOWN.cpp466
-rw-r--r--OR_TRADE.cpp501
-rw-r--r--OSCROLL.cpp535
-rw-r--r--OSE.cpp534
-rw-r--r--OSERES.cpp511
-rw-r--r--OSFRMRES.cpp111
-rw-r--r--OSITE.cpp834
-rw-r--r--OSITEDRW.cpp229
-rw-r--r--OSKILL.cpp95
-rw-r--r--OSLIDCUS.cpp457
-rw-r--r--OSNOW1.cpp169
-rw-r--r--OSNOW2.cpp68
-rw-r--r--OSNOWG.cpp190
-rw-r--r--OSNOWRES.cpp243
-rw-r--r--OSPATH.cpp3290
-rw-r--r--OSPATHBT.cpp162
-rw-r--r--OSPATHS2.cpp5157
-rw-r--r--OSPREDBG.cpp112
-rw-r--r--OSPREOFF.cpp965
-rw-r--r--OSPRESMO.cpp816
-rw-r--r--OSPREUSE.cpp856
-rw-r--r--OSPRITE.cpp378
-rw-r--r--OSPRITE2.cpp520
-rw-r--r--OSPRITEA.cpp381
-rw-r--r--OSPRTRES.cpp400
-rw-r--r--OSPY.cpp1432
-rw-r--r--OSPY2.cpp487
-rw-r--r--OSPYA.cpp717
-rw-r--r--OSTR.cpp198
-rw-r--r--OSYS.cpp3246
-rw-r--r--OSYS2.cpp1185
-rw-r--r--OSYS3.cpp220
-rw-r--r--OTALKENG.cpp1137
-rw-r--r--OTALKFRE.cpp1357
-rw-r--r--OTALKGER.cpp1316
-rw-r--r--OTALKMSG.cpp595
-rw-r--r--OTALKRES.cpp1389
-rw-r--r--OTALKSPA.cpp1340
-rw-r--r--OTECHRES.cpp438
-rw-r--r--OTERRAIN.cpp965
-rw-r--r--OTORNADO.cpp397
-rw-r--r--OTOWN.cpp4490
-rw-r--r--OTOWNA.cpp806
-rw-r--r--OTOWNAI.cpp1800
-rw-r--r--OTOWNBLD.cpp87
-rw-r--r--OTOWNDRW.cpp376
-rw-r--r--OTOWNIF.cpp2065
-rw-r--r--OTOWNIND.cpp464
-rw-r--r--OTOWNRES.cpp490
-rw-r--r--OTRANSL.cpp256
-rw-r--r--OTUTOR.cpp605
-rw-r--r--OTUTOR2.cpp425
-rw-r--r--OUNIT.cpp2756
-rw-r--r--OUNIT2.cpp411
-rw-r--r--OUNITA.cpp837
-rw-r--r--OUNITAAC.cpp1292
-rw-r--r--OUNITAAT.cpp1995
-rw-r--r--OUNITAC.cpp1679
-rw-r--r--OUNITAI.cpp1293
-rw-r--r--OUNITAM.cpp1325
-rw-r--r--OUNITAMT.cpp302
-rw-r--r--OUNITAT.cpp1427
-rw-r--r--OUNITAT2.cpp2255
-rw-r--r--OUNITAT3.cpp978
-rw-r--r--OUNITATB.cpp408
-rw-r--r--OUNITD.cpp1143
-rw-r--r--OUNITDRW.cpp482
-rw-r--r--OUNITHB.cpp563
-rw-r--r--OUNITI.cpp1232
-rw-r--r--OUNITIF.cpp1649
-rw-r--r--OUNITIND.cpp142
-rw-r--r--OUNITM.cpp3024
-rw-r--r--OUNITRES.cpp540
-rw-r--r--OUNITS.cpp656
-rw-r--r--OU_CARA.cpp1584
-rw-r--r--OU_CARA2.cpp338
-rw-r--r--OU_CARAS.cpp312
-rw-r--r--OU_CARAT.cpp530
-rw-r--r--OU_CART.cpp163
-rw-r--r--OU_GOD.cpp763
-rw-r--r--OU_GOD2.cpp644
-rw-r--r--OU_MARI.cpp775
-rw-r--r--OU_MARI2.cpp201
-rw-r--r--OU_MARIF.cpp1154
-rw-r--r--OU_MARIS.cpp211
-rw-r--r--OU_MARIT.cpp1404
-rw-r--r--OU_MONS.cpp476
-rw-r--r--OU_VEHI.cpp100
-rw-r--r--OVBROWIF.cpp166
-rw-r--r--OVBROWSE.cpp605
-rw-r--r--OVGA.cpp714
-rw-r--r--OVGA2.cpp110
-rw-r--r--OVGABUF.cpp810
-rw-r--r--OVGABUF2.cpp872
-rw-r--r--OVGALOCK.cpp154
-rw-r--r--OVIDEO.cpp565
-rw-r--r--OVOLUME.cpp103
-rw-r--r--OVQUEUE.cpp144
-rw-r--r--OWALLRES.cpp356
-rw-r--r--OWARPT.cpp180
-rw-r--r--OWEATHER.cpp530
-rw-r--r--OWORLD.cpp2401
-rw-r--r--OWORLD_M.cpp420
-rw-r--r--OWORLD_Z.cpp2070
-rw-r--r--OW_FIRE.cpp325
-rw-r--r--OW_PLANT.cpp533
-rw-r--r--OW_ROCK.cpp386
-rw-r--r--OW_SOUND.cpp57
-rw-r--r--OW_WALL.cpp1112
-rw-r--r--README67
-rw-r--r--asm/ALL.inc35
-rw-r--r--asm/COLCODE.inc58
-rw-r--r--asm/CRC.asm131
-rw-r--r--asm/IB.asm93
-rw-r--r--asm/IB2.asm92
-rw-r--r--asm/IB_32.asm130
-rw-r--r--asm/IB_A.asm115
-rw-r--r--asm/IB_AR.asm125
-rw-r--r--asm/IB_AT.asm235
-rw-r--r--asm/IB_ATD.asm251
-rw-r--r--asm/IB_ATDM.asm261
-rw-r--r--asm/IB_ATM.asm238
-rw-r--r--asm/IB_ATR.asm252
-rw-r--r--asm/IB_ATRD.asm326
-rw-r--r--asm/IB_ATRDM.asm266
-rw-r--r--asm/IB_DW.asm94
-rw-r--r--asm/IB_R.asm103
-rw-r--r--asm/IB_RD.asm127
-rw-r--r--asm/IB_T.asm213
-rw-r--r--asm/IB_TD.asm175
-rw-r--r--asm/IB_TDM.asm183
-rw-r--r--asm/IB_TM1.asm218
-rw-r--r--asm/IB_TM2.asm215
-rw-r--r--asm/IB_TM3.asm220
-rw-r--r--asm/IB_TR.asm229
-rw-r--r--asm/IB_TRD.asm181
-rw-r--r--asm/IB_TRDM.asm187
-rw-r--r--asm/IC.asm113
-rw-r--r--asm/IC_R.asm156
-rw-r--r--asm/IJ_T.asm109
-rw-r--r--asm/IMGFUN.inc74
-rw-r--r--asm/IR.asm94
-rw-r--r--asm/IR_A.asm107
-rw-r--r--asm/IR_AM.asm110
-rw-r--r--asm/IR_BAR.asm91
-rw-r--r--asm/IR_M.asm97
-rw-r--r--asm/I_BAR.asm116
-rw-r--r--asm/I_BLACK.asm119
-rw-r--r--asm/I_CTRL.asm88
-rw-r--r--asm/I_EMASK.asm247
-rw-r--r--asm/I_EREMAP.asm250
-rw-r--r--asm/I_FONT.asm154
-rw-r--r--asm/I_FREMAP.asm467
-rw-r--r--asm/I_LINE.asm170
-rw-r--r--asm/I_PIXEL.asm91
-rw-r--r--asm/I_READ.asm99
-rw-r--r--asm/I_SNOW.asm112
-rw-r--r--asm/I_XOR.asm73
-rw-r--r--asm/targets.pl43
-rw-r--r--build.pl210
-rw-r--r--clean.pl67
-rw-r--r--configure.pl224
-rw-r--r--ico.rc1
-rw-r--r--include/ALL.h155
-rw-r--r--include/COLCODE.h39
-rw-r--r--include/COLOR.h72
-rw-r--r--include/CRC.h38
-rw-r--r--include/DMOUSE.h199
-rw-r--r--include/GAMEDEF.h127
-rw-r--r--include/IMGFUN.h93
-rw-r--r--include/KEY.h201
-rw-r--r--include/MPTYPES.h50
-rw-r--r--include/OANLINE.h73
-rw-r--r--include/OAUDIO.h169
-rw-r--r--include/OBATTLE.h63
-rw-r--r--include/OBLOB.h47
-rw-r--r--include/OBOX.h88
-rw-r--r--include/OBULLET.h132
-rw-r--r--include/OBUTT3D.h104
-rw-r--r--include/OBUTTCUS.h109
-rw-r--r--include/OBUTTON.h145
-rw-r--r--include/OB_FLAME.h48
-rw-r--r--include/OB_HOMIN.h56
-rw-r--r--include/OB_PROJ.h54
-rw-r--r--include/OCOLTBL.h132
-rw-r--r--include/OCONFIG.h241
-rw-r--r--include/OCRC_STO.h68
-rw-r--r--include/ODATE.h61
-rw-r--r--include/ODB.h97
-rw-r--r--include/ODIR.h57
-rw-r--r--include/ODPLAY.h172
-rw-r--r--include/ODYNARR.h302
-rw-r--r--include/ODYNARRB.h89
-rw-r--r--include/OEFFECT.h55
-rw-r--r--include/OERRCTRL.h136
-rw-r--r--include/OEXPMASK.h47
-rw-r--r--include/OFILE.h77
-rw-r--r--include/OFILETXT.h73
-rw-r--r--include/OFIRERES.h77
-rw-r--r--include/OFIRM.h440
-rw-r--r--include/OFIRMA.h91
-rw-r--r--include/OFIRMALL.h42
-rw-r--r--include/OFIRMDIE.h108
-rw-r--r--include/OFIRMID.h47
-rw-r--r--include/OFIRMRES.h300
-rw-r--r--include/OFLAME.h88
-rw-r--r--include/OFONT.h201
-rw-r--r--include/OF_BASE.h97
-rw-r--r--include/OF_CAMP.h175
-rw-r--r--include/OF_FACT.h98
-rw-r--r--include/OF_HARB.h144
-rw-r--r--include/OF_INN.h118
-rw-r--r--include/OF_MARK.h164
-rw-r--r--include/OF_MINE.h103
-rw-r--r--include/OF_MONS.h152
-rw-r--r--include/OF_RESE.h87
-rw-r--r--include/OF_WAR.h93
-rw-r--r--include/OGAME.h174
-rw-r--r--include/OGAMESET.h101
-rw-r--r--include/OGET.h155
-rw-r--r--include/OGETA.h92
-rw-r--r--include/OGFILE.h173
-rw-r--r--include/OGF_V1.h424
-rw-r--r--include/OGODRES.h138
-rw-r--r--include/OGRPSEL.h44
-rw-r--r--include/OHELP.h124
-rw-r--r--include/OHILLRES.h120
-rw-r--r--include/OHSETRES.h151
-rw-r--r--include/OIMGRES.h67
-rw-r--r--include/OIMMPLAY.h152
-rw-r--r--include/OINFO.h257
-rw-r--r--include/OINGMENU.h52
-rw-r--r--include/OISOAREA.h82
-rw-r--r--include/OLIGHTN.h94
-rw-r--r--include/OLOG.h86
-rw-r--r--include/OLONGLOG.h42
-rw-r--r--include/OLZW.h119
-rw-r--r--include/OMATRIX.h375
-rw-r--r--include/OMISC.h145
-rw-r--r--include/OMLINK.h70
-rw-r--r--include/OMONSRES.h111
-rw-r--r--include/OMOUSE.h233
-rw-r--r--include/OMOUSE2.h52
-rw-r--r--include/OMOUSECR.h195
-rw-r--r--include/OMUSIC.h58
-rw-r--r--include/ONATION.h638
-rw-r--r--include/ONATIONA.h175
-rw-r--r--include/ONATIONB.h447
-rw-r--r--include/ONEWS.h273
-rw-r--r--include/OOPTMENU.h71
-rw-r--r--include/OPLANT.h145
-rw-r--r--include/OPLASMA.h69
-rw-r--r--include/OPOWER.h141
-rw-r--r--include/ORACERES.h167
-rw-r--r--include/ORAIN.h106
-rw-r--r--include/ORAWRES.h131
-rw-r--r--include/OREBEL.h144
-rw-r--r--include/OREGION.h143
-rw-r--r--include/OREGIONS.h91
-rw-r--r--include/OREMOTE.h467
-rw-r--r--include/OREMOTEQ.h65
-rw-r--r--include/ORES.h68
-rw-r--r--include/ORESDB.h66
-rw-r--r--include/ORESTXT.h80
-rw-r--r--include/ORESX.h90
-rw-r--r--include/OROCK.h77
-rw-r--r--include/OROCKRES.h216
-rw-r--r--include/OSCROLL.h78
-rw-r--r--include/OSE.h111
-rw-r--r--include/OSERES.h132
-rw-r--r--include/OSFRMRES.h97
-rw-r--r--include/OSITE.h130
-rw-r--r--include/OSKILL.h79
-rw-r--r--include/OSLIDCUS.h146
-rw-r--r--include/OSLIDER.h69
-rw-r--r--include/OSNOW.h82
-rw-r--r--include/OSNOWG.h86
-rw-r--r--include/OSNOWRES.h114
-rw-r--r--include/OSPATH.h249
-rw-r--r--include/OSPATHS2.h142
-rw-r--r--include/OSPINNER.h81
-rw-r--r--include/OSPREUSE.h216
-rw-r--r--include/OSPRITE.h212
-rw-r--r--include/OSPRTRES.h252
-rw-r--r--include/OSPY.h203
-rw-r--r--include/OSTR.h101
-rw-r--r--include/OSYS.h268
-rw-r--r--include/OTALKMSG.h55
-rw-r--r--include/OTALKRES.h217
-rw-r--r--include/OTARRAY.h68
-rw-r--r--include/OTECHRES.h196
-rw-r--r--include/OTERRAIN.h236
-rw-r--r--include/OTORNADO.h104
-rw-r--r--include/OTOWN.h463
-rw-r--r--include/OTOWNREC.h100
-rw-r--r--include/OTOWNRES.h172
-rw-r--r--include/OTRANSL.h65
-rw-r--r--include/OTUTOR.h130
-rw-r--r--include/OUNIT.h884
-rw-r--r--include/OUNITALL.h34
-rw-r--r--include/OUNITRES.h393
-rw-r--r--include/OU_CARA.h207
-rw-r--r--include/OU_CART.h48
-rw-r--r--include/OU_GOD.h112
-rw-r--r--include/OU_MARI.h217
-rw-r--r--include/OU_MONS.h70
-rw-r--r--include/OU_VEHI.h52
-rw-r--r--include/OVBROWIF.h59
-rw-r--r--include/OVBROWSE.h117
-rw-r--r--include/OVGA.h118
-rw-r--r--include/OVGABUF.h273
-rw-r--r--include/OVGALOCK.h63
-rw-r--r--include/OVIDEO.h66
-rw-r--r--include/OVOLUME.h80
-rw-r--r--include/OVQUEUE.h53
-rw-r--r--include/OWALLRES.h119
-rw-r--r--include/OWARPT.h61
-rw-r--r--include/OWEATHER.h155
-rw-r--r--include/OWORLD.h283
-rw-r--r--include/OWORLDMT.h166
-rw-r--r--include/RESOURCE.h36
-rw-r--r--include/WALLTILE.h153
-rw-r--r--opts.pl5
-rw-r--r--targets.pl314
490 files changed, 236660 insertions, 0 deletions
diff --git a/7k.ico b/7k.ico
new file mode 100644
index 0000000..7516afe
--- /dev/null
+++ b/7k.ico
Binary files differ
diff --git a/AM.cpp b/AM.cpp
new file mode 100644
index 0000000..174f92f
--- /dev/null
+++ b/AM.cpp
@@ -0,0 +1,406 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : AM.CPP
+//Description : Ambition Entry Program
+
+#include <windows.h>
+#include <initguid.h>
+#ifdef ENABLE_INTRO_VIDEO
+#include <dshow.h>
+#endif
+
+#include <ALL.h>
+#include <OANLINE.h>
+#include <OAUDIO.h>
+#include <OBATTLE.h>
+#include <OBOX.h>
+#include <OBULLET.h>
+#include <OCONFIG.h>
+#include <ODATE.h>
+#include <OFIRM.h>
+#include <OFLAME.h>
+#include <OFONT.h>
+#include <OGAME.h>
+#include <OGAMESET.h>
+#include <OGFILE.h>
+#include <OGODRES.h>
+#include <OHELP.h>
+#include <OHILLRES.h>
+#include <OIMGRES.h>
+#include <OINFO.h>
+#include <OMONSRES.h>
+#include <OMOUSE.h>
+#include <OMOUSECR.h>
+#include <ONATION.h>
+#include <ONEWS.h>
+#include <OPLANT.h>
+#include <OPOWER.h>
+#include <ORACERES.h>
+#include <OREBEL.h>
+#include <OREMOTE.h>
+#include <OSPATH.h>
+#include <OSPATHS2.h>
+#include <OSITE.h>
+#include <OSPREUSE.h>
+#include <OSPY.h>
+#include <OSYS.h>
+#include <OTALKRES.h>
+#include <OTECHRES.h>
+#include <OTERRAIN.h>
+#include <OTOWN.h>
+#include <OTRANSL.h>
+#include <OUNIT.h>
+#include <OVGA.h>
+#ifdef ENABLE_INTRO_VIDEO
+#include <OVIDEO.h>
+#endif
+#include <OWALLRES.h>
+#include <OWORLD.h>
+#include <OWEATHER.h>
+#include <OTORNADO.h>
+#include <OTUTOR.h>
+#include <OSE.h>
+#include <OSERES.h>
+#include <OSNOWRES.h>
+#include <OSNOWG.h>
+#include <OROCKRES.h>
+#include <OROCK.h>
+#include <OEFFECT.h>
+#include <OEXPMASK.h>
+#include <OREGION.h>
+#include <OWARPT.h>
+#include <ODPLAY.h>
+#include <OIMMPLAY.h>
+#include <OERRCTRL.h>
+#include <OMUSIC.h>
+#include <OLOG.h>
+#include <OLONGLOG.h>
+//### begin alex 3/10 ###//
+#include <OGRPSEL.h>
+//#### end alex 3/10 ####//
+#include <OFIRMDIE.h>
+#include <OCRC_STO.h>
+// ###### begin Gilbert 23/10 #######//
+#include <OOPTMENU.h>
+#include <OINGMENU.h>
+// ###### end Gilbert 23/10 #######//
+
+//------- define game version constant --------//
+
+#ifdef AMPLUS
+ char *GAME_VERSION_STR = "2.13.2";
+ const int GAME_VERSION = 212; // Version 2.00, don't change it unless the format of save game files has been changed
+#else
+ char *GAME_VERSION_STR = "1.11";
+ const int GAME_VERSION = 111; // Version 1.00, don't change it unless the format of save game files has been changed
+#endif
+
+//-------- System class ----------//
+
+#ifndef NO_MEM_CLASS
+ Mem mem; // constructor only init var and allocate memory
+#endif
+
+Error err; // constructor only call set_new_handler()d
+Mouse mouse;
+MouseCursor mouse_cursor;
+Misc m, m2;
+DateInfo date;
+Vga vga;
+VgaBuf vga_front, vga_back, vga_true_front;
+#ifdef ENABLE_INTRO_VIDEO
+Video video;
+#endif
+Audio audio;
+Music music;
+MultiPlayerType mp_obj;
+// MultiPlayerDP mp_dp;
+// MultiPlayerIM mp_im;
+Sys sys;
+Translate translate; // constructor only memset()
+SeekPath seek_path;
+SeekPathS2 seek_path_s2;
+SeekPathReuse seek_path_reuse;
+Flame flame[FLAME_GROW_STEP];
+Remote remote;
+ErrorControl ec_remote;
+AnimLine anim_line;
+SECtrl se_ctrl(&audio);
+SERes se_res;
+Log msg_log;
+#ifdef DEBUG
+LongLog * long_log;
+#endif
+//### begin alex 3/10 ###//
+GroupSelect group_select;
+//#### end alex 3/10 ####//
+
+//------- Resource class ----------//
+
+Font font_san, font_std, font_small, font_mid, font_news;
+Font font_hitpoint, font_bible, font_bard;
+
+#if( defined(GERMAN) || defined(FRENCH) || defined(SPANISH) )
+Font font_hall;
+#endif
+
+Box box;
+ImageRes image_icon, image_interface, image_menu,
+ image_button, image_spict;
+ImageRes image_encyc;
+ImageRes image_tpict;
+ImageRes image_tutorial;
+#ifdef AMPLUS
+ImageRes image_menu_plus;
+ImageRes& image_menu2 = image_menu_plus;
+#else
+ImageRes& image_menu2 = image_menu;
+#endif
+SpriteRes sprite_res;
+SpriteFrameRes sprite_frame_res;
+UnitRes unit_res;
+TerrainRes terrain_res;
+PlantRes plant_res;
+WallRes wall_res;
+RawRes raw_res;
+FirmRes firm_res;
+FirmDieRes firm_die_res;
+RaceRes race_res;
+TownRes town_res;
+HillRes hill_res;
+TalkRes talk_res;
+TechRes tech_res;
+GodRes god_res;
+MonsterRes monster_res;
+SnowRes snow_res;
+RockRes rock_res;
+ExploredMask explored_mask;
+Help help;
+Tutor tutor;
+
+//-------- Game Data class -----------//
+
+UnitArray unit_array(100); // 100-initial array size
+BulletArray bullet_array(100);
+SiteArray site_array;
+TownArray town_array;
+NationArray nation_array;
+FirmArray firm_array;
+FirmDieArray firm_die_array;
+TornadoArray tornado_array(10);
+RebelArray rebel_array;
+SpyArray spy_array;
+SnowGroundArray snow_ground_array;
+RockArray rock_array;
+RockArray dirt_array;
+SpriteArray effect_array(50);
+RegionArray region_array;
+NewsArray news_array;
+WarPointArray war_point_array;
+CrcStore crc_store;
+
+//--------- Game Surface class ------------//
+
+Info info;
+Weather weather, weather_forecast[MAX_WEATHER_FORECAST];
+MagicWeather magic_weather;
+Config config;
+Game game;
+GameSet game_set; // no constructor
+Battle battle;
+Power power;
+World world;
+GameFileArray game_file_array;
+GameFile game_file;
+// ###### begin Gilbert 23/10 #######//
+OptionMenu option_menu;
+InGameMenu in_game_menu;
+// ###### end Gilbert 23/10 #######//
+
+//----------- Global Variables -----------//
+
+char game_design_mode=0;
+char game_demo_mode=0;
+char debug2_enable_flag=0;
+File seedCompareFile;
+char debug_seed_status_flag=0;
+int debug_sim_game_type = 0;
+int unit_search_node_used=0;
+short nation_hand_over_flag=0;
+int unit_search_tries = 0; // the number of tries used in the current searching
+char unit_search_tries_flag = 0; // indicate num of tries is set, reset after searching
+
+char new_config_dat_flag=0;
+
+#ifdef DEBUG
+int check_unit_dir1, check_unit_dir2;
+unsigned long last_unit_ai_profile_time = 0L;
+unsigned long unit_ai_profile_time = 0L;
+unsigned long last_unit_profile_time = 0L;
+unsigned long unit_profile_time = 0L;
+unsigned long seek_path_profile_time = 0L;
+unsigned long last_seek_path_profile_time = 0L;
+unsigned long last_sprite_array_profile_time = 0L;
+unsigned long sprite_array_profile_time = 0L;
+unsigned long last_sprite_idle_profile_time = 0L;
+unsigned long sprite_idle_profile_time = 0L;
+unsigned long last_sprite_move_profile_time = 0L;
+unsigned long sprite_move_profile_time = 0L;
+unsigned long last_sprite_wait_profile_time = 0L;
+unsigned long sprite_wait_profile_time = 0L;
+unsigned long last_sprite_attack_profile_time = 0L;
+unsigned long sprite_attack_profile_time = 0L;
+unsigned long last_unit_attack_profile_time = 0L;
+unsigned long unit_attack_profile_time = 0L;
+unsigned long last_unit_assign_profile_time = 0L;
+unsigned long unit_assign_profile_time = 0L;
+#endif
+
+//------- Define static functions --------//
+
+static void extra_error_handler();
+
+//---------- Begin of function WinMain ----------//
+//
+// WinMain - initialization, message loop
+//
+// Compilation constants:
+//
+// DEBUG - normal debugging
+// DEBUG2 - shortest path searching and unit action debugging
+// DEBUG3 - debugging some functions (e.g. Location::get_loc()) which
+// will cause major slowdown.
+//
+int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
+ LPSTR lpCmdLine, int nCmdShow)
+{
+ //try to read from CONFIG.DAT, moved to AM.CPP
+
+ if( !config.load("CONFIG.DAT") )
+ {
+ new_config_dat_flag = 1;
+ config.init();
+ }
+
+ //--------------------------------------//
+
+
+#ifdef IMAGICMP
+ static char lobbyLaunchCmdLine[] = "IM";
+#else
+ static char lobbyLaunchCmdLine[] = "-!lobby!";
+#endif
+
+#ifdef ENABLE_INTRO_VIDEO
+ //----------- play movie ---------------//
+
+ sys.set_game_dir();
+ if( strstr(lpCmdLine, lobbyLaunchCmdLine) == NULL ) // skip if launch from lobby
+ {
+ String movieFileStr;
+ movieFileStr = DIR_MOVIE;
+ movieFileStr += "INTRO.AVI";
+
+ video.set_skip_on_fail();
+
+ // ###### begin Gilbert 29/10 #####//
+ if( !m.is_file_exist("SKIPAVI.SYS") && m.is_file_exist(movieFileStr) )
+ // ###### end Gilbert 29/10 #####//
+ {
+ //---------- play the movie now ---------//
+
+ video.init();
+
+ if( video.init_success )
+ {
+ video.play_until_end( movieFileStr, hInstance, 60 );
+ }
+ else
+ {
+ // display a message box (note:sys.main_hwnd is not valid)
+ // MessageBox( NULL, "Cannot initialize ActiveMovie",
+ // "Seven Kingdoms", MB_OK | MB_ICONWARNING | MB_DEFBUTTON1 | MB_TASKMODAL );
+ }
+
+ video.deinit();
+ }
+ }
+#endif // ENABLE_INTRO_VIDEO
+
+ if( !sys.init(hInstance) )
+ return FALSE;
+
+ err.set_extra_handler( extra_error_handler ); // set extra error handler, save the game when a error happens
+
+#ifdef DEMO
+ game.demo_disp_logo();
+ game.main_menu();
+#else
+ if( strstr(lpCmdLine, lobbyLaunchCmdLine) == NULL )
+ game.main_menu();
+#ifndef DISABLE_MULTI_PLAYER
+ else
+ game.multi_player_menu(lpCmdLine); // if detect launched from lobby
+#endif // DISABLE_MULTI_PLAYER
+#endif
+
+ sys.deinit();
+
+ return 1;
+}
+//---------- End of function WinMain ----------//
+
+
+//----------- Begin of function Msg ----------//
+//
+// Routine for displaying debug messages
+//
+
+#ifdef DEBUG
+
+void __cdecl debug_msg( char* fmt, ... )
+{
+ char buff[256];
+
+ lstrcpy( buff, "SEVEN KINGDOMS: " );
+ wvsprintf( buff+lstrlen(buff), fmt, (char*)(&fmt+1) );
+ lstrcat( buff, "\r\n" );
+
+ OutputDebugString( buff );
+}
+
+#endif
+
+//----------- End of function Msg ----------//
+
+
+//------- Begin of function extra_error_handler -----------//
+
+static void extra_error_handler()
+{
+ if( game.game_mode != GAME_SINGLE_PLAYER )
+ return;
+
+ game_file_array.save_new_game("ERROR.SAV"); // save a new game immediately without prompting menu
+
+ box.msg( "Error encountered. The game has been saved to ERROR.SAV" );
+}
+//----------- End of function extra_error_handler -------------//
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..623b625
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/OAI_ACT.cpp b/OAI_ACT.cpp
new file mode 100644
index 0000000..085b79d
--- /dev/null
+++ b/OAI_ACT.cpp
@@ -0,0 +1,746 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_ACT.CPP
+//Description: AI - action progressing functions
+
+#include <ALL.h>
+#include <OBOX.h>
+#include <OSYS.h>
+#include <OCONFIG.h>
+#include <OINFO.h>
+#include <OU_MARI.h>
+#include <ONATION.h>
+
+//------- Begin of function Nation::process_action --------//
+//
+// [int] priorityActionRecno - if this is given, this specific action will
+// be processed first. Otherwise it will process
+// actions in the array in a sequential order.
+//
+// [int] processActionMode - if this is given, only message of this type
+// will be processed and all messages in the queued of this
+// type will be processed.
+//
+// Note: priorityActionRecno and processActionMode couldn't be used at
+// the same time.
+//
+// return: <int> 1 - all messages of the specific action mode are processed or all messages are processed
+// or the priority message has been processed.
+//
+int Nation::process_action(int priorityActionRecno, int processActionMode)
+{
+ err_when( priorityActionRecno && processActionMode );
+
+ // #define MAX_PROCESS_ACTION_TIME 0.01 // maximum time given to processing actions (second)
+ // unsigned expireTime = m.get_time() + (unsigned)(MAX_PROCESS_ACTION_TIME*1000);
+
+ int actionRecno, rc, delFlag, doneFlag=0;
+ int thisSessionProcessCount=0; // actions processed in this call session
+ ActionNode* actionNode;
+
+ int divider = 4-config.ai_aggressiveness; // the more nations there, the less process count
+ int nationRecno = nation_recno;
+ int maxSessionProcessCount = 70 / nation_array.nation_count / max(divider,1);
+
+ for( actionRecno=1 ; actionRecno<=action_count() &&
+ (thisSessionProcessCount < maxSessionProcessCount || processActionMode) && !doneFlag ; // if processActionMode has been specific, then all messages in the queue of this type will be processed
+ actionRecno++ )
+ {
+ //----- priority action ------//
+
+ if( priorityActionRecno )
+ {
+ actionRecno = priorityActionRecno;
+ doneFlag = 1; // mark it done, so if the function "continue" to the next loop, the function will end
+ }
+
+ actionNode = get_action(actionRecno);
+
+ //----- if only process specific action mode -----//
+
+ if( processActionMode && actionNode->action_mode != processActionMode )
+ continue;
+
+ //--- if the AI action is about processing diplomatic message ---//
+
+ if( actionNode->action_mode == ACTION_AI_PROCESS_TALK_MSG &&
+ processActionMode != ACTION_AI_PROCESS_TALK_MSG )
+ {
+ if( m.random(10) > 0 ) // 1/10 chance of processing the diplomatic messages
+ continue;
+ }
+
+ //----------------------------------------------//
+
+ if( actionNode->processing_instance_count == actionNode->instance_count )
+ {
+ //---------------------------------------------//
+ //
+ // If this action has been marked processing for over 6 months
+ // and we still haven't received finishing notifications,
+ // then there may be some accidents (or bugs) happened, and
+ // we will need to delete the action.
+ //
+ //---------------------------------------------//
+
+ if( info.game_date > actionNode->add_date + 30 * 6 )
+ {
+ del_action(actionRecno);
+ actionRecno--; // stay in this array position as the current one has been deleted, the following one replace the current one's position
+ }
+
+ continue;
+ }
+
+ err_when( actionNode->processing_instance_count > actionNode->instance_count );
+
+ if( info.game_date < actionNode->next_retry_date && !priorityActionRecno ) // priorityAction bypass retry date checking
+ continue;
+
+ if( actionNode->retry_count==0 ) // the actionNode may still exist even when retry_count==0, waiting for processed_count to reach processing_count
+ continue;
+
+ //-- there is an unprocessing action in this waiting node --//
+
+ switch( actionNode->action_mode )
+ {
+ case ACTION_AI_BUILD_FIRM:
+ rc = ai_build_firm(actionNode);
+ break;
+
+ case ACTION_AI_ASSIGN_OVERSEER:
+ rc = ai_assign_overseer(actionNode);
+ break;
+
+ case ACTION_AI_ASSIGN_CONSTRUCTION_WORKER:
+ rc = ai_assign_construction_worker(actionNode);
+ break;
+
+ case ACTION_AI_ASSIGN_WORKER:
+ rc = ai_assign_worker(actionNode);
+ break;
+
+ case ACTION_AI_ASSIGN_SPY:
+ rc = ai_assign_spy(actionNode);
+ break;
+
+ case ACTION_AI_SCOUT:
+ rc = ai_scout(actionNode);
+ break;
+
+ case ACTION_AI_SETTLE_TO_OTHER_TOWN:
+ rc = ai_settle_to_other_town(actionNode);
+ break;
+
+ case ACTION_AI_PROCESS_TALK_MSG:
+ rc = ai_process_talk_msg(actionNode);
+ break;
+
+ case ACTION_AI_SEA_TRAVEL:
+ rc = ai_sea_travel(actionNode);
+ break;
+
+ case ACTION_AI_SEA_TRAVEL2:
+ rc = ai_sea_travel2(actionNode);
+ break;
+
+ case ACTION_AI_SEA_TRAVEL3:
+ rc = ai_sea_travel3(actionNode);
+ break;
+ }
+
+ if( nation_array.is_deleted(nationRecno) ) // diplomatic option can result in surrendering
+ return 0;
+
+ thisSessionProcessCount++;
+
+ //------ check the return result -------//
+
+ delFlag = 0;
+
+ if( rc==1 ) // the action has been processed, but not sure whether it is complete or not
+ {
+ actionNode->processing_instance_count++;
+
+ //---------------------------------------------------//
+ // for ACTION_DYNAMIC, the action is immediately
+ // deleted when processing_instance_count == instance_count.
+ //---------------------------------------------------//
+
+ if( actionNode->action_type == ACTION_DYNAMIC )
+ {
+ if( actionNode->processing_instance_count > actionNode->instance_count )
+ delFlag = 1;
+ }
+ }
+ else if( rc==0 ) // action failed, retry
+ {
+ actionNode->next_retry_date = info.game_date + 7; // try again one week later
+
+ if( --actionNode->retry_count==0 )
+ delFlag = 1;
+
+ err_when( actionNode->retry_count < 0 );
+ }
+ else if( rc== -1 ) // action failed, remove immediately if return -1
+ {
+ actionNode->retry_count = 0;
+ delFlag = 1;
+ }
+
+ //-----------------------------------------//
+
+ if( delFlag && actionNode->processing_instance_count == actionNode->processed_instance_count ) // if processing_count > processed_count, do not remove this ActionNode, as there are some unit using this actionNode, when they finish or fail the action, processed_count will increase and processing_count will reach processed_count
+ {
+ del_action(actionRecno);
+ actionRecno--; // stay in this array position as the current one has been deleted, the following one replace the current one's position
+ }
+ }
+
+ return actionRecno > action_count() || doneFlag;
+}
+//---------- End of function Nation::process_action --------//
+
+
+//------- Begin of function Nation::process_action_id --------//
+//
+// Process a specific action.
+//
+int Nation::process_action_id(int actionId)
+{
+ for( int i=action_count() ; i>0 ; i-- )
+ {
+ if( get_action(i)->action_id == actionId )
+ {
+ process_action(i);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+//---------- End of function Nation::process_action_id --------//
+
+
+//------- Begin of function Nation::get_action_based_on_id --------//
+//
+// Return ActionNode for the given actionId.
+//
+ActionNode* Nation::get_action_based_on_id(int actionId)
+{
+ for( int i=action_count() ; i>0 ; i-- )
+ {
+ if( get_action(i)->action_id == actionId )
+ {
+ return get_action(i);
+ }
+ }
+
+ return 0;
+}
+//---------- End of function Nation::get_action_based_on_id --------//
+
+
+//--------- Begin of function Nation::add_action --------//
+//
+// <short> xLoc, yLoc - location of the action
+// <short> refXLoc, refYLoc - reference location (optional, not all action types need this)
+// <int> actionMode - action mode
+// <int> actionPara - action parameter
+// [int] instanceCount - no. of instances of this action should be carried out
+// (default: 1)
+// [int] unitRecno - recno of the unit responsible for this action
+// if not given, an appropriate unit will be found.
+// [int] actionPara2 - action para2
+// [short*] groupUnitArray - array of unit recno in the group for the action
+// the no. of units in the array is stored in instance_count
+// (default: NULL)
+//
+// return: <int> recno of the action added in action_array
+// 0 - if the action is not added as it is already in action_array.
+//
+int Nation::add_action(short xLoc, short yLoc, short refXLoc, short refYLoc,
+ int actionMode, int actionPara, int instanceCount,
+ int unitRecno, int actionPara2, short* groupUnitArray)
+{
+ err_when( instanceCount < 1 );
+
+ //--- check if the action has been added already or not ---//
+
+ if( is_action_exist(xLoc, yLoc, refXLoc, refYLoc, actionMode, actionPara, unitRecno) )
+ return 0;
+
+ //---------- queue the action ----------//
+
+ ActionNode actionNode;
+
+ memset( &actionNode, 0, sizeof(ActionNode) );
+
+ actionNode.action_mode = actionMode; // what kind of action
+ actionNode.action_para = actionPara; // parameter of the action
+ actionNode.action_para2 = actionPara2; // parameter of the action
+ actionNode.action_x_loc = xLoc; // location to act to
+ actionNode.action_y_loc = yLoc;
+ actionNode.ref_x_loc = refXLoc; // the refective location of this action make to
+ actionNode.ref_y_loc = refYLoc;
+ actionNode.retry_count = STD_ACTION_RETRY_COUNT; // number of term to wait before discarding this action
+ actionNode.instance_count = instanceCount; // num of this action being processed in the waiting queue
+
+ int immediateProcess=0;
+
+ if( groupUnitArray )
+ {
+ // the no. of units in the array is stored in instance_count
+
+ err_when( instanceCount < 1 );
+ err_when( instanceCount > ActionNode::MAX_ACTION_GROUP_UNIT );
+
+ memcpy( actionNode.group_unit_array, groupUnitArray, instanceCount * sizeof(groupUnitArray[0]) );
+
+ immediateProcess = 1; // have to execute this command immediately as the unit in unit_array[] may change
+ actionNode.retry_count = 1; // only try once as the unit in unit_array[] may change
+ }
+
+ if( unitRecno )
+ {
+ //-- this may happen when the unit is a spy and has just changed cloak --//
+
+ if( !nation_array[unit_array[unitRecno]->true_nation_recno()]->is_ai() &&
+ !nation_array[unit_array[unitRecno]->nation_recno]->is_ai() )
+ {
+ return 0;
+ }
+
+ //-------------------------------------//
+
+ actionNode.unit_recno = unitRecno;
+
+ if( unit_array[unitRecno]->is_visible() )
+ {
+ immediateProcess = 1; // have to execute this command immediately as the unit in unit_array[] may change
+ actionNode.retry_count = 1; // only try once as the unit in unit_array[] may change
+ }
+ else //--- the unit is still being trained ---//
+ {
+ actionNode.next_retry_date = info.game_date + TOTAL_TRAIN_DAYS + 1;
+ }
+ }
+
+ //-------- set action type ---------//
+
+ actionNode.action_type = ACTION_FIXED; // default action type
+
+ //------- link into action_array --------//
+
+ return add_action( &actionNode, immediateProcess );
+}
+//---------- End of function Nation::add_action --------//
+
+
+//--------- Begin of function Nation::add_action --------//
+//
+// <ActionNode*> actionNode - the action node to be added.
+// [int] immediateProcess - process this action immediately
+//
+int Nation::add_action(ActionNode* actionNode, int immediateProcess)
+{
+ //----------- reset some vars -----------//
+
+ actionNode->add_date = info.game_date;
+ actionNode->action_id = ++last_action_id;
+ actionNode->retry_count = STD_ACTION_RETRY_COUNT;
+
+ actionNode->processing_instance_count = 0;
+ actionNode->processed_instance_count = 0;
+
+ //------- link into action_array --------//
+
+ action_array.linkin( actionNode );
+
+ if( immediateProcess )
+ process_action( action_array.recno() );
+
+ return action_array.recno();
+}
+//---------- End of function Nation::add_action --------//
+
+
+//--------- Begin of function Nation::del_action --------//
+//
+void Nation::del_action(int actionRecno)
+{
+ action_array.linkout(actionRecno);
+}
+//---------- End of function Nation::del_action --------//
+
+
+//--------- Begin of function Nation::is_action_exist --------//
+//
+// <short> actionXLoc, actionYLoc - action_?_loc in ActionNode to match with
+// <short> refXLoc, refYLoc - ref_?_loc in ActionNode to match with
+// <int> actionMode - action mode
+// <int> actionPara - parameter of the action
+// [int] unitRecno - unit recno to match with, only useful for actions under processing.
+// [int] checkMode - 1-check actionXLoc & actionYLoc only
+// 2-check refXLoc & refYLoc only
+// 0-check both
+// (default: 0)
+//
+// return: <int> >0 if the action recno of the existing action
+// ==0 if not exist
+//
+int Nation::is_action_exist(short actionXLoc, short actionYLoc, short refXLoc, short refYLoc, int actionMode, int actionPara, int unitRecno, int checkMode)
+{
+ int i;
+ ActionNode* actionNode;
+
+ for( i=action_count() ; i>0 ; i-- )
+ {
+ actionNode = get_action(i);
+
+ if( actionNode->action_mode == actionMode &&
+ actionNode->action_para == actionPara )
+ {
+ if( unitRecno && unitRecno != actionNode->unit_recno ) // it requests to match the unit recno and it is not matched here
+ continue;
+
+ if( refXLoc>=0 )
+ {
+ if( checkMode==0 || checkMode==2 )
+ {
+ if( actionNode->ref_x_loc==refXLoc && actionNode->ref_y_loc==refYLoc)
+ return i;
+ }
+ }
+ else
+ {
+ if( checkMode==0 || checkMode==1 )
+ {
+ if( actionNode->action_x_loc==actionXLoc && actionNode->action_y_loc==actionYLoc )
+ return i;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+//---------- End of function Nation::is_action_exist --------//
+
+
+//--------- Begin of function Nation::is_action_exist --------//
+//
+// Check if the an action of the specific mode and para
+// exists in the action_array.
+//
+// <int> actionMode - action mode
+// <int> actionPara - parameter of the action
+// [int] regionId - if this parameter is given, only
+// action with destination in this
+// region will be checked.
+//
+int Nation::is_action_exist(int actionMode, int actionPara, int regionId)
+{
+ int i;
+ ActionNode* actionNode;
+
+ for( i=action_count() ; i>0 ; i-- )
+ {
+ actionNode = get_action(i);
+
+ if( actionNode->action_mode == actionMode &&
+ actionNode->action_para == actionPara )
+ {
+ if( !regionId )
+ return 1;
+
+ err_when( actionNode->action_x_loc < 0 ||
+ actionNode->action_y_loc < 0 ||
+ actionNode->action_x_loc >= MAX_WORLD_X_LOC ||
+ actionNode->action_y_loc >= MAX_WORLD_Y_LOC );
+
+ if( world.get_region_id(actionNode->action_x_loc,
+ actionNode->action_y_loc) == regionId )
+ {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+//---------- End of function Nation::is_action_exist --------//
+
+
+//--------- Begin of function Nation::is_build_action_exist --------//
+//
+// Return 1 if there is already a firm queued for building with
+// a building location that is within the effective range
+// of the given position.
+//
+int Nation::is_build_action_exist(int firmId, int xLoc, int yLoc)
+{
+ int i;
+ ActionNode* actionNode;
+
+ for( i=action_count() ; i>0 ; i-- )
+ {
+ actionNode = get_action(i);
+
+ if( actionNode->action_mode == ACTION_AI_BUILD_FIRM &&
+ actionNode->action_para == firmId )
+ {
+ if( m.points_distance( actionNode->action_x_loc, actionNode->action_y_loc,
+ xLoc, yLoc) <= EFFECTIVE_FIRM_TOWN_DISTANCE )
+ {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+//---------- End of function Nation::is_build_action_exist --------//
+
+
+//--------- Begin of function Nation::action_finished --------//
+//
+// The action under processing is finished successfully.
+//
+// <WORD> aiActionId - the id. of the action to be marked finished.
+// [short] unitRecno - if this is given, the the unit's all action
+// will be stopped.
+// [int] actionFailure - whether the action is failed and called by
+// action_failure(). (default: 0)
+//
+void Nation::action_finished(WORD aiActionId, short unitRecno, int actionFailure)
+{
+ //----- locate the ActionNode of this action ------//
+
+ int actionRecno;
+ ActionNode* actionNode;
+
+ //----- try to match actions by unitRecno first ----//
+
+ for( actionRecno=action_count() ; actionRecno>0 ; actionRecno-- )
+ {
+ actionNode = get_action(actionRecno);
+
+ if( aiActionId == actionNode->action_id )
+ break;
+ }
+
+ if( actionRecno==0 ) // not found
+ {
+// if( sys.debug_session && !sys.signal_exit_flag )
+// box.msg( "Error: action_finished() - entry not found." );
+
+ stop_unit_action(unitRecno);
+ return;
+ }
+
+ //------------------------------------------------//
+ //
+ // In the above condition is true, that means this ship
+ // unit has called this function once and the current
+ // calling is a duplicated calling.
+ //
+ //------------------------------------------------//
+
+ int shouldStop=1;
+
+ if( actionNode->action_mode == ACTION_AI_SEA_TRAVEL ) // don't reset the unit's ai_action_id in ACTION_AI_SEA_TRAVEL mode as if we reset it, the ship will take new action and won't wait for the units to go aboard.
+ {
+ if( !unit_array.is_deleted(unitRecno) &&
+ unit_res[ unit_array[unitRecno]->unit_id ]->unit_class == UNIT_CLASS_SHIP )
+ {
+ if( actionNode->action_para2 )
+ {
+ return;
+ }
+ else
+ {
+ actionNode->action_para2 = unitRecno;
+ shouldStop = 0;
+ }
+ }
+ }
+
+ //---------------------------------------------//
+ //
+ // Only handle ACTION_FIXED, for ACTION_DYNAMIC,
+ // the action is immediately deleted when
+ // processing_instance_count == instance_count.
+ //
+ //---------------------------------------------//
+
+ if( actionNode->action_type != ACTION_FIXED )
+ {
+ stop_unit_action(unitRecno);
+ return;
+ }
+
+ //-------------------------------------------------//
+
+ actionNode->processed_instance_count++;
+
+ err_when( actionNode->processed_instance_count > actionNode->processing_instance_count );
+ err_when( actionNode->processed_instance_count > actionNode->instance_count );
+
+ //---- if all requested instances are processed ----//
+
+ int allDoneFlag=0;
+
+ if( actionNode->processed_instance_count >= actionNode->instance_count )
+ allDoneFlag = 1;
+
+ //---- if the action is failed and all the outstanding units are finished, del the action ---//
+
+ else if( actionNode->retry_count==0 &&
+ actionNode->processed_instance_count >= actionNode->processing_instance_count )
+ {
+ allDoneFlag = 1;
+ }
+
+ //------- stop the AI actions of the unit -----//
+
+ if( shouldStop )
+ stop_unit_action(unitRecno);
+
+ //---- if the action is done, see if there needs to be a following action ----//
+
+ if( allDoneFlag )
+ {
+ auto_next_action(actionNode);
+ del_action(actionRecno);
+ }
+}
+//---------- End of function Nation::action_finished --------//
+
+
+//--------- Begin of function Nation::action_failure --------//
+//
+// It's basically the same as action_finish(). Now there isn't any
+// difference.
+//
+// <WORD> aiActionId - the id. of the action to be marked finished.
+// [short] unitRecno - if this is given, the the unit's all action
+// will be stopped.
+//
+void Nation::action_failure(WORD aiActionId, short unitRecno)
+{
+ //-- if the unit is a ship, ignore it as it will be called by stop2() when it stops, but hasn't yet finished its action --//
+/*
+ Unit* unitPtr = unit_array[unitRecno];
+
+ if( unit_res[unitPtr->unit_id]->unit_class == UNIT_CLASS_SHIP )
+ {
+ int actionMode = get_action_based_on_id(aiActionId)->action_mode;
+
+ if( actionMode == ACTION_AI_SEA_TRAVEL ||
+ actionMode == ACTION_AI_SEA_TRAVEL2 )
+ {
+ return;
+ }
+ }
+*/
+ //------------------------------------------------//
+
+ action_finished(aiActionId, unitRecno, 1); // 1 - action failure
+}
+//---------- End of function Nation::action_failure ---------//
+
+
+//--------- Begin of function Nation::stop_unit_action --------//
+
+void Nation::stop_unit_action(short unitRecno)
+{
+ if( !unitRecno )
+ return;
+
+ //------- stop the AI actions of the unit -----//
+ //
+ // It is possible that this is not an AI unit as
+ // when a player spy cloaked as an enemy unit,
+ // the AI will control it.
+ //
+ //---------------------------------------------//
+
+ if( !unit_array.is_deleted(unitRecno) )
+ {
+ Unit* unitPtr = unit_array[unitRecno];
+
+ unitPtr->ai_action_id = 0;
+
+ //---- if the unit is a ship on the beach and it's mode isn't NO_EXTRA_MOVE, we couldn't call stop2() as that will cause bug ---//
+
+ if(unitPtr->action_mode2==ACTION_SHIP_TO_BEACH)
+ {
+ UnitMarine *shipPtr = (UnitMarine*) unitPtr;
+
+ err_when( unit_res[shipPtr->unit_id]->unit_class != UNIT_CLASS_SHIP );
+
+ if( shipPtr->extra_move_in_beach != NO_EXTRA_MOVE )
+ return;
+ }
+
+ //--------------------------------------------------//
+
+ unitPtr->stop2();
+ unitPtr->reset_action_misc_para();
+
+ err_when(unitPtr->in_auto_defense_mode());
+ err_when(unitPtr->action_x_loc!=-1 || unitPtr->action_y_loc!=-1);
+ err_when(unitPtr->action_mode!=ACTION_STOP);
+ }
+}
+//---------- End of function Nation::stop_unit_action ---------//
+
+
+//--------- Begin of function Nation::auto_next_action --------//
+//
+// Automatically create a follow-up action to this action
+// if this action needs one.
+//
+void Nation::auto_next_action(ActionNode* actionNode)
+{
+ int actionRecno=0;
+
+ switch( actionNode->action_mode )
+ {
+ case ACTION_AI_SEA_TRAVEL:
+ actionNode->action_mode = ACTION_AI_SEA_TRAVEL2;
+ actionNode->instance_count = 1; // only move one ship, it was previously set to the no. units to aboard the ship
+ actionRecno = add_action(actionNode, 1); // 1-immediate process flag
+ break;
+
+ case ACTION_AI_SEA_TRAVEL2:
+ actionNode->action_mode = ACTION_AI_SEA_TRAVEL3;
+ actionRecno = add_action(actionNode, 1); // 1-immediate process flag
+ break;
+ }
+
+ if( actionRecno )
+ process_action(actionRecno);
+}
+//---------- End of function Nation::auto_next_action ---------//
+
+
diff --git a/OAI_ACT2.cpp b/OAI_ACT2.cpp
new file mode 100644
index 0000000..59c371f
--- /dev/null
+++ b/OAI_ACT2.cpp
@@ -0,0 +1,496 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_ACT.CPP
+//Description: AI - action progressing functions
+
+#include <ALL.h>
+#include <OBOX.h>
+#include <OSYS.h>
+#include <OSPY.h>
+#include <OINFO.h>
+#include <OUNIT.h>
+#include <OFIRM.h>
+#include <ONATION.h>
+
+
+//-------- Begin of function Nation::ai_build_firm -------//
+//
+// action_x_loc, action_y_loc - location of the building site.
+// ref_x_loc, ref_y_loc - location of the town which the base
+// should be built next to.
+// action_para - firm id.
+// action_para2 - firm race id. (for FirmBase only)
+//
+int Nation::ai_build_firm(ActionNode* actionNode)
+{
+ if(!seek_path.total_node_avail)
+ return 0;
+
+ err_when( actionNode->action_x_loc < 0 || actionNode->action_y_loc < 0 ); // they must be given when this function is called
+
+ //-------- determine the skill id. needed -------//
+
+ int firmId = actionNode->action_para;
+ int raceId = actionNode->action_para2;
+ int skillId = firm_res[firmId]->firm_skill_id;
+
+ if( !skillId ) // if the firm does not have a specific skill (e.g. Inn), use the general SKILL_CONSTRUCTION
+ skillId = SKILL_CONSTRUCTION; // for military camps, use construction workers instead of generals for facilitating migration of military camps
+
+ //---- if there are camps that should be closed available now, transfer soldiers there to the new camp, ask a construction worker to build the camp so we can transfer the whole troop to the new camp ---//
+
+ if( firmId==FIRM_CAMP &&
+ ai_has_should_close_camp( world.get_region_id( actionNode->action_x_loc, actionNode->action_y_loc ) ) )
+ {
+ skillId = SKILL_CONSTRUCTION; // for military camps, use construction workers instead of generals for facilitating migration of military camps
+ }
+
+ //------------- get a skilled unit --------------//
+
+ Unit* skilledUnit = get_skilled_unit(skillId, raceId, actionNode);
+
+ if( !skilledUnit )
+ return 0;
+
+ //--- a unit with the specific skill is not found, try to find a construction unit instead ---//
+
+ if( skillId != SKILL_CONSTRUCTION )
+ {
+ Unit* skilledUnit = get_skilled_unit(skillId, raceId, actionNode);
+
+ if( !skilledUnit )
+ return 0;
+ }
+
+ //------- build the firm now ---------//
+
+ skilledUnit->build_firm(actionNode->action_x_loc, actionNode->action_y_loc, firmId, COMMAND_AI);
+
+ if(skilledUnit->action_x_loc==actionNode->action_x_loc && skilledUnit->action_y_loc==actionNode->action_y_loc)
+ {
+ err_when( skilledUnit->nation_recno==0 );
+
+ skilledUnit->ai_action_id = actionNode->action_id;
+ actionNode->unit_recno = skilledUnit->sprite_recno;
+
+ return 1;
+ }
+ else
+ {
+ skilledUnit->stop2();
+ return 0;
+ }
+}
+//-------- End of function Nation::ai_build_firm -------//
+
+
+//----- Begin of function Nation::ai_assign_overseer -----//
+//
+// action_x_loc, action_y_loc - location of the firm to which the overseer should be assigned.
+// ref_x_loc, ref_y_loc - not used
+// action_para - firm id.
+// [action_para2] - race id of the overseer
+// (if not given, the majority race will be used.)
+//
+int Nation::ai_assign_overseer(ActionNode* actionNode)
+{
+ //---------------------------------------------------------------------------//
+ // cancel action if the firm is deleted, has incorrect firm_id or nation is
+ // changed
+ //---------------------------------------------------------------------------//
+
+ int firmId = actionNode->action_para;
+
+ err_when( !firmId );
+
+ if(!check_firm_ready(actionNode->action_x_loc, actionNode->action_y_loc, firmId)) // return 0 to cancel action
+ return -1; // -1 means remove the current action immediately
+
+ if(!seek_path.total_node_avail)
+ return 0;
+
+ //-------- get the poisnter to the firm -------//
+
+ Location* locPtr = world.get_loc(actionNode->action_x_loc, actionNode->action_y_loc);
+
+ err_when(!locPtr->is_firm());
+
+ Firm* firmPtr = firm_array[ locPtr->firm_recno() ];
+
+ //-------- get a skilled unit --------//
+
+ int raceId; // the race of the needed unit
+
+ if( actionNode->action_para2 )
+ {
+ raceId = actionNode->action_para2;
+ }
+ else
+ {
+ if( firmPtr->firm_id == FIRM_BASE ) // for seat of power, the race must be specific
+ raceId = firm_res.get_build(firmPtr->firm_build_id)->race_id;
+ else
+ raceId = firmPtr->majority_race();
+ }
+
+ Unit* skilledUnit = get_skilled_unit(SKILL_LEADING, raceId, actionNode);
+
+ if( !skilledUnit )
+ return 0;
+
+ //---------------------------------------------------------------------------//
+
+ FirmInfo* firmInfo = firm_res[firmId];
+
+ err_when( !firmInfo->need_overseer );
+
+ if(skilledUnit->rank_id==RANK_SOLDIER)
+ skilledUnit->set_rank(RANK_GENERAL);
+
+ skilledUnit->assign(actionNode->action_x_loc, actionNode->action_y_loc);
+ skilledUnit->ai_action_id = actionNode->action_id;
+
+ err_when( skilledUnit->nation_recno != nation_recno );
+
+ actionNode->unit_recno = skilledUnit->sprite_recno;
+
+ return 1;
+}
+//----- End of function Nation::ai_assign_overseer -----//
+
+
+//----- Begin of function Nation::ai_assign_construction_worker -----//
+//
+// action_x_loc, action_y_loc - location of the firm to which the overseer should be assigned.
+// ref_x_loc, ref_y_loc - not used
+//
+int Nation::ai_assign_construction_worker(ActionNode* actionNode)
+{
+ //---------------------------------------------------------------------------//
+ // cancel action if the firm is deleted, has incorrect firm_id or nation is
+ // changed
+ //---------------------------------------------------------------------------//
+
+ if(!check_firm_ready(actionNode->action_x_loc, actionNode->action_y_loc)) // return 0 to cancel action
+ return -1; // -1 means remove the current action immediately
+
+ if(!seek_path.total_node_avail)
+ return 0;
+
+ //-------- get the poisnter to the firm -------//
+
+ Location* locPtr = world.get_loc(actionNode->action_x_loc, actionNode->action_y_loc);
+
+ err_when(!locPtr->is_firm());
+
+ Firm* firmPtr = firm_array[ locPtr->firm_recno() ];
+
+ if( firmPtr->builder_recno ) // if the firm already has a construction worker
+ return -1;
+
+ //-------- get a skilled unit --------//
+
+ Unit* skilledUnit = get_skilled_unit(SKILL_CONSTRUCTION, 0, actionNode);
+
+ if( !skilledUnit )
+ return 0;
+
+ //------------------------------------------------------------------//
+
+ skilledUnit->assign(actionNode->action_x_loc, actionNode->action_y_loc);
+ skilledUnit->ai_action_id = actionNode->action_id;
+
+ err_when( skilledUnit->nation_recno != nation_recno );
+
+ actionNode->unit_recno = skilledUnit->sprite_recno;
+
+ return 1;
+}
+//----- End of function Nation::ai_assign_construction_worker -----//
+
+
+//----- Begin of function Nation::ai_assign_worker -----//
+//
+// action_x_loc, action_y_loc - location of the firm to which the overseer should be assigned.
+// ref_x_loc, ref_y_loc - not used
+// action_para - firm id.
+// [action_para2] - race id of the overseer
+// (if not given, the majority race will be used.)
+//
+int Nation::ai_assign_worker(ActionNode* actionNode)
+{
+ //---------------------------------------------------------------------------//
+ // cancel action if the firm is deleted, has incorrect firm_id or nation is
+ // changed
+ //---------------------------------------------------------------------------//
+
+ int firmId = actionNode->action_para;
+
+ err_when( !firmId );
+
+ if(!check_firm_ready(actionNode->action_x_loc, actionNode->action_y_loc, firmId)) // return 0 to cancel action
+ return -1; // -1 means remove the current action immediately
+
+ if(!seek_path.total_node_avail)
+ return 0;
+
+ //---------------------------------------------------------------------------//
+ // cancel this action if the firm already has enough workers
+ //---------------------------------------------------------------------------//
+
+ Location* locPtr = world.get_loc(actionNode->action_x_loc, actionNode->action_y_loc);
+
+ err_when(!locPtr->is_firm());
+
+ Firm* firmPtr = firm_array[locPtr->firm_recno()];
+
+ err_when( firmPtr->firm_id != firmId );
+ err_when( !firm_res[firmPtr->firm_id]->need_worker );
+
+ if(firmPtr->worker_count>=MAX_WORKER)
+ return -1;
+
+ if( MAX_WORKER - firmPtr->worker_count < actionNode->instance_count ) // if the firm now has more workers, reduce the number needed to be assigned to the firm
+ {
+ err_when( actionNode->processing_instance_count >= actionNode->instance_count );
+ actionNode->instance_count = max( actionNode->processing_instance_count+1, MAX_WORKER - firmPtr->worker_count );
+ }
+
+ //---------------------------------------------------------------------------//
+ // firm exists and belongs to our nation. Assign worker to firm
+ //---------------------------------------------------------------------------//
+
+ int unitRecno=0;
+ Unit* unitPtr = NULL;
+
+ //----------- use a trained unit --------//
+
+ if( actionNode->unit_recno )
+ unitPtr = unit_array[actionNode->unit_recno];
+
+ //------ recruit on job worker ----------//
+
+ if( !unitPtr && firmPtr->firm_id != FIRM_BASE ) // seat of power shouldn't call this function at all, as it doesn't handle the racial issue.
+ {
+ unitRecno = recruit_on_job_worker(firmPtr, actionNode->action_para2);
+
+ if( unitRecno )
+ unitPtr = unit_array[unitRecno];
+ }
+
+ //------- train a unit --------------//
+
+ if( !unitPtr &&
+ firmPtr->firm_id == FIRM_CAMP &&
+ ai_should_spend( 20+pref_military_development/2 ) ) // 50 to 70
+ {
+ int trainTownRecno;
+
+ if( train_unit( firmPtr->firm_skill_id, firmPtr->majority_race(),
+ actionNode->action_x_loc, actionNode->action_y_loc,
+ trainTownRecno, actionNode->action_id ) )
+ {
+ actionNode->next_retry_date = info.game_date + TOTAL_TRAIN_DAYS + 1;
+ actionNode->retry_count++;
+ return 0; // training in process
+ }
+ }
+
+ //-------- recruit a unit ----------//
+
+ if( !unitPtr )
+ {
+ unitRecno = recruit_jobless_worker(firmPtr, actionNode->action_para2);
+
+ if( unitRecno )
+ unitPtr = unit_array[unitRecno];
+ }
+
+ if( !unitPtr )
+ return 0;
+
+ //---------------------------------------------------------------------------//
+
+ FirmInfo* firmInfo = firm_res[firmId];
+
+ if( !world.get_loc(actionNode->action_x_loc, actionNode->action_y_loc)->is_firm() ) // firm exists, so assign
+ return -1;
+
+ err_when( unitPtr->rank_id != RANK_SOLDIER );
+
+ unitPtr->assign(actionNode->action_x_loc, actionNode->action_y_loc);
+ unitPtr->ai_action_id = actionNode->action_id;
+
+ err_when( unitPtr->nation_recno != nation_recno );
+
+ return 1;
+}
+//----- End of function Nation::ai_assign_worker -----//
+
+
+//----- Begin of function Nation::ai_settle_to_other_town -----//
+//
+// action_x_loc, action_y_loc - location of the destination town.
+// ref_x_loc, ref_y_loc - location of the origin town.
+//
+int Nation::ai_settle_to_other_town(ActionNode* actionNode)
+{
+ if(!seek_path.total_node_avail)
+ return 0;
+
+ //------- check if both towns are ready first --------//
+
+ if(!check_town_ready(actionNode->action_x_loc, actionNode->action_y_loc) ||
+ !check_town_ready(actionNode->ref_x_loc, actionNode->ref_y_loc))
+ {
+ return -1;
+ }
+
+ //----------------------------------------------------//
+ // stop if no jobless population
+ //----------------------------------------------------//
+
+ Location* locPtr = world.get_loc(actionNode->ref_x_loc, actionNode->ref_y_loc);
+
+ err_when(!locPtr->is_town() || town_array.is_deleted(locPtr->town_recno()));
+
+ Town* townPtr = town_array[locPtr->town_recno()]; // point to the old town
+
+ int raceId = townPtr->pick_random_race(0, 1); // 0-don't pick has job unit, 1-pick spies
+
+ if( !raceId )
+ return -1;
+
+ //---- if cannot recruit because the loyalty is too low ---//
+
+ if( !townPtr->can_recruit(raceId) && townPtr->has_linked_own_camp )
+ {
+ int minRecruitLoyalty = MIN_RECRUIT_LOYALTY + townPtr->recruit_dec_loyalty(raceId, 0);
+
+ //--- if cannot recruit because of low loyalty, reward the town people now ---//
+
+ if( townPtr->race_loyalty_array[raceId-1] < minRecruitLoyalty )
+ {
+ if( cash > 0 && townPtr->accumulated_reward_penalty==0 )
+ {
+ townPtr->reward(COMMAND_AI);
+ }
+
+ if( !townPtr->can_recruit(raceId) ) // if still cannot be recruited, return 0 now
+ return 0;
+ }
+
+ return 0;
+ }
+
+ //------------------------------------------------------//
+ // recruit
+ //------------------------------------------------------//
+
+ int unitRecno = townPtr->recruit(-1, raceId, COMMAND_AI);
+
+ if( !unitRecno )
+ return 0;
+
+ //---------------------------------------------------------------------------//
+ // since it is not an important action, no need to add processing action
+ //---------------------------------------------------------------------------//
+
+ Unit* unitPtr = unit_array[unitRecno];
+
+ unitPtr->assign(actionNode->action_x_loc, actionNode->action_y_loc); // assign to the town
+ unitPtr->ai_action_id = actionNode->action_id;
+
+ err_when( unitPtr->nation_recno==0 );
+
+ return 1;
+}
+//----- End of function Nation::ai_settle_to_other_town -----//
+
+
+//--------- Begin of function Nation::ai_scout ----------//
+//
+// action_x_loc, action_y_loc - location of the destination town.
+// ref_x_loc, ref_y_loc - location of the origin town.
+//
+int Nation::ai_scout(ActionNode* actionNode)
+{
+ if(!seek_path.total_node_avail)
+ return 0;
+
+ //------- check if both towns are ready first --------//
+
+ if(!check_town_ready(actionNode->action_x_loc, actionNode->action_y_loc) ||
+ !check_town_ready(actionNode->ref_x_loc, actionNode->ref_y_loc))
+ {
+ return -1;
+ }
+
+ //----------------------------------------------------//
+ // stop if no jobless population
+ //----------------------------------------------------//
+
+ Location* locPtr = world.get_loc(actionNode->ref_x_loc, actionNode->ref_y_loc);
+
+ err_when(!locPtr->is_town() || town_array.is_deleted(locPtr->town_recno()));
+
+ Town* townPtr = town_array[locPtr->town_recno()]; // point to the old town
+
+ int raceId = townPtr->pick_random_race(0, 1); // 0-don't pick has job unit, 1-pick spies
+
+ if( !raceId )
+ return -1;
+
+ //---- if cannot recruit because the loyalty is too low ---//
+
+ if( !townPtr->can_recruit(raceId) && townPtr->has_linked_own_camp )
+ return 0;
+
+ //------------------------------------------------------//
+ // recruit
+ //------------------------------------------------------//
+
+ int unitRecno = townPtr->recruit(-1, raceId, COMMAND_AI);
+
+ if( !unitRecno )
+ return 0;
+
+ //---------------------------------------------------------------------------//
+ // since it is not an important action, no need to add processing action
+ //---------------------------------------------------------------------------//
+
+ Unit* unitPtr = unit_array[unitRecno];
+
+ short selectedArray[1];
+ selectedArray[0] = unitRecno;
+
+ //-- must use unit_array.move_to() instead of unit.move_to() because the destination may be reachable, it can be in a different region --//
+
+ unit_array.move_to( actionNode->action_x_loc, actionNode->action_y_loc, 0, selectedArray, 1, COMMAND_AI );
+
+ unitPtr->ai_action_id = actionNode->action_id;
+
+ err_when( unitPtr->nation_recno==0 );
+
+ return 1;
+}
+//-------- End of function Nation::ai_scout ----------//
+
diff --git a/OAI_ATTK.cpp b/OAI_ATTK.cpp
new file mode 100644
index 0000000..0d80260
--- /dev/null
+++ b/OAI_ATTK.cpp
@@ -0,0 +1,923 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_ATTK.CPP
+//Description: AI - attacking
+
+#include <stdlib.h>
+#include <ALL.h>
+#include <OUNIT.h>
+#include <OCONFIG.h>
+#include <OFIRMALL.h>
+#include <OTALKRES.h>
+#include <ONATION.h>
+
+
+//------ Declare static functions --------//
+
+static int get_target_nation_recno(int targetXLoc, int targetYLoc);
+static int sort_attack_camp_function( const void *a, const void *b );
+
+
+//--------- Begin of function Nation::ai_attack_target --------//
+//
+// Think about attacking a specific target.
+//
+// <int> targetXLoc, targetYLoc - location of the target
+// <int> targetCombatLevel - the combat level of the target, will
+// only attack the target if the attacker's
+// force is larger than it.
+// [int] defenseMode - whether the attack is basically for
+// defending against an attack
+// (default: 0)
+// [int] justMoveToFlag - whether just all move there and wait for
+// the units to attack the enemies automatically
+// (default: 0)
+// [int] attackerMinCombatLevel - the minimum combat level of the attacker,
+// do not send troops whose combat level
+// is lower than this.
+// (default: 0)
+// [int] leadAttackCampRecno - if this is given, this camp will be
+// included in the attacker list by passing
+// checking on it.
+// (default: 0)
+// [int] useAllCamp - use all camps to attack even if defenseMode is 0
+// (default: defenseMode, which is default to 0)
+//
+// return: <int> 0 - no attack action
+// >0 - the total combat level of attacking force.
+//
+int Nation::ai_attack_target(int targetXLoc, int targetYLoc, int targetCombatLevel, int defenseMode, int justMoveToFlag, int attackerMinCombatLevel, int leadAttackCampRecno, int useAllCamp)
+{
+/* // this will be called when the AI tries to capture the town and attack the town's defense.
+#ifdef DEBUG //----- check for attacking own objects error ------//
+ {
+ int targetNationRecno = get_target_nation_recno(targetXLoc, targetYLoc);
+
+ if( targetNationRecno )
+ {
+ err_when( get_relation(targetNationRecno)->status >= NATION_FRIENDLY );
+ }
+ }
+#endif
+*/
+ //--- order nearby mobile units who are on their way to home camps to join this attack mission. ---//
+
+ if( defenseMode )
+ useAllCamp = 1;
+
+ if( defenseMode ) // only for defense mode, for attack mission, we should plan and organize it better
+ {
+ int originalTargetCombatLevel;
+
+ targetCombatLevel = ai_attack_order_nearby_mobile(targetXLoc, targetYLoc, targetCombatLevel);
+
+ if( targetCombatLevel < 0 ) // the mobile force alone can finish all the enemies
+ return originalTargetCombatLevel;
+ }
+
+ //--- try to send troop with maxTargetCombatLevel, and don't send troop if available combat level < minTargetCombatLevel ---//
+
+ int maxTargetCombatLevel = targetCombatLevel * (150+pref_force_projection/2) / 100; // 150% to 200%
+ int minTargetCombatLevel;
+
+ if( defenseMode )
+ minTargetCombatLevel = targetCombatLevel * (100-pref_military_courage/2) / 100; // 50% to 100%
+ else
+ minTargetCombatLevel = targetCombatLevel * (125+pref_force_projection/4) / 100; // 125% to 150%
+
+ //--- if the AI is already on an attack mission ---//
+
+ if( attack_camp_count )
+ return 0;
+
+ //---- first locate for camps that do not need to protect any towns ---//
+
+ #define MAX_SUITABLE_TOWN_CAMP 10 // no. of camps in a town
+
+ FirmCamp* firmCamp;
+ int i, j;
+ int targetRegionId = world.get_loc(targetXLoc, targetYLoc)->region_id;
+
+ err_when( targetXLoc < 0 || targetXLoc >= MAX_WORLD_X_LOC );
+ err_when( targetYLoc < 0 || targetYLoc >= MAX_WORLD_Y_LOC );
+
+ ai_attack_target_x_loc = targetXLoc;
+ ai_attack_target_y_loc = targetYLoc;
+ ai_attack_target_nation_recno = get_target_nation_recno(targetXLoc, targetYLoc);
+
+ attack_camp_count=0;
+
+ AttackCamp townCampArray[MAX_SUITABLE_TOWN_CAMP];
+ short townCampCount;
+
+ //------- if there is a pre-selected camp -------//
+
+ lead_attack_camp_recno = leadAttackCampRecno;
+
+ if( leadAttackCampRecno )
+ {
+ err_when( firm_array[leadAttackCampRecno]->nation_recno != nation_recno );
+ err_when( firm_array[leadAttackCampRecno]->firm_id != FIRM_CAMP );
+
+ attack_camp_array[attack_camp_count].firm_recno = leadAttackCampRecno;
+ attack_camp_array[attack_camp_count].combat_level = ((FirmCamp*)firm_array[leadAttackCampRecno])->total_combat_level();
+
+ err_when( attack_camp_array[attack_camp_count].combat_level < 0 );
+
+ attack_camp_count++;
+ }
+
+ //---- if the military courage is low or the king is injured, don't send the king out to battles ---//
+
+ Nation* ownNation = nation_array[nation_recno];
+ int kingFirmRecno=0;
+
+ if( king_unit_recno )
+ {
+ Unit* kingUnit = unit_array[king_unit_recno];
+
+ if( kingUnit->unit_mode == UNIT_MODE_OVERSEE )
+ {
+ Firm* kingFirm = firm_array[kingUnit->unit_mode_para];
+ int rc = 0;
+
+ if( ai_camp_count > 3 + (100-ownNation->pref_military_courage)/20 ) // don't use the king if we have other generals, the king won't be used if we have 3 to 8 camps. The higher the military courage is, the smaller will be the number of camps
+ rc = 1;
+
+ //--- if the military courage is low or the king is injured ---//
+
+ else if( kingUnit->hit_points < 230-ownNation->pref_military_courage ) // 130 to 230, if over 200, the king will not fight
+ rc = 1;
+
+ //--- if the King does have a full troop ----//
+
+ else if( kingFirm->worker_count < MAX_WORKER )
+ rc = 1;
+
+ //-------------------------------------------//
+
+ if( rc )
+ {
+ kingFirmRecno = kingUnit->unit_mode_para;
+
+ //--- if the king is very close to the target, ask him to attack also ---//
+
+ if( kingFirmRecno &&
+ kingUnit->hit_points >= 150-ownNation->pref_military_courage/4 ) // if the king is not heavily injured
+ {
+ firmCamp = (FirmCamp*) firm_array[kingFirmRecno];
+
+ if( firmCamp->worker_count == MAX_WORKER ) // the king shouldn't go out alone
+ {
+ if( m.points_distance(firmCamp->center_x, firmCamp->center_y,
+ targetXLoc, targetYLoc) <= EFFECTIVE_FIRM_TOWN_DISTANCE )
+ {
+ kingFirmRecno = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //--------- locate for camps that are not linked to towns ---------//
+
+ int rc;
+
+ for( i=0 ; i<ai_camp_count ; i++ )
+ {
+ firmCamp = (FirmCamp*) firm_array[ ai_camp_array[i] ];
+
+ err_when( firmCamp->firm_id != FIRM_CAMP );
+
+ if( firmCamp->region_id != targetRegionId )
+ continue;
+
+ if( !firmCamp->overseer_recno || !firmCamp->worker_count )
+ continue;
+
+ if( firmCamp->patrol_unit_count > 0 ) // if there are units patrolling out
+ continue;
+
+ if( firmCamp->ai_capture_town_recno ) // the base is trying to capture an independent town
+ continue;
+
+ if( firmCamp->is_attack_camp )
+ continue;
+
+ if( firmCamp->firm_recno == kingFirmRecno )
+ continue;
+
+ //---- don't order this camp if the overseer is injured ----//
+
+ Unit* overseerUnit = unit_array[firmCamp->overseer_recno];
+
+ if( overseerUnit->hit_points < overseerUnit->max_hit_points &&
+ overseerUnit->hit_points < 100-ownNation->pref_military_courage/2 ) // 50 to 100
+ {
+ continue;
+ }
+
+ //----------------------------------------------------------//
+
+ if( attackerMinCombatLevel )
+ {
+ if( firmCamp->average_combat_level() < attackerMinCombatLevel )
+ continue;
+ }
+
+ //-------------------------------------//
+ //
+ // Add this camp if:
+ // 1. we are in defense mode, and have to get all the forces available to defend against the attack.
+ // 2. this camp isn't linked to any of our towns.
+ //
+ //-------------------------------------//
+
+ if( useAllCamp )
+ rc = 1;
+ else
+ {
+ rc = firmCamp->linked_town_count==0; // don't use this camp as it may be in the process of capturing an indepdendent town or an enemy town
+/*
+ for( int j=firmCamp->linked_town_count-1 ; j>=0 ; j-- )
+ {
+ if( town_array[firmCamp->linked_town_array[j]]->nation_recno == nation_recno )
+ break;
+ }
+
+ rc = j<0; // j<0 means not linked to any of our towns.
+*/
+ }
+
+ if( rc )
+ {
+ //--- if this camp into the list of suitable attacker firm ---//
+
+ if( attack_camp_count < MAX_SUITABLE_ATTACK_CAMP )
+ {
+ err_when( firmCamp->nation_recno != nation_recno );
+
+ attack_camp_array[attack_camp_count].firm_recno = firmCamp->firm_recno;
+ attack_camp_array[attack_camp_count].combat_level = firmCamp->total_combat_level();
+
+ err_when( attack_camp_array[attack_camp_count].combat_level < 0 );
+
+ attack_camp_count++;
+ }
+ }
+ }
+
+ //---- locate for camps that are extra for protecting towns (there are basic ones doing the protection job only) ----//
+
+ int totalCombatLevel, protectionNeeded;
+ Town* townPtr;
+ Firm* firmPtr;
+
+ if( !useAllCamp ) // in defense mode, every camp has been already counted
+ {
+ for( i=0 ; i<ai_town_count ; i++ )
+ {
+ townPtr = town_array[ ai_town_array[i] ];
+
+ if( townPtr->region_id != targetRegionId )
+ continue;
+
+ err_when( townPtr->nation_recno != nation_recno );
+
+ //----- calculate the protection needed for this town ----//
+
+ protectionNeeded = townPtr->protection_needed();
+
+ townCampCount =0;
+ totalCombatLevel=0;
+
+ for( j=townPtr->linked_firm_count-1 ; j>=0 ; j-- )
+ {
+ firmPtr = firm_array[ townPtr->linked_firm_array[j] ];
+
+ if( firmPtr->nation_recno != nation_recno )
+ continue;
+
+ if( firmPtr->firm_recno == kingFirmRecno )
+ continue;
+
+ //----- if this is a camp, add combat level points -----//
+
+ if( firmPtr->firm_id == FIRM_CAMP )
+ {
+ if( !firmPtr->overseer_recno && !firmPtr->worker_count )
+ continue;
+
+ firmCamp = (FirmCamp*) firmPtr;
+
+ if( firmCamp->patrol_unit_count > 0 ) // if there are units patrolling out
+ continue;
+
+ if( firmCamp->ai_capture_town_recno ) // the base is trying to capture an independent town
+ continue;
+
+ if( firmCamp->is_attack_camp )
+ continue;
+
+ totalCombatLevel += firmCamp->total_combat_level();
+
+ if( townCampCount < MAX_SUITABLE_TOWN_CAMP )
+ {
+ err_when( firmCamp->nation_recno != nation_recno );
+
+ townCampArray[townCampCount].firm_recno = firmCamp->firm_recno;
+ townCampArray[townCampCount].combat_level = firmCamp->total_combat_level();
+
+ err_when( townCampArray[townCampCount].combat_level < 0 );
+
+ townCampCount++;
+ }
+ }
+
+ //--- if this is a civilian firm, add needed protection points ---//
+
+ else
+ {
+ if( firmPtr->firm_id == FIRM_MARKET )
+ protectionNeeded += ((FirmMarket*)firmPtr)->stock_value_index();
+ else
+ protectionNeeded += (int) firmPtr->productivity;
+ }
+ }
+
+ //--- see if the current combat level is larger than the protection needed ---//
+
+ if( totalCombatLevel > protectionNeeded )
+ {
+ //--- see if the protection is still enough if we put one of the camps into the upcoming battle ---//
+
+ for( int j=0 ; j<townCampCount ; j++ )
+ {
+ if( totalCombatLevel - townCampArray[j].combat_level > protectionNeeded )
+ {
+ //--- if so, add this camp to the suitable camp list ---//
+
+ if( attack_camp_count < MAX_SUITABLE_ATTACK_CAMP )
+ {
+ //--- this camp can be linked to a town previously processed already (in this case, two towns linked to the same camp) ---//
+
+ int k;
+ for( k=0 ; k<attack_camp_count ; k++ )
+ {
+ if( attack_camp_array[k].firm_recno == townCampArray[j].firm_recno )
+ break;
+ }
+
+ //---- if this camp hasn't been added yet ----//
+
+ if( k==attack_camp_count )
+ {
+ err_when( firm_array[townCampArray[j].firm_recno]->nation_recno != nation_recno );
+
+ attack_camp_array[attack_camp_count] = townCampArray[j];
+ attack_camp_count++;
+
+ totalCombatLevel -= townCampArray[j].combat_level; // reduce it from the total combat level as its combat level has just been used, and is no longer available
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //---- now we get all suitable camps in the list, it's time to attack ---//
+
+ //----- think about which ones in the list should be used -----//
+
+ //--- first calculate the total combat level of these camps ---//
+
+ totalCombatLevel = 0;
+
+ for( i=0 ; i<attack_camp_count ; i++ )
+ totalCombatLevel += attack_camp_array[i].combat_level;
+
+ //--- see if we are not strong enough to attack yet -----//
+
+ if( totalCombatLevel < minTargetCombatLevel ) // if we are not strong enough to attack yet
+ {
+ attack_camp_count=0;
+ return 0;
+ }
+
+ //----- build an array of the distance data first -----//
+
+ for( i=0 ; i<attack_camp_count ; i++ )
+ {
+ firmPtr = firm_array[ attack_camp_array[i].firm_recno ];
+
+ err_when( firmPtr->nation_recno != nation_recno );
+
+ attack_camp_array[i].distance = m.points_distance( firmPtr->center_x, firmPtr->center_y,
+ targetXLoc, targetYLoc );
+
+ err_when( attack_camp_array[i].distance < 0 );
+ }
+
+ //---- now sort the camps based on their distances & combat levels ----//
+
+ qsort( &attack_camp_array, attack_camp_count, sizeof(attack_camp_array[0]), sort_attack_camp_function );
+
+ //----- now take out the lowest rating ones -----//
+
+ for( i=attack_camp_count-1 ; i>=0 ; i-- )
+ {
+ if( totalCombatLevel - attack_camp_array[i].combat_level > maxTargetCombatLevel )
+ {
+ totalCombatLevel -= attack_camp_array[i].combat_level;
+ attack_camp_count--;
+ }
+ }
+
+ err_when( attack_camp_count < 0 );
+
+ //------- synchronize the attack date for different camps ----//
+
+ ai_attack_target_sync();
+
+ ai_attack_target_execute(!justMoveToFlag);
+
+ return totalCombatLevel;
+}
+//---------- End of function Nation::ai_attack_target --------//
+
+
+//--------- Begin of function Nation::ai_attack_order_nearby_mobile --------//
+//
+// Order nearby mobile units who are on their way to home camps to
+// join this attack mission.
+//
+// <int> targetXLoc, targetYLoc - location of the target
+// <int> targetCombatLevel - the combat level of the target, will
+// only attack the target if the attacker's
+// force is larger than it.
+//
+// return: <int> the remaining target combat level of the target
+// after ordering the mobile units to deal with some of them.
+//
+int Nation::ai_attack_order_nearby_mobile(int targetXLoc, int targetYLoc, int targetCombatLevel)
+{
+ int scanRange = 15+pref_military_development/20; // 15 to 20
+ int xOffset, yOffset;
+ int xLoc, yLoc;
+ int targetRegionId = world.get_region_id(targetXLoc, targetYLoc);
+ Location* locPtr;
+
+ for( int i=2 ; i<scanRange*scanRange ; i++ )
+ {
+ m.cal_move_around_a_point(i, scanRange, scanRange, xOffset, yOffset);
+
+ xLoc = targetXLoc + xOffset;
+ yLoc = targetYLoc + yOffset;
+
+ xLoc = max(0, xLoc);
+ xLoc = min(MAX_WORLD_X_LOC-1, xLoc);
+
+ yLoc = max(0, yLoc);
+ yLoc = min(MAX_WORLD_Y_LOC-1, yLoc);
+
+ locPtr = world.get_loc(xLoc, yLoc);
+
+ if( locPtr->region_id != targetRegionId )
+ continue;
+
+ if( !locPtr->has_unit(UNIT_LAND) )
+ continue;
+
+ //----- if there is a unit on the location ------//
+
+ int unitRecno = locPtr->unit_recno(UNIT_LAND);
+
+ if( unit_array.is_deleted(unitRecno) ) // the unit is dying
+ continue;
+
+ Unit* unitPtr = unit_array[unitRecno];
+
+ //--- if if this is our own military unit ----//
+
+ if( unitPtr->nation_recno != nation_recno ||
+ unitPtr->skill.skill_id != SKILL_LEADING )
+ {
+ continue;
+ }
+
+ //--------- if this unit is injured ----------//
+
+ if( unitPtr->hit_points <
+ unitPtr->max_hit_points * (150-pref_military_courage/2) / 200 )
+ {
+ continue;
+ }
+
+ //---- only if this is not assigned to an action ---//
+
+ if( unitPtr->ai_action_id )
+ continue;
+
+ //---- if this unit is stop or assigning to a firm ----//
+
+ if( unitPtr->action_mode2 == ACTION_STOP ||
+ unitPtr->action_mode2 == ACTION_ASSIGN_TO_FIRM )
+ {
+ //-------- set should_attack on the target to 1 --------//
+
+ enable_should_attack_on_target(targetXLoc, targetYLoc);
+
+ //---------- attack now -----------//
+
+ unitPtr->attack_unit(targetXLoc, targetYLoc);
+
+ targetCombatLevel -= (int) unitPtr->hit_points; // reduce the target combat level
+
+ if( targetCombatLevel <= 0 )
+ break;
+ }
+ }
+
+ return targetCombatLevel;
+}
+//--------- End of function Nation::ai_attack_order_nearby_mobile --------//
+//
+
+//--------- Begin of function Nation::ai_attack_target_sync --------//
+//
+// Synchronize the timing of attacking a target. Camps that are further
+// away from the target will move first while camps that are closer
+// to the target will move later.
+//
+void Nation::ai_attack_target_sync()
+{
+ //---- find the distance of the camp that is farest to the target ----//
+
+ int maxDistance=0;
+
+ int i;
+ for( i=0 ; i<attack_camp_count ; i++ )
+ {
+ err_when( attack_camp_array[i].distance < 0 );
+
+ if( attack_camp_array[i].distance > maxDistance )
+ maxDistance = attack_camp_array[i].distance;
+ }
+
+ int maxTravelDays = sprite_res[ unit_res[UNIT_NORMAN]->sprite_id ]->travel_days(maxDistance);
+
+ //------ set the date which the troop should start moving -----//
+
+ int travelDays;
+
+ for( i=0 ; i<attack_camp_count ; i++ )
+ {
+ travelDays = maxTravelDays * attack_camp_array[i].distance / maxDistance;
+
+ attack_camp_array[i].patrol_date = info.game_date + (maxTravelDays-travelDays);
+ }
+
+ //----- set the is_attack_camp flag of the camps ------//
+
+ for( i=0 ; i<attack_camp_count ; i++ )
+ {
+ Firm* firmPtr = firm_array[ attack_camp_array[i].firm_recno ];
+
+ err_when( firmPtr->firm_id != FIRM_CAMP );
+ err_when( firmPtr->nation_recno != nation_recno );
+
+ ((FirmCamp*)firmPtr)->is_attack_camp = 1;
+ }
+}
+//---------- End of function Nation::ai_attack_target_sync --------//
+
+
+//--------- Begin of function Nation::ai_attack_target_execute --------//
+//
+// Synchronize the timing of attacking a target. Camps that are further
+// away from the target will move first while camps that are closer
+// to the target will move later.
+//
+// <int> directAttack - whether directly attack the target or
+// just move close to the target.
+//
+void Nation::ai_attack_target_execute(int directAttack)
+{
+ FirmCamp* firmCamp;
+ int firmRecno;
+
+ err_when( ai_attack_target_x_loc < 0 || ai_attack_target_x_loc >= MAX_WORLD_X_LOC );
+ err_when( ai_attack_target_y_loc < 0 || ai_attack_target_y_loc >= MAX_WORLD_Y_LOC );
+
+ //---- if the target no longer exist -----//
+
+ if( ai_attack_target_nation_recno != get_target_nation_recno(ai_attack_target_x_loc, ai_attack_target_y_loc) )
+ {
+ reset_ai_attack_target();
+ }
+
+ //----------------------------------------//
+
+ for( int i=attack_camp_count-1 ; i>=0 ; i-- )
+ {
+ //----- if it's still not the date to move to attack ----//
+
+ if( info.game_date < attack_camp_array[i].patrol_date )
+ continue;
+
+ //-------------------------------------------------------//
+
+ firmRecno = attack_camp_array[i].firm_recno;
+
+ firmCamp = (FirmCamp*) firm_array[firmRecno];
+
+ if( firmCamp->overseer_recno || firmCamp->worker_count )
+ {
+ //--- if this is the lead attack camp, don't mobilize the overseer ---//
+
+ if( lead_attack_camp_recno == firmRecno )
+ firmCamp->patrol_all_soldier(); // don't mobilize the overseer
+ else
+ firmCamp->patrol(); // mobilize the overseer and the soldiers
+
+ //----------------------------------------//
+
+ if( firmCamp->patrol_unit_count > 0 ) // there could be chances that there are no some for mobilizing the units
+ {
+ //------- declare war with the target nation -------//
+
+ if( ai_attack_target_nation_recno )
+ talk_res.ai_send_talk_msg(ai_attack_target_nation_recno, nation_recno, TALK_DECLARE_WAR);
+
+ //--- in defense mode, just move close to the target, the unit will start attacking themselves as their relationship is hostile already ---//
+
+ if( !directAttack )
+ {
+ unit_array.move_to(ai_attack_target_x_loc, ai_attack_target_y_loc, 0, firmCamp->patrol_unit_array,
+ firmCamp->patrol_unit_count, COMMAND_AI);
+ }
+ else
+ {
+ //-------- set should_attack on the target to 1 --------//
+
+ enable_should_attack_on_target(ai_attack_target_x_loc, ai_attack_target_y_loc);
+
+ //---------- attack now -----------//
+
+ // ##### patch begin Gilbert 5/8 ######//
+ unit_array.attack(ai_attack_target_x_loc, ai_attack_target_y_loc, 0, firmCamp->patrol_unit_array,
+ firmCamp->patrol_unit_count, COMMAND_AI, 0);
+ // ##### patch end Gilbert 5/8 ######//
+ }
+ }
+ }
+
+ //--------- reset FirmCamp::is_attack_camp ---------//
+
+ firmCamp->is_attack_camp = 0;
+
+ //------- remove this from attack_camp_array -------//
+
+ m.del_array_rec(attack_camp_array, attack_camp_count, sizeof(AttackCamp), i+1 );
+ attack_camp_count--;
+ }
+}
+//---------- End of function Nation::ai_attack_target_execute --------//
+
+
+//--------- Begin of function Nation::reset_ai_attack_target --------//
+//
+void Nation::reset_ai_attack_target()
+{
+ //------ reset all is_attack_camp -------//
+
+ for( int i=0 ; i<attack_camp_count ; i++ )
+ {
+ Firm* firmPtr = firm_array[ attack_camp_array[i].firm_recno ];
+
+ err_when( firmPtr->firm_id != FIRM_CAMP ||
+ firmPtr->nation_recno != nation_recno );
+
+ ((FirmCamp*)firmPtr)->is_attack_camp = 0;
+ }
+
+ //--------------------------------------//
+
+ attack_camp_count = 0;
+}
+//---------- End of function Nation::reset_ai_attack_target --------//
+
+
+//--------- Begin of function Nation::enable_should_attack_on_target --------//
+//
+void Nation::enable_should_attack_on_target(int targetXLoc, int targetYLoc)
+{
+ //------ set should attack to 1 --------//
+
+ int targetNationRecno = 0;
+
+ Location* locPtr = world.get_loc(targetXLoc, targetYLoc);
+
+ if( locPtr->has_unit(UNIT_LAND) )
+ targetNationRecno = unit_array[ locPtr->unit_recno(UNIT_LAND) ]->nation_recno;
+
+ else if( locPtr->is_firm() )
+ targetNationRecno = firm_array[locPtr->firm_recno()]->nation_recno;
+
+ else if( locPtr->is_town() )
+ targetNationRecno = town_array[locPtr->town_recno()]->nation_recno;
+
+ if( targetNationRecno )
+ {
+ set_relation_should_attack(targetNationRecno, 1, COMMAND_AI);
+ }
+}
+//--------- End of function Nation::enable_should_attack_on_target --------//
+
+
+//--------- Begin of static function get_target_nation_recno --------//
+//
+// Return the nation recno of the target.
+//
+static int get_target_nation_recno(int targetXLoc, int targetYLoc)
+{
+ Location* locPtr = world.get_loc(targetXLoc, targetYLoc);
+
+ if( locPtr->is_firm() )
+ {
+ return firm_array[locPtr->firm_recno()]->nation_recno;
+ }
+ else if( locPtr->is_town() )
+ {
+ return town_array[locPtr->town_recno()]->nation_recno;
+ }
+ else if( locPtr->has_unit(UNIT_LAND) )
+ {
+ return unit_array[locPtr->unit_recno(UNIT_LAND)]->nation_recno;
+ }
+
+ return 0;
+}
+//---------- End of static function get_target_nation_recno --------//
+
+
+//------ Begin of function sort_attack_camp_function ------//
+//
+static int sort_attack_camp_function( const void *a, const void *b )
+{
+ int ratingA = ((AttackCamp*)a)->combat_level - ((AttackCamp*)a)->distance;
+ int ratingB = ((AttackCamp*)b)->combat_level - ((AttackCamp*)b)->distance;
+
+ return ratingB - ratingA;
+}
+//------- End of function sort_attack_camp_function ------//
+
+
+//--------- Begin of function Nation::think_secret_attack --------//
+//
+// Think about secret assault plans.
+//
+int Nation::think_secret_attack()
+{
+ //--- never secret attack if its peacefulness >= 80 ---//
+
+ if( pref_peacefulness >= 80 )
+ return 0;
+
+ //--- don't try to get new enemies if we already have many ---//
+
+ int totalEnemyMilitary = total_enemy_military();
+
+ if( totalEnemyMilitary > 20+pref_military_courage-pref_peacefulness )
+ return 0;
+
+ //---------------------------------------------//
+
+ int curRating=0, bestRating=0, bestNationRecno=0;
+ int ourMilitary = military_rank_rating();
+ int relationStatus, tradeRating;
+ Nation* nationPtr;
+ NationRelation* nationRelation;
+
+ for( int i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || nation_recno == i )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ nationRelation = get_relation(i);
+ relationStatus = nationRelation->status;
+
+ //---- if the secret attack flag is not enabled yet ----//
+
+ if( !nationRelation->ai_secret_attack )
+ {
+ //---- if we have a friendly treaty with this nation ----//
+
+ if( relationStatus == NATION_FRIENDLY )
+ {
+ if( totalEnemyMilitary > 0 ) // do not attack if we still have enemies
+ continue;
+ }
+
+ //-------- never attacks an ally ---------//
+
+ else if( relationStatus == NATION_ALLIANCE )
+ {
+ continue;
+ }
+
+ //---- don't attack if we have a big trade volume with the nation ---//
+
+ tradeRating = trade_rating(i)/2 + // existing trade
+ ai_trade_with_rating(i)/2; // possible trade
+
+ if( tradeRating > (50-pref_trading_tendency/2) ) // 0 to 50, 0 if trade tendency is 100, it is 0
+ {
+ continue;
+ }
+ }
+
+ //--------- calculate the rating ----------//
+
+ curRating = (ourMilitary - nationPtr->military_rank_rating()) * 2
+ + (overall_rank_rating() - 50) // if <50 negative, if >50 positive
+ - tradeRating*2
+ - get_relation(i)->ai_relation_level/2
+ - pref_peacefulness/2;
+
+ //------- if aggressiveness config is medium or high ----//
+
+ if( !nationPtr->is_ai() ) // more aggressive towards human players
+ {
+ switch( config.ai_aggressiveness )
+ {
+ case OPTION_MODERATE:
+ curRating += 100;
+ break;
+
+ case OPTION_HIGH:
+ curRating += 300;
+ break;
+
+ case OPTION_VERY_HIGH:
+ curRating += 500;
+ break;
+ }
+ }
+
+ //----- if the secret attack is already on -----//
+
+ if( nationRelation->ai_secret_attack )
+ {
+ //--- cancel secret attack if the situation has changed ---//
+
+ if( curRating < 0 )
+ {
+ nationRelation->ai_secret_attack = 0;
+ continue;
+ }
+ }
+
+ //--------- compare ratings -----------//
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestNationRecno = i;
+ }
+ }
+
+ //-------------------------------//
+
+ if( bestNationRecno )
+ {
+ get_relation(bestNationRecno)->ai_secret_attack = 1;
+ return 1;
+ }
+
+ return 0;
+}
+//---------- End of function Nation::think_secret_attack --------//
+
diff --git a/OAI_BUIL.cpp b/OAI_BUIL.cpp
new file mode 100644
index 0000000..14d5e14
--- /dev/null
+++ b/OAI_BUIL.cpp
@@ -0,0 +1,343 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_ACT.CPP
+//Description: AI - building firms
+
+#include <ALL.h>
+#include <OUNIT.h>
+#include <OF_CAMP.h>
+#include <OREGIONS.h>
+#include <OCONFIG.h>
+#include <OSITE.h>
+#include <ONATION.h>
+
+
+//--------- Begin of function Nation::think_build_firm --------//
+
+void Nation::think_build_firm()
+{
+ if( !ai_should_build_mine() )
+ return;
+
+ //----- think building mine --------//
+
+ if( think_build_mine() )
+ return;
+
+ //---- if there is a mine action currently -----//
+
+ think_destroy_raw_site_guard();
+}
+//---------- End of function Nation::think_build_firm --------//
+
+
+//--------- Begin of function Nation::think_build_mine --------//
+//
+int Nation::think_build_mine()
+{
+ //------- queue to build the new mine -------//
+
+ short xLoc, yLoc, refXLoc, refYLoc;
+
+ int rawId = seek_mine(xLoc, yLoc, refXLoc, refYLoc); //reference location is the raw material location
+
+ if( !rawId )
+ return 0;
+
+ //--- if we have a mine producing that raw type already ---//
+
+ if( raw_count_array[rawId-1] > 0 )
+ {
+ if( !ai_should_spend(20) ) // then it's not important to build it
+ return 0;
+ }
+
+ //-------------------------------------------//
+ // If the map is set to unexplored, wait for a
+ // reasonable amount of time before moving out
+ // to build the mine.
+ //-------------------------------------------//
+
+ if( !config.explore_whole_map )
+ {
+ int i;
+ for( i=0 ; i<ai_town_count ; i++ )
+ {
+ Town* townPtr = town_array[ ai_town_array[i] ];
+
+ int rawDistance = m.points_distance(xLoc, yLoc, townPtr->center_x, townPtr->center_y);
+
+ if( info.game_date-info.game_start_date >
+ rawDistance * (5-config.ai_aggressiveness) / 5 ) // 3 to 5 / 5
+ {
+ break;
+ }
+ }
+
+ if( i==ai_town_count )
+ return 0;
+ }
+
+ return add_action(xLoc, yLoc, refXLoc, refYLoc, ACTION_AI_BUILD_FIRM, FIRM_MINE);
+}
+//---------- End of function Nation::think_build_mine --------//
+
+
+//--------- Begin of function Nation::ai_should_build_mine --------//
+//
+int Nation::ai_should_build_mine()
+{
+ //---- only build mines when it has enough population ----//
+
+ if( total_jobless_population < (100-pref_economic_development)/2 )
+ return 0;
+
+ if( total_jobless_population < 16 ) // only build mine when you have enough population to support the economic chain: mine + factory + camp
+ return 0;
+
+ if( site_array.untapped_raw_count==0 )
+ return 0;
+
+ if( !can_ai_build(FIRM_MINE) )
+ return 0;
+
+ //--- don't build additional mines unless we have enough population and demand to support it ----//
+
+ if( ai_mine_count == 1 )
+ {
+ if( true_profit_365days() < 0 && total_population < 40+pref_economic_development/5 )
+ return 0;
+ }
+
+ //-- if the nation is already in the process of building a new one --//
+
+ if( is_action_exist( ACTION_AI_BUILD_FIRM, FIRM_MINE ) )
+ return 0;
+
+ //--- if the population is low, make sure existing mines are in full production before building a new one ---//
+
+ if( total_jobless_population < 30 )
+ {
+ Firm* firmPtr;
+
+ for( int i=0 ; i<ai_mine_count ; i++ )
+ {
+ firmPtr = firm_array[ ai_mine_array[i] ];
+
+ if( firmPtr->worker_count < MAX_WORKER )
+ return 0;
+
+ if( firmPtr->linked_firm_count==0 && !firmPtr->no_neighbor_space ) // if the firm does not have any linked firms, that means the firm is still not in full operation
+ return 0;
+ }
+ }
+
+ return 1;
+}
+//---------- End of function Nation::ai_should_build_mine --------//
+
+
+//--------- Begin of function Nation::ai_attack_unit_in_area --------//
+//
+// AI attacks all units that are not friendly to us within the given area.
+//
+// <int> xLoc1, yLoc1, xLoc2, yLoc2 - coordination of the area.
+//
+void Nation::ai_attack_unit_in_area(int xLoc1, int yLoc1, int xLoc2, int yLoc2)
+{
+ int enemyXLoc, enemyYLoc, enemyCombatLevel=0;
+ int enemyStatus = NATION_FRIENDLY;
+ Location* locPtr;
+ Unit* unitPtr;
+
+ //--------------------------------------------------//
+
+ for( int yLoc=yLoc1 ; yLoc<=yLoc2 ; yLoc++ )
+ {
+ for( int xLoc=xLoc1 ; xLoc<=xLoc2 ; xLoc++ )
+ {
+ locPtr = world.get_loc(xLoc, yLoc);
+
+ if( !locPtr->has_unit(UNIT_LAND) )
+ continue;
+
+ unitPtr = unit_array[ locPtr->unit_recno(UNIT_LAND) ];
+
+ //--- if there is an idle unit on the mine building site ---//
+
+ if( unitPtr->cur_action != SPRITE_IDLE || unitPtr->nation_recno==0 )
+ continue;
+
+ //----- if this is our spy cloaked in another nation, reveal its true identity -----//
+
+ if( unitPtr->nation_recno != nation_recno &&
+ unitPtr->true_nation_recno() == nation_recno )
+ {
+ unitPtr->spy_change_nation(nation_recno, COMMAND_AI);
+ }
+
+ //--- if this is our own unit, order him to stay out of the building site ---//
+
+ if( unitPtr->nation_recno == nation_recno )
+ {
+ unitPtr->think_normal_human_action(); // send the unit to a firm or a town
+ }
+ else //--- if it is an enemy unit, attack it ------//
+ {
+ int nationStatus = get_relation_status(unitPtr->nation_recno);
+
+ if( nationStatus < enemyStatus ) // if the status is worse than the current target
+ {
+ enemyXLoc = xLoc;
+ enemyYLoc = yLoc;
+ enemyStatus = nationStatus;
+ enemyCombatLevel += (int) unitPtr->unit_power();
+ }
+ }
+ }
+ }
+
+ //--- if there are enemies on our firm building site, attack them ---//
+
+ if( enemyCombatLevel )
+ {
+ ai_attack_target( enemyXLoc, enemyYLoc, enemyCombatLevel );
+ }
+}
+//---------- End of function Nation::ai_attack_unit_in_area --------//
+
+
+//--------- Begin of function Nation::think_destroy_raw_site_guard --------//
+//
+int Nation::think_destroy_raw_site_guard()
+{
+ Site* sitePtr;
+ Location* locPtr;
+ Unit* unitPtr;
+
+ for( int i=site_array.size() ; i>0 ; i-- )
+ {
+ if( site_array.is_deleted(i) )
+ continue;
+
+ sitePtr = site_array[i];
+
+ //--- if there is already a mine built on this raw site ---//
+
+ if( sitePtr->has_mine )
+ continue;
+
+ //----- if there is a unit standing on this site -----//
+
+ locPtr = world.get_loc( sitePtr->map_x_loc, sitePtr->map_y_loc );
+
+ if( !locPtr->has_unit(UNIT_LAND) )
+ continue;
+
+ unitPtr = unit_array[ locPtr->unit_recno(UNIT_LAND) ];
+
+ if( unitPtr->cur_action != SPRITE_IDLE ) // only attack if this unit is idle
+ continue;
+
+ if( unitPtr->nation_recno == nation_recno ) // don't attack our own units
+ continue;
+
+ //------ check if we have a presence in this region ----//
+
+ // ####### patch begin Gilbert 16/3 ########//
+ //if( region_array.get_region_stat(sitePtr->region_id)->base_town_nation_count_array[nation_recno-1] == 0 )
+ // continue;
+ if( base_town_count_in_region(sitePtr->region_id) == 0 )
+ continue;
+ // ####### patch end Gilbert 16/3 ########//
+
+ //------ check the relationship with this unit ------//
+ //
+ // If we are friendly with this nation, don't attack it.
+ //
+ //---------------------------------------------------//
+
+ if( get_relation_status(unitPtr->nation_recno) >= NATION_FRIENDLY )
+ continue;
+
+ //--------- attack the enemy unit ---------//
+
+ int hasWar;
+ int enemyCombatLevel = mobile_defense_combat_level( sitePtr->map_x_loc,
+ sitePtr->map_y_loc, unitPtr->nation_recno, 1, hasWar );
+
+ if( enemyCombatLevel == - 1 ) // a war is going on here, don't attack this target
+ continue;
+
+ if( ai_attack_target(sitePtr->map_x_loc, sitePtr->map_y_loc, enemyCombatLevel, 0, 0, 0, 0, 1) ) // 1-use all camps
+ return 1;
+ }
+
+ return 0;
+}
+//---------- End of function Nation::think_destroy_raw_site_guard --------//
+
+
+//--------- Begin of function Nation::ai_supported_inn_count --------//
+//
+// Return the number of inns this nation can support.
+//
+int Nation::ai_supported_inn_count()
+{
+ float fixedExpense = fixed_expense_365days();
+
+ int innCount = int( cash / 5000 * (100+pref_hire_unit) / 100 );
+
+ innCount = min(3, innCount); // maximum 3 inns, minimum 1 inn.
+
+ return max(1, innCount);
+}
+//---------- End of function Nation::ai_supported_inn_count --------//
+
+
+//--------- Begin of function Nation::ai_has_should_close_camp --------//
+//
+// Return whether the this nation has any camps that should be closed.
+//
+// <int> regionId - only camps in this region are counted.
+//
+int Nation::ai_has_should_close_camp(int regionId)
+{
+ //--- if this nation has some firms going to be closed ---//
+
+ if( firm_should_close_array[FIRM_CAMP-1] > 0 )
+ {
+ //--- check if any of them are in the same region as the current town ---//
+
+ for( int i=ai_camp_count-1 ; i>=0 ; i-- )
+ {
+ FirmCamp* firmCamp = (FirmCamp*) firm_array[ ai_camp_array[i] ];
+
+ if( firmCamp->should_close_flag && firmCamp->region_id == regionId )
+ {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+//---------- End of function Nation::ai_has_should_close_camp --------//
diff --git a/OAI_CAP2.cpp b/OAI_CAP2.cpp
new file mode 100644
index 0000000..656323b
--- /dev/null
+++ b/OAI_CAP2.cpp
@@ -0,0 +1,621 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_CAP2.CPP
+//Description: AI - capturing AI towns
+
+#include <stdlib.h>
+#include <ALL.h>
+#include <OGAME.h>
+#include <OCONFIG.h>
+#include <OUNIT.h>
+#include <OFIRMALL.h>
+#include <OTALKRES.h>
+#include <ONATION.h>
+
+//--------- Begin of function Nation::think_capture_new_enemy_town --------//
+//
+// <Town*> capturerTown - our town to capture enemy towns.
+// [int] useAllCamp - whether use troops in all camps to attack the enemy town
+// (default: 0)
+//
+int Nation::think_capture_new_enemy_town(Town* capturerTown, int useAllCamp)
+{
+ if( ai_camp_count==0 ) // this can happen when a new nation has just emerged
+ return 0;
+
+ if( ai_capture_enemy_town_recno ) // no new action if we are still trying to capture a town
+ return 0;
+
+ //---- only attack when we have enough money to support the war ----//
+
+ if( cash < 1000 + 2000 * pref_cash_reserve / 100 ) // if the cash is really too low now
+ return 0;
+
+ //--------------------------------------//
+
+ Town* targetTown = think_capture_enemy_town_target(capturerTown);
+
+ if( !targetTown )
+ return 0;
+
+ //---- attack enemy's defending forces on the target town ----//
+
+ int rc = attack_enemy_town_defense(targetTown, useAllCamp);
+
+ if( rc==0 ) // 0 means we don't have enough troop to attack the enemy
+ return 0;
+
+ else if( rc == 1 ) // 1 means a troop has been sent to attack the town
+ {
+ ai_capture_enemy_town_recno = targetTown->town_recno; // this nation is currently trying to capture this town
+ ai_capture_enemy_town_plan_date = info.game_date;
+ ai_capture_enemy_town_start_attack_date = 0;
+ ai_capture_enemy_town_use_all_camp = useAllCamp;
+
+ return 1;
+ }
+
+ else if( rc == -1 ) // -1 means no defense on the target town, no attacking is needed.
+ {
+ return start_capture( targetTown->town_recno ); // call AI functions in OAI_CAPT.CPP to capture the town
+ }
+
+ return 0;
+}
+//---------- End of function Nation::think_capture_new_enemy_town --------//
+
+
+//--------- Begin of function Nation::think_capturing_enemy_town --------//
+
+void Nation::think_capturing_enemy_town()
+{
+ if( !ai_capture_enemy_town_recno )
+ return;
+
+ if( town_array.is_deleted(ai_capture_enemy_town_recno) ||
+ town_array[ai_capture_enemy_town_recno]->nation_recno == nation_recno ) // this town has been captured already
+ {
+ ai_capture_enemy_town_recno = 0;
+ return;
+ }
+
+ //--- check the enemy's mobile defense combat level around the town ---//
+
+ Town* targetTown = town_array[ai_capture_enemy_town_recno];
+ int hasWar;
+
+ int mobileCombatLevel = mobile_defense_combat_level(targetTown->center_x, targetTown->center_y, targetTown->nation_recno, 0, hasWar); // 0-don't return immediately even if there is war around this town
+
+ //---- if we haven't started attacking the town yet -----//
+
+ if( !ai_capture_enemy_town_start_attack_date )
+ {
+ if( hasWar==2 ) // we are at war with the nation now
+ ai_capture_enemy_town_start_attack_date = info.game_date;
+
+ if( info.game_date > ai_capture_enemy_town_plan_date + 90 ) // when 3 months have gone and there still hasn't been any attack on the town, there must be something bad happened to our troop, cancel the entire action
+ ai_capture_enemy_town_recno = 0;
+
+ return; // do nothing if the attack hasn't started yet
+ }
+
+ //--------- check if we need reinforcement --------//
+
+ //-----------------------------------------------------------//
+ // Check how long we have started attacking because only
+ // when the it has been started for a while, our force
+ // will reach the target and the offensive and defensive force
+ // total can be calculated accurately.
+ //-----------------------------------------------------------//
+
+ if( info.game_date - ai_capture_enemy_town_start_attack_date >= 15 )
+ {
+ //-------- check if we need any reinforcement --------//
+
+ if( mobileCombatLevel > 0 && hasWar==2 ) // we are still in war with the enemy
+ {
+ ai_attack_target(targetTown->center_x, targetTown->center_y, mobileCombatLevel, 0, 1 ); // 1-just all move there and wait for the units to attack the enemies automatically
+ return;
+ }
+ }
+
+ //----- there is currently no war at the town -----//
+ //
+ // - either we are defeated or we have destroyed their command base.
+ //
+ //--------------------------------------------------//
+
+ if( hasWar != 2 )
+ {
+ //---- attack enemy's defending forces on the target town ----//
+
+ int rc = attack_enemy_town_defense(targetTown, ai_capture_enemy_town_use_all_camp);
+
+ if( rc == 1 ) // 1 means a troop has been sent to attack the town
+ {
+ ai_capture_enemy_town_start_attack_date = 0;
+ return;
+ }
+
+ //---------- reset the vars --------//
+
+ ai_capture_enemy_town_recno = 0;
+ ai_capture_enemy_town_start_attack_date = 0;
+
+ //--------- other situations --------//
+
+ if( rc == -1 ) // -1 means no defense on the target town, no attacking is needed.
+ {
+ start_capture( targetTown->town_recno ); // call AI functions in OAI_CAPT.CPP to capture the town
+ }
+
+ // 0 means we don't have enough troop to attack the enemy
+ }
+}
+//---------- End of function Nation::think_capturing_enemy_town --------//
+
+
+//--------- Begin of function Nation::attack_enemy_town_defense --------//
+//
+// Attack enemy's defending forces on the target town.
+//
+// <Town*> targetTown - the pointer to the target town.
+// [int] useAllCamp - whether use troops in all camps to attack the enemy town
+// (default: 0)
+//
+// return: <int> 1 - a troop has been sent to attack the target.
+// 0 - we don't have sufficient troops for attacking the target.
+// -1 - no defense on the target town, no attacking is needed.
+//
+int Nation::attack_enemy_town_defense(Town* targetTown, int useAllCamp)
+{
+ err_when( targetTown->nation_recno == nation_recno ); // cannot attack itself
+
+ if( targetTown->nation_recno == 0 )
+ return -1;
+
+ //--- if there are any command bases linked to the town, attack them first ---//
+
+ int campCombatLevel, maxCampCombatLevel= -1;
+ Firm *firmPtr, *bestTargetFirm=NULL;
+
+ for( int i=targetTown->linked_firm_count-1 ; i>=0 ; i-- )
+ {
+ firmPtr = firm_array[ targetTown->linked_firm_array[i] ];
+
+ if( firmPtr->nation_recno == targetTown->nation_recno &&
+ firmPtr->firm_id == FIRM_CAMP )
+ {
+ campCombatLevel = ((FirmCamp*)firmPtr)->total_combat_level();
+
+ if( campCombatLevel > maxCampCombatLevel )
+ {
+ maxCampCombatLevel = campCombatLevel;
+ bestTargetFirm = firmPtr;
+ }
+ }
+ }
+
+ //----- get the defense combat level of the mobile units around the town ----//
+
+ int hasWar;
+ int townMobileCombatLevel = mobile_defense_combat_level(targetTown->center_x, targetTown->center_y, targetTown->nation_recno, 0, hasWar);
+ int totalDefenseCombatLevel = maxCampCombatLevel + townMobileCombatLevel;
+
+ //----------------------------------------//
+
+ if( bestTargetFirm )
+ {
+ Nation* targetNation = nation_array[bestTargetFirm->nation_recno];
+
+ if( targetNation->is_at_war() ) // use all camps force if the nation is at war
+ useAllCamp = 1;
+
+ return ai_attack_target(bestTargetFirm->loc_x1, bestTargetFirm->loc_y1, totalDefenseCombatLevel,
+ 0, 0, 0, 0, useAllCamp );
+ }
+ else
+ {
+ //--- if there are any mobile defense force around the town ----//
+
+ if( townMobileCombatLevel > 0 )
+ return ai_attack_target(targetTown->center_x, targetTown->center_y, totalDefenseCombatLevel, 0, 1 ); // 1-just all move there and wait for the units to attack the enemies automatically
+ }
+
+ return -1;
+}
+//---------- End of function Nation::attack_enemy_town_defense --------//
+
+
+//--------- Begin of function Nation::think_capture_enemy_town_target --------//
+//
+// <Town*> capturerTown - our town to capture enemy towns.
+//
+// Motives for attacking another nation:
+//
+// 1. Capture towns
+// 2. Conquer land
+// 3. Defeat enemies
+//
+Town* Nation::think_capture_enemy_town_target(Town* capturerTown)
+{
+ int townRecno, curRating;
+ Town* targetTown, *bestTown=NULL;
+ Firm* firmPtr;
+ int ourMilitary = military_rank_rating();
+ Nation* ownNation = nation_array[nation_recno];
+ int bestRating = -1000;
+ int hasWar;
+ int neededCombatLevel=0;
+
+ for( townRecno=town_array.size() ; townRecno>0 ; townRecno-- )
+ {
+ if( town_array.is_deleted(townRecno) )
+ continue;
+
+ targetTown = town_array[townRecno];
+
+ if( targetTown->nation_recno == 0 ||
+ targetTown->nation_recno == nation_recno )
+ {
+ continue;
+ }
+
+ if( targetTown->region_id != capturerTown->region_id )
+ continue;
+
+ //----- if we have already built a camp next to this town -----//
+
+ if( targetTown->has_linked_camp(nation_recno, 0) ) //0-count both camps with or without overseers
+ continue;
+
+ //--------- only attack enemies -----------//
+
+ NationRelation* nationRelation = get_relation(targetTown->nation_recno);
+
+ int rc=0;
+
+ if( nationRelation->status == NATION_HOSTILE )
+ rc = 1;
+
+ else if( nationRelation->ai_relation_level < 10 ) // even if the relation is not hostile, if the ai_relation_level is < 10, attack anyway
+ rc = 1;
+
+ else if( nationRelation->status <= NATION_NEUTRAL &&
+ targetTown->nation_recno == nation_array.max_overall_nation_recno && // if this is our biggest enemy
+ nationRelation->ai_relation_level < 30 )
+ {
+ rc = 1;
+ }
+
+ if( !rc )
+ continue;
+
+ //----- if this town does not have any linked camps, capture this town immediately -----//
+
+ if( targetTown->has_linked_camp(targetTown->nation_recno, 0) ) //0-count both camps with or without overseers
+ return targetTown;
+
+ //--- if the enemy is very powerful overall, don't attack it yet ---//
+
+ if( nation_array[targetTown->nation_recno]->military_rank_rating() >
+ ourMilitary * (80+pref_military_courage/2) / 100 )
+ {
+ continue;
+ }
+
+ //------ only attack if we have enough money to support the war ----//
+
+ if( !ai_should_spend_war( nation_array[targetTown->nation_recno]->military_rank_rating() ) )
+ continue;
+
+ //-------------------------------------------------------//
+
+ int townCombatLevel = enemy_town_combat_level(targetTown, 1, hasWar); // 1-return a rating if there is war with the town
+
+ if( townCombatLevel == -1 ) // do not attack this town because a battle is already going on
+ continue;
+
+ //------- calculate the rating --------------//
+
+ curRating = world.distance_rating(capturerTown->center_x, capturerTown->center_y,
+ targetTown->center_x, targetTown->center_y);
+
+ curRating -= townCombatLevel/10;
+
+ curRating -= targetTown->average_loyalty();
+
+ curRating += targetTown->population; // put a preference on capturing villages with large population
+
+ //----- the power of between the nation also affect the rating ----//
+
+ curRating += 2 * (ourMilitary - nation_array[targetTown->nation_recno]->military_rank_rating());
+
+ //-- AI Aggressive is set above Low, than the AI will try to capture the player's town first ---//
+
+ if( !targetTown->ai_town )
+ {
+ if( game.game_mode == GAME_TUTORIAL ) // next attack the player in a tutorial game
+ {
+ continue;
+ }
+ else
+ {
+ switch( config.ai_aggressiveness )
+ {
+ case OPTION_MODERATE:
+ curRating += 100;
+ break;
+
+ case OPTION_HIGH:
+ curRating += 300;
+ break;
+
+ case OPTION_VERY_HIGH:
+ curRating += 500;
+ break;
+ }
+ }
+ }
+
+ //--- if there are mines linked to this town, increase its rating ---//
+
+ for( int i=targetTown->linked_firm_count-1 ; i>=0 ; i-- )
+ {
+ firmPtr = firm_array[ targetTown->linked_firm_array[i] ];
+
+ if( firmPtr->nation_recno != targetTown->nation_recno )
+ continue;
+
+ if( firmPtr->firm_id == FIRM_MINE )
+ {
+ //--- if this mine's raw materials is one that we don't have --//
+
+ if( raw_count_array[ ((FirmMine*)firmPtr)->raw_id-1 ]==0 )
+ curRating += 150 * (int) ((FirmMine*)firmPtr)->reserve_qty / MAX_RAW_RESERVE_QTY;
+ }
+ }
+
+ //--- more linked towns increase the attractiveness rating ---//
+
+ curRating += targetTown->linked_firm_count*5;
+
+ //-------- compare with the current best rating ---------//
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestTown = targetTown;
+ neededCombatLevel = townCombatLevel;
+ }
+ }
+
+ return bestTown;
+}
+//-------- End of function Nation::think_capture_enemy_town_target ------//
+
+
+//--------- Begin of function Nation::enemy_town_combat_level --------//
+//
+// <Town*> targetTown - the target town
+// <int> returnIfWar - return -1 if there is any war around the town
+// <int&> hasWar - a reference var for returning whether there is any war
+//
+// return: <int> the enemy's total defense combat level minus the player's
+// combat level.
+/// return -1 if there is war and returnIfWar is 1
+//
+int Nation::enemy_town_combat_level(Town* targetTown, int returnIfWar, int hasWar)
+{
+ int enemyCombatLevel = mobile_defense_combat_level(targetTown->center_x, targetTown->center_y, targetTown->nation_recno, returnIfWar, hasWar); //0-don't return even there are wars around the town
+
+ if( enemyCombatLevel < 0 ) // there is a war going on
+ return -1;
+
+ //---- calculate the attack rating of this target town ----//
+
+// enemyCombatLevel += targetTown->jobless_population * 5; //**BUGHERE
+
+ //--- calculate the combat level of enemy camps linked to this town ---//
+
+ Firm* firmPtr;
+
+ for( int i=targetTown->linked_firm_count-1 ; i>=0 ; i-- )
+ {
+ firmPtr = firm_array[ targetTown->linked_firm_array[i] ];
+
+ if( firmPtr->nation_recno == targetTown->nation_recno &&
+ firmPtr->firm_id == FIRM_CAMP )
+ {
+ enemyCombatLevel += ((FirmCamp*)firmPtr)->total_combat_level();
+ }
+ }
+/*
+ //----- add this and neighbor town's needed combat level ----//
+
+ Town* townPtr;
+
+ for( i=targetTown->linked_town_count-1 ; i>=0 ; i-- )
+ {
+ townPtr = town_array[ targetTown->linked_town_array[i] ];
+
+ if( townPtr->nation_recno == targetTown->nation_recno ) //**BUGHERE
+ enemyCombatLevel += townPtr->jobless_population * 5;
+ }
+*/
+ return enemyCombatLevel;
+}
+//-------- End of function Nation::enemy_town_combat_level ------//
+
+
+//--------- Begin of function Nation::enemy_firm_combat_level --------//
+//
+// <Firm*> targetFirm - the target firm
+// <int> returnIfWar - return -1 if there is any war around the town
+// <int&> hasWar - a reference var for returning whether there is any war
+//
+// return: <int> the enemy's total defense combat level minus the player's
+// combat level.
+/// return -1 if there is war and returnIfWar is 1
+//
+int Nation::enemy_firm_combat_level(Firm* targetFirm, int returnIfWar, int hasWar)
+{
+ int enemyCombatLevel = mobile_defense_combat_level(targetFirm->center_x, targetFirm->center_y, targetFirm->nation_recno, returnIfWar, hasWar); //0-don't return even there are wars around the town
+
+ if( enemyCombatLevel < 0 ) // there is a war going on
+ return -1;
+
+ //--- calculate the combat level of enemy camps linked to this towns that are linked to this mine ---//
+
+ Town* linkedTown;
+ Firm* firmPtr;
+ int targetNationRecno = targetFirm->nation_recno;
+
+ //---- scan towns linked to this mine -----//
+
+ for( int i=targetFirm->linked_town_count-1 ; i>=0 ; i-- )
+ {
+ linkedTown = town_array[ targetFirm->linked_town_array[i] ];
+
+ if( linkedTown->nation_recno != targetNationRecno )
+ continue;
+
+ //------ scan firms linked to this town -------//
+
+ for( int j=linkedTown->linked_firm_count-1 ; j>=0 ; j-- )
+ {
+ firmPtr = firm_array[ linkedTown->linked_firm_array[j] ];
+
+ if( firmPtr->nation_recno == targetNationRecno &&
+ firmPtr->firm_id == FIRM_CAMP )
+ {
+ enemyCombatLevel += ((FirmCamp*)firmPtr)->total_combat_level();
+ }
+ }
+ }
+
+ return enemyCombatLevel;
+}
+//-------- End of function Nation::enemy_firm_combat_level ------//
+
+
+//------- Begin of function Nation::mobile_defense_combat_level ------//
+//
+// Take into account of the mobile units around this target location
+// when considering attacking it.
+//
+// <int> targetXLoc, targetYLoc - the target location
+// <int> targetNationRecno - nation recno of the target
+// <int> returnIfWar - whether return -1 if there is war
+// around the given area.
+// <int&> hasWar - a var for returning whether there is war
+// around the given area.
+// 1 - if there is war
+// 2 - if our nation is involved in the war
+//
+// return : >= 0 the defense rating of this location, the rating can be < 0,
+// if we have our own units there.
+//
+// -1 don't attack this town because a battle is already
+// going on.
+//
+int Nation::mobile_defense_combat_level(int targetXLoc, int targetYLoc, int targetNationRecno, int returnIfWar, int& hasWar)
+{
+ //--- the scanning distance is determined by the AI aggressiveness setting ---//
+
+ int scanRangeX = 5 + config.ai_aggressiveness * 2;
+ int scanRangeY = scanRangeX;
+
+ int xLoc1 = targetXLoc - scanRangeX;
+ int yLoc1 = targetYLoc - scanRangeY;
+ int xLoc2 = targetXLoc + scanRangeX;
+ int yLoc2 = targetYLoc + scanRangeY;
+
+ xLoc1 = max( xLoc1, 0 );
+ yLoc1 = max( yLoc1, 0 );
+ xLoc2 = min( xLoc2, MAX_WORLD_X_LOC-1 );
+ yLoc2 = min( yLoc2, MAX_WORLD_Y_LOC-1 );
+
+ //------------------------------------------//
+
+ float totalCombatLevel=(float)0; // the higher the rating, the easier we can attack the target town.
+ int xLoc, yLoc;
+ Unit* unitPtr;
+ Location* locPtr;
+
+ hasWar = 0;
+
+ for( yLoc=yLoc1 ; yLoc<=yLoc2 ; yLoc++ )
+ {
+ locPtr = world.get_loc(xLoc1, yLoc);
+
+ for( xLoc=xLoc1 ; xLoc<=xLoc2 ; xLoc++, locPtr++ )
+ {
+ if( !locPtr->has_unit(UNIT_LAND) )
+ continue;
+
+ unitPtr = unit_array[ locPtr->unit_recno(UNIT_LAND) ];
+
+ //--------------------------------------------------//
+ // If there is already a battle going on in this town,
+ // do not attack this town.
+ //--------------------------------------------------//
+
+ if( unitPtr->cur_action == SPRITE_ATTACK )
+ {
+ if( returnIfWar )
+ return -1;
+ else
+ {
+ if( unitPtr->nation_recno == nation_recno )
+ hasWar = 2;
+ else
+ hasWar = 1;
+ }
+ }
+
+ //---- if this unit is guarding the town -----//
+
+ if( unitPtr->nation_recno == targetNationRecno )
+ {
+ totalCombatLevel += unitPtr->unit_power();
+ }
+
+ //------- if this is our own unit ------//
+
+ else if( unitPtr->nation_recno == nation_recno )
+ {
+ if( unitPtr->cur_action == SPRITE_ATTACK || // only units that are currently attacking or idle are counted, moving units may just be passing by
+ unitPtr->cur_action == SPRITE_IDLE )
+ {
+ totalCombatLevel -= unitPtr->unit_power();
+ }
+ }
+ }
+ }
+
+ if( totalCombatLevel == -1 ) // -1 is reserved for returning don't attack
+ return 0;
+ else
+ return (int) totalCombatLevel;
+}
+//-------- End of function Nation::mobile_defense_combat_level ------//
+
diff --git a/OAI_CAPT.cpp b/OAI_CAPT.cpp
new file mode 100644
index 0000000..f21fe0a
--- /dev/null
+++ b/OAI_CAPT.cpp
@@ -0,0 +1,603 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_CAPT.CPP
+//Description: AI - capturing independent towns
+
+#include <ALL.h>
+#include <OREMOTE.h>
+#include <OCONFIG.h>
+#include <OUNIT.h>
+#include <OF_CAMP.h>
+#include <OF_INN.h>
+#include <ONATION.h>
+
+//------- define struct CaptureTown -------//
+
+struct CaptureTown
+{
+ short town_recno;
+ short min_resistance;
+};
+
+//------ Declare static functions --------//
+
+static int sort_capture_town_function( const void *a, const void *b );
+
+//--------- Begin of function Nation::think_capture --------//
+//
+int Nation::think_capture()
+{
+ if( ai_camp_count==0 ) // this can happen when a new nation has just emerged
+ return 0;
+
+ //--- don't capture if the AI is using growth and capture strategy (as opposite to build mine strategy) ---//
+
+ if( ai_mine_count==0 && total_population < 25 )
+ return 0;
+
+ //-----------------------------------------//
+
+ if( think_capture_independent() )
+ return 1;
+
+ return 0;
+}
+//---------- End of function Nation::think_capture ---------//
+
+
+//--------- Begin of function Nation::think_capture_independent --------//
+//
+// Think about capturing independent towns.
+//
+int Nation::think_capture_independent()
+{
+ //------- Capture target choices -------//
+
+ #define MAX_CAPTURE_TOWN 30
+
+ CaptureTown capture_town_array[MAX_CAPTURE_TOWN];
+ short capture_town_count=0;
+
+ //--- find the town that makes most sense to capture ---//
+
+ int townRecno;
+ Town* townPtr;
+
+ for(townRecno=town_array.size(); townRecno>0; townRecno--)
+ {
+ if(town_array.is_deleted(townRecno))
+ continue;
+
+ townPtr = town_array[townRecno];
+
+ if( townPtr->nation_recno ) // only capture independent towns
+ continue;
+
+ if( townPtr->no_neighbor_space ) // if there is no space in the neighbor area for building a new firm.
+ continue;
+
+ if( townPtr->rebel_recno ) // towns controlled by rebels will not drop in resistance even if a command base is present
+ continue;
+
+ //------ only if we have a presence/a base town in this region -----//
+
+ if( !has_base_town_in_region(townPtr->region_id) )
+ continue;
+
+ //---- check if there are already camps linked to this town ----//
+
+ int i;
+ for( i=townPtr->linked_firm_count-1 ; i>=0 ; i-- )
+ {
+ Firm* firmPtr = firm_array[ townPtr->linked_firm_array[i] ];
+
+ if( firmPtr->firm_id != FIRM_CAMP )
+ continue;
+
+ //------ if we already have a camp linked to this town -----//
+
+ if( firmPtr->nation_recno == nation_recno )
+ break;
+
+ //--- if there is an overseer with high leadership and right race in the opponent's camp, do bother to compete with him ---//
+
+ if( firmPtr->overseer_recno )
+ {
+ Unit* unitPtr = unit_array[firmPtr->overseer_recno];
+
+ if( unitPtr->skill.skill_level >= 70 &&
+ unitPtr->race_id == townPtr->majority_race() )
+ {
+ break;
+ }
+ }
+ }
+
+ if( i>=0 ) // there is already a camp linked to this town and we don't want to get involved with its capturing plan
+ continue;
+
+ //-- if the town has linked military camps of the same nation --//
+
+ int targetResistance = capture_expected_resistance(townRecno);
+ int averageResistance = townPtr->average_resistance(nation_recno);
+ int minResistance = min( averageResistance, targetResistance );
+
+ if( minResistance < 50 - pref_peacefulness/5 ) // 30 to 50 depending on
+ {
+ capture_town_array[capture_town_count].town_recno = townRecno;
+ capture_town_array[capture_town_count].min_resistance = minResistance;
+
+ capture_town_count++;
+ }
+ }
+
+ //------ sort the capture target choices by min_resistance ----//
+
+ qsort( &capture_town_array, capture_town_count, sizeof(capture_town_array[0]), sort_capture_town_function );
+
+ //------- try to capture the town in their resistance order ----//
+
+ for( int i=0 ; i<capture_town_count ; i++ )
+ {
+ err_when( town_array.is_deleted(capture_town_array[i].town_recno) );
+
+ //-------------------------------------------//
+ // If the map is set to unexplored, wait for a
+ // reasonable amount of time before moving out
+ // to build the mine.
+ //-------------------------------------------//
+
+ if( !config.explore_whole_map )
+ {
+ Town* targetTown = town_array[ capture_town_array[i].town_recno ];
+
+ int j;
+ for( j=0 ; j<ai_town_count ; j++ )
+ {
+ Town* ownTown = town_array[ ai_town_array[j] ];
+
+ int townDistance = m.points_distance(targetTown->center_x, targetTown->center_y,
+ ownTown->center_x, ownTown->center_y);
+
+ if( info.game_date-info.game_start_date >
+ townDistance * (5-config.ai_aggressiveness) / 5 ) // 3 to 5 / 5
+ {
+ break;
+ }
+ }
+
+ if( j==ai_town_count )
+ continue;
+ }
+
+ if( start_capture( capture_town_array[i].town_recno ) )
+ return 1;
+ }
+
+ return 0;
+}
+//---------- End of function Nation::think_capture_independent ---------//
+
+
+//--------- Begin of function Nation::should_use_cash_to_capture --------//
+//
+int Nation::should_use_cash_to_capture()
+{
+ //--- if we have plenty of cash, use cash to decrease the resistance of the villagers ---//
+
+ return military_rank_rating() < 50+pref_peacefulness/5 && // 50 to 70
+ ai_should_spend(pref_loyalty_concern/4);
+}
+//---------- End of function Nation::should_use_cash_to_capture ---------//
+
+
+//--------- Begin of function Nation::capture_expected_resistance --------//
+//
+// The lowest resistance can be expected if we are going to capture the
+// town.
+//
+int Nation::capture_expected_resistance(int townRecno)
+{
+ //--- we have plenty of cash, use cash to decrease the resistance of the villagers ---//
+
+ if( should_use_cash_to_capture() )
+ return 0; // return zero resistance
+
+ //----- the average resistance determines the captureRating ------//
+
+ int captureRating = 0;
+ Town* townPtr = town_array[townRecno];
+
+ int averageResistance;
+
+ if( townPtr->nation_recno )
+ averageResistance = townPtr->average_loyalty();
+ else
+ averageResistance = townPtr->average_resistance(nation_recno);
+
+ //----- get the id. of the most populated races in the town -----//
+
+ int majorityRace = townPtr->majority_race();
+
+ err_when( !majorityRace ); // this should not happen
+
+ //---- see if there are general available for capturing this town ---//
+
+ int targetResistance=0;
+
+ if( !find_best_capturer(townRecno, majorityRace, targetResistance) )
+ return 100;
+
+ int resultResistance =
+ ( targetResistance * townPtr->race_pop_array[majorityRace-1] +
+ averageResistance * (townPtr->population - townPtr->race_pop_array[majorityRace-1]) )
+ / townPtr->population;
+
+ return resultResistance;
+}
+//---------- End of function Nation::capture_expected_resistance ---------//
+
+
+//--------- Begin of function Nation::start_capture --------//
+//
+int Nation::start_capture(int townRecno)
+{
+ //--- find the two races with most population in the town ---//
+
+ Town* townPtr = town_array[townRecno];
+
+ int majorityRace=0;
+
+ //--- if it's an independent town, the race of the commander must match with the race of the town ---//
+
+ if( townPtr->nation_recno == 0 )
+ {
+ majorityRace = townPtr->majority_race();
+ err_when( !majorityRace ); // this shouldn't happen
+ }
+
+ //---- see if we have generals in the most populated race, if so build a camp next to the town ----//
+
+ return capture_build_camp(townRecno, majorityRace);
+}
+//---------- End of function Nation::start_capture ---------//
+
+
+//--------- Begin of function Nation::capture_build_camp --------//
+//
+int Nation::capture_build_camp(int townRecno, int raceId)
+{
+ Town* captureTown = town_array[townRecno];
+
+ //---- find the best available general for the capturing action ---//
+
+ int targetResistance;
+
+ int unitRecno = find_best_capturer(townRecno, raceId, targetResistance);
+
+ if( !unitRecno )
+ unitRecno = hire_best_capturer(townRecno, raceId);
+
+ if( !unitRecno )
+ {
+ //--- if we have plenty of cash and can use cash to decrease the resistance of the independent villagers ---//
+
+ if( should_use_cash_to_capture() )
+ {
+ char resultFlag;
+
+ Unit* skilledUnit = find_skilled_unit(SKILL_LEADING, raceId,
+ captureTown->center_x, captureTown->center_y, resultFlag);
+
+ if( skilledUnit )
+ unitRecno = skilledUnit->sprite_recno;
+ }
+
+ if( !unitRecno )
+ return 0;
+ }
+
+ //------- locate a place to build the camp -------//
+
+ short buildXLoc, buildYLoc;
+
+ if( !find_best_firm_loc(FIRM_CAMP, captureTown->loc_x1, captureTown->loc_y1, buildXLoc, buildYLoc) )
+ {
+ captureTown->no_neighbor_space = 1;
+ return 0;
+ }
+
+ //--- if the picked unit is an overseer of an existng camp ---//
+
+ if( !mobilize_capturer(unitRecno) )
+ return 0;
+
+ //---------- add the action to the queue now ----------//
+
+ err_when( captureTown->nation_recno==0 &&
+ unit_array[unitRecno]->race_id != captureTown->majority_race() );
+
+ int actionRecno = add_action( buildXLoc, buildYLoc, captureTown->loc_x1, captureTown->loc_y1,
+ ACTION_AI_BUILD_FIRM, FIRM_CAMP, 1, unitRecno );
+
+ if( actionRecno )
+ process_action(actionRecno);
+
+ return 1;
+}
+//---------- End of function Nation::capture_build_camp ---------//
+
+
+//-------- Begin of function Nation::find_best_capturer ------//
+//
+// Find an existing unit as the capturer of the town.
+//
+// <int> townRecno - recno of the town to capture
+// <int> raceId - race id. of the capturer. 0 if any races.
+// <int&> bestTargetResistance - a reference var for returning the target resistance if the returned unit is assigned as the overseer
+//
+// return: <int> the recno of the unit found.
+//
+int Nation::find_best_capturer(int townRecno, int raceId, int& bestTargetResistance)
+{
+ #define MIN_CAPTURE_RESISTANCE_DEC 20 // if we assign a unit as the commander, the minimum expected resistance decrease should be 20, otherwise we don't do it.
+
+ Unit* unitPtr;
+ Town* targetTown = town_array[townRecno];
+ Firm* firmPtr;
+ int targetResistance;
+ int bestUnitRecno=0;
+
+ bestTargetResistance = 100;
+
+ for( int i=ai_general_count-1 ; i>=0 ; i-- )
+ {
+ unitPtr = unit_array[ ai_general_array[i] ];
+
+ if( raceId && unitPtr->race_id != raceId )
+ continue;
+
+ err_when( unitPtr->nation_recno != nation_recno );
+ err_when( unitPtr->rank_id != RANK_KING && unitPtr->rank_id != RANK_GENERAL );
+
+ if( unitPtr->nation_recno != nation_recno )
+ continue;
+
+ //---- if this unit is on a mission ----//
+
+ if( unitPtr->home_camp_firm_recno )
+ continue;
+
+ //---- don't use the king to build camps next to capture enemy towns, only next to independent towns ----//
+
+ if( unitPtr->rank_id == RANK_KING && targetTown->nation_recno )
+ continue;
+
+ //----- if this unit is in a camp -------//
+
+ if( unitPtr->unit_mode == UNIT_MODE_OVERSEE )
+ {
+ firmPtr = firm_array[unitPtr->unit_mode_para];
+
+ //--- check if the unit currently in a command base trying to take over an independent town ---//
+
+ int j;
+ for( j=firmPtr->linked_town_count-1 ; j>=0 ; j-- )
+ {
+ Town* townPtr = town_array[ firmPtr->linked_town_array[j] ];
+
+ //--- if the unit is trying to capture an independent town and he is still influencing the town to decrease resistance ---//
+
+ if( townPtr->nation_recno==0 &&
+ townPtr->average_target_resistance(nation_recno) <
+ townPtr->average_resistance(nation_recno) )
+ {
+ break; // then don't use this unit
+ }
+ }
+
+ if( j>=0 ) // if so, don't use this unit
+ continue;
+ }
+
+ //--- if this unit is idle and the region ids are matched ---//
+
+ if( unitPtr->action_mode != ACTION_STOP ||
+ unitPtr->region_id() != targetTown->region_id )
+ {
+ continue;
+ }
+
+ //------- get the unit's influence index --------//
+
+ err_when( unitPtr->skill.skill_id != SKILL_LEADING );
+
+ targetResistance = 100-targetTown->camp_influence(unitPtr->sprite_recno); // influence of this unit if he is assigned as a commander of a military camp
+
+ //-- see if this unit's rating is higher than the current best --//
+
+ if( targetResistance < bestTargetResistance )
+ {
+ bestTargetResistance = targetResistance;
+ bestUnitRecno = unitPtr->sprite_recno;
+ }
+ }
+
+ return bestUnitRecno;
+}
+//-------- End of function Nation::find_best_capturer -------//
+
+
+//-------- Begin of function Nation::mobilize_capturer ------//
+//
+// Mobilize the capturer unit if he isn't mobilized yet.
+//
+int Nation::mobilize_capturer(int unitRecno)
+{
+ //--- if the picked unit is an overseer of an existng camp ---//
+
+ Unit* unitPtr = unit_array[unitRecno];
+
+ if( unitPtr->unit_mode == UNIT_MODE_OVERSEE )
+ {
+ Firm* firmPtr = firm_array[unitPtr->unit_mode_para];
+ Town* townPtr;
+
+ //-- can recruit from either a command base or seat of power --//
+
+ //-- train a villager with leadership to replace current overseer --//
+
+ int i;
+ for( i=0 ; i<firmPtr->linked_town_count ; i++ )
+ {
+ townPtr = town_array[ firmPtr->linked_town_array[i] ];
+
+ if( townPtr->nation_recno != nation_recno )
+ continue;
+
+ //--- first try to train a unit who is racially homogenous to the commander ---//
+
+ int unitRecno = townPtr->recruit( SKILL_LEADING, firmPtr->majority_race(), COMMAND_AI );
+
+ //--- if unsucessful, then try to train a unit whose race is the same as the majority of the town ---//
+
+ if( !unitRecno )
+ unitRecno = townPtr->recruit( SKILL_LEADING, townPtr->majority_race(), COMMAND_AI );
+
+ if( unitRecno )
+ {
+ add_action(townPtr->loc_x1, townPtr->loc_y1, -1, -1, ACTION_AI_ASSIGN_OVERSEER, FIRM_CAMP);
+ break;
+ }
+ }
+
+ if( i==firmPtr->linked_town_count ) // unsuccessful
+ return 0;
+
+ //------- mobilize the current overseer --------//
+
+ firmPtr->mobilize_overseer();
+ }
+
+ return 1;
+}
+//-------- End of function Nation::mobilize_capturer -------//
+
+
+//-------- Begin of function Nation::hire_best_capturer ------//
+//
+// Hire the best unit you can find in one of the existing inns.
+//
+// <int> townRecno - recno of the town to capture
+// <int> raceId - race id. of the unit to hire
+//
+// return: <int> the recno of the unit hired.
+//
+int Nation::hire_best_capturer(int townRecno, int raceId)
+{
+ if( !ai_should_hire_unit(30) ) // 30 - importance rating
+ return 0;
+
+ FirmInn *firmInn;
+ Firm *firmPtr;
+ InnUnit *innUnit;
+ Skill *innUnitSkill;
+ int i, j, innUnitCount, curRating;
+ int bestRating=0, bestInnRecno=0, bestInnUnitId=0;
+ Town* townPtr = town_array[townRecno];
+ int destRegionId = world.get_region_id(townPtr->loc_x1, townPtr->loc_y1);
+
+ for(i=0; i<ai_inn_count; i++)
+ {
+ firmPtr = (FirmInn*) firm_array[ai_inn_array[i]];
+
+ err_when( firmPtr->firm_id != FIRM_INN );
+
+ if( firmPtr->region_id != destRegionId )
+ continue;
+
+ firmInn = firmPtr->cast_to_FirmInn();
+
+ innUnitCount=firmInn->inn_unit_count;
+
+ if( !innUnitCount )
+ continue;
+
+ innUnit = firmInn->inn_unit_array + innUnitCount - 1;
+
+ //------- check units in the inn ---------//
+
+ for(j=innUnitCount; j>0; j--, innUnit--)
+ {
+ innUnitSkill = &(innUnit->skill);
+
+ if( innUnitSkill->skill_id==SKILL_LEADING &&
+ unit_res[innUnit->unit_id]->race_id == raceId &&
+ cash >= innUnit->hire_cost )
+ {
+ //----------------------------------------------//
+ // evalute a unit on:
+ // -its race, whether it's the same as the nation's race
+ // -the inn's distance from the destination
+ // -the skill level of the unit.
+ //----------------------------------------------//
+
+ curRating = innUnitSkill->skill_level;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+
+ bestInnRecno = firmInn->firm_recno;
+ bestInnUnitId = j;
+ }
+ }
+ }
+ }
+
+ if( !bestInnUnitId )
+ return 0;
+
+ //----------------------------------------------------//
+
+ firmInn = (FirmInn*) firm_array[bestInnRecno];
+
+ int unitRecno = firmInn->hire(bestInnUnitId);
+
+ if( !unitRecno )
+ return 0;
+
+ unit_array[unitRecno]->set_rank(RANK_GENERAL);
+
+ return unitRecno;
+}
+//-------- End of function Nation::hire_best_capturer -------//
+
+
+//------ Begin of function sort_capture_town_function ------//
+//
+static int sort_capture_town_function( const void *a, const void *b )
+{
+ return ((CaptureTown*)a)->min_resistance - ((CaptureTown*)b)->min_resistance;
+}
+//------- End of function sort_capture_town_function ------//
+
+
diff --git a/OAI_DEFE.cpp b/OAI_DEFE.cpp
new file mode 100644
index 0000000..dc6b0c7
--- /dev/null
+++ b/OAI_DEFE.cpp
@@ -0,0 +1,96 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_DEFE.CPP
+//Description: AI on defense
+
+#include <ALL.h>
+#include <OTALKRES.h>
+#include <ONATION.h>
+
+//----- Begin of function Nation::ai_defend -----//
+//
+// <int> attackerUnitRecno - unit recno of the attacker.
+//
+int Nation::ai_defend(int attackerUnitRecno)
+{
+ //--- don't call for defense too frequently, only call once 7 days (since this function will be called every time our king/firm/town is attacked, so this filtering is necessary ---//
+
+ if( info.game_date < ai_last_defend_action_date+7 )
+ return 0;
+
+ ai_last_defend_action_date = info.game_date;
+
+ //---------- analyse the situation first -----------//
+
+ Unit* attackerUnit = unit_array[attackerUnitRecno];
+
+ err_when( attackerUnit->nation_recno == nation_recno );
+
+ int attackerXLoc = attackerUnit->next_x_loc();
+ int attackerYLoc = attackerUnit->next_y_loc();
+
+ int hasWar;
+
+ int enemyCombatLevel = mobile_defense_combat_level( attackerXLoc, attackerYLoc,
+ attackerUnit->nation_recno, 0, hasWar ); // 0-don't return immediately even if there is war around this town
+
+ //-- the value returned is enemy strength minus your own strength, so if it's positive, it means that your enemy is stronger than you, otherwise you're stronger than your enemy --//
+
+ int attackCombatLevel = ai_attack_target(attackerXLoc, attackerYLoc, enemyCombatLevel, 1); // 1-defense mode
+
+ //------ request military aid from allies ----//
+
+ if( attackCombatLevel < enemyCombatLevel && attackerUnit->nation_recno )
+ {
+ ai_request_military_aid();
+ }
+
+ return 1;
+}
+//----- End of function Nation::ai_defend -----//
+
+
+//----- Begin of function Nation::ai_request_military_aid -----//
+//
+// Request allied nations to provide immediate military aid.
+//
+int Nation::ai_request_military_aid()
+{
+ return 0; //**BUGHERE, multiplayer sync error, disabled temporarily
+
+ for( int i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) )
+ continue;
+
+ if( get_relation(i)->status != NATION_ALLIANCE )
+ continue;
+
+ if( should_diplomacy_retry(TALK_REQUEST_MILITARY_AID, i) )
+ {
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_REQUEST_MILITARY_AID);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+//----- End of function Nation::ai_request_military_aid -----//
diff --git a/OAI_DIPL.cpp b/OAI_DIPL.cpp
new file mode 100644
index 0000000..dcbb58d
--- /dev/null
+++ b/OAI_DIPL.cpp
@@ -0,0 +1,1092 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_DIPL.CPP
+//Description: AI functions on diplomacy
+
+#include <OTALKRES.h>
+#include <OCONFIG.h>
+#include <OTECHRES.h>
+#include <ONATION.h>
+
+
+//----- Begin of function Nation::think_diplomacy -----//
+//
+void Nation::think_diplomacy()
+{
+ //--- process incoming messages first, so we won't send out the same request to nation which has already proposed the same thing ---//
+
+ int nationRecno = nation_recno;
+
+ process_action(0, ACTION_AI_PROCESS_TALK_MSG);
+
+ if( nation_array.is_deleted(nationRecno) ) // the nation may have been deleted, if the nation accepts a purchase kingdom offer
+ return;
+
+ //---- thinking about war first -----//
+
+ if( think_declare_war() )
+ return;
+
+ //----- think buy food first -------//
+
+ think_request_buy_food(); // don't return even if this request is sent
+
+ //----- think request cease fire ----//
+
+ if( think_request_cease_war() )
+ return;
+
+ //------ thinking about treaty ---------//
+
+ if( think_trade_treaty() )
+ return;
+
+ if( think_propose_alliance_treaty() ) // try proposing alliance treaty first, then try proposing friendly treaty
+ return;
+
+ if( think_propose_friendly_treaty() )
+ return;
+
+ if( think_end_treaty() )
+ return;
+
+ //-------- think about other matters --------//
+
+ if( think_demand_tribute_aid() )
+ return;
+
+ if( think_give_tech() )
+ return;
+
+ if( think_demand_tech() )
+ return;
+
+ //---- think about offering to purchase throne ----//
+
+ if( think_request_surrender() )
+ return;
+}
+//------ End of function Nation::think_diplomacy ------//
+
+
+//----- Begin of function Nation::should_diplomacy_retry -----//
+//
+int Nation::should_diplomacy_retry(int talkId, int nationRecno)
+{
+ if( !talk_res.can_send_msg(nationRecno, nation_recno, talkId ) )
+ return 0;
+
+ int retryInterval;
+
+ //--- shorter retry interval for demand talk message ----//
+
+ if( talkId == TALK_DEMAND_TRIBUTE ||
+ talkId == TALK_DEMAND_AID ||
+ talkId == TALK_DEMAND_TECH )
+ {
+ retryInterval = 60 + 60 * (100-pref_diplomacy_retry) / 100; // 2-4 months
+ }
+ else
+ {
+ retryInterval = 90 + 270 * (100-pref_diplomacy_retry) / 100; // 3 months to 12 months before next try
+ }
+
+ return info.game_date >
+ get_relation(nationRecno)->last_talk_reject_date_array[talkId-1] + retryInterval;
+}
+//------ End of function Nation::should_diplomacy_retry ------//
+
+
+//----- Begin of function Nation::ai_notify_reply -----//
+//
+// Notify this AI nation that there is a reply to one
+// of the diplomatic messages that it has sent out.
+//
+void Nation::ai_notify_reply(int talkMsgRecno)
+{
+ err_when( talk_res.is_talk_msg_deleted(talkMsgRecno) );
+
+ TalkMsg* talkMsg = talk_res.get_talk_msg(talkMsgRecno);
+ int relationChange = 0;
+ NationRelation* nationRelation = get_relation(talkMsg->to_nation_recno);
+
+ if( talkMsg->reply_type == REPLY_REJECT )
+ nationRelation->last_talk_reject_date_array[talkMsg->talk_id-1] = info.game_date;
+ else
+ nationRelation->last_talk_reject_date_array[talkMsg->talk_id-1] = 0;
+
+ switch( talkMsg->talk_id )
+ {
+ case TALK_PROPOSE_TRADE_TREATY:
+ if( talkMsg->reply_type == REPLY_ACCEPT )
+ relationChange = pref_trading_tendency/10;
+ else
+ relationChange = -pref_trading_tendency/10;
+ break;
+
+ case TALK_PROPOSE_FRIENDLY_TREATY:
+ case TALK_PROPOSE_ALLIANCE_TREATY:
+ if( talkMsg->reply_type == REPLY_REJECT )
+ relationChange = -5;
+ break;
+
+ case TALK_REQUEST_MILITARY_AID:
+ if( talkMsg->reply_type == REPLY_ACCEPT )
+ relationChange = 0; // the AI never knows whether the player has really aided him in the war
+ else
+ relationChange = -(20-pref_military_courage/10); // -10 to -20
+ break;
+
+ case TALK_REQUEST_TRADE_EMBARGO:
+ if( talkMsg->reply_type == REPLY_ACCEPT )
+ relationChange = (10+pref_trading_tendency/10); // +10 to +20
+ else
+ relationChange = -(10+pref_trading_tendency/20); // -10 to -15
+ break;
+
+ case TALK_REQUEST_CEASE_WAR:
+ if( talkMsg->reply_type == REPLY_REJECT )
+ relationChange = -5;
+ break;
+
+ case TALK_REQUEST_DECLARE_WAR:
+ if( talkMsg->reply_type == REPLY_ACCEPT )
+ relationChange = pref_allying_tendency/10;
+ else
+ relationChange = -30;
+ break;
+
+ case TALK_REQUEST_BUY_FOOD:
+ if( talkMsg->reply_type == REPLY_ACCEPT )
+ relationChange = pref_food_reserve/10;
+ else
+ relationChange = -pref_food_reserve/10;
+ break;
+
+ case TALK_DEMAND_TRIBUTE:
+ case TALK_DEMAND_AID:
+ if( talkMsg->reply_type == REPLY_ACCEPT )
+ {
+ //-- the less cash the nation, the more it will appreciate the tribute --//
+
+ relationChange = 100 * talkMsg->talk_para1 / max(1000, (int) cash);
+ }
+ else
+ {
+ relationChange = -(400-pref_peacefulness)/10; // -30 to 40 points depending the peacefulness preference
+ }
+ break;
+
+ case TALK_DEMAND_TECH:
+ if( talkMsg->reply_type == REPLY_ACCEPT )
+ relationChange = 10+pref_use_weapon/5; // +10 to +30
+ else
+ relationChange = -(10+pref_use_weapon/10); // -10 to -20
+ break;
+
+ case TALK_GIVE_TRIBUTE:
+ case TALK_GIVE_AID:
+ case TALK_GIVE_TECH:
+ if( talkMsg->reply_type == REPLY_REJECT ) // reject your gift
+ relationChange = -5;
+ break;
+
+ case TALK_REQUEST_SURRENDER: // no relation change on this request
+ break;
+
+ default:
+ err_here();
+ }
+
+ //------- chance relationship now -------//
+
+ if( relationChange < 0 )
+ relationChange -= relationChange * (200-pref_forgiveness) / 200;
+
+ if( relationChange != 0 )
+ change_ai_relation_level( talkMsg->to_nation_recno, relationChange );
+
+ //---- think about giving tribute to become more friendly with the nation so it will accept our request next time ---//
+
+ if( talkMsg->reply_type == REPLY_REJECT )
+ {
+ if( think_give_tribute_aid( talkMsg ) )
+ return;
+
+ //--- if our request was rejected, end treaty if the ai_nation_relation is low enough ---//
+
+ if( talkMsg->talk_id != TALK_PROPOSE_ALLIANCE_TREATY && // the rejected request is not alliance treaty
+ nationRelation->status >= NATION_FRIENDLY &&
+ nationRelation->ai_relation_level < 40-pref_allying_tendency/5 ) // 20 to 40
+ {
+ int talkId;
+
+ if( nationRelation->status == NATION_FRIENDLY )
+ talkId = TALK_END_FRIENDLY_TREATY;
+ else
+ talkId = TALK_END_ALLIANCE_TREATY;
+
+ talk_res.ai_send_talk_msg(talkMsg->to_nation_recno, nation_recno, talkId);
+ }
+
+ //----- declare war if ai_relation_level==0 -----//
+
+ else if( nationRelation->ai_relation_level == 0 )
+ {
+ //--------- declare war ---------//
+
+ if( config.ai_aggressiveness >= OPTION_HIGH || pref_peacefulness < 50 )
+ {
+ talk_res.ai_send_talk_msg(talkMsg->to_nation_recno, nation_recno, TALK_DECLARE_WAR);
+
+ //------- attack immediately --------//
+
+ if( config.ai_aggressiveness >= OPTION_VERY_HIGH ||
+ ( config.ai_aggressiveness >= OPTION_HIGH && pref_peacefulness < 50 ) )
+ {
+ if( largest_town_recno )
+ {
+ think_capture_new_enemy_town( town_array[largest_town_recno], 1 ); // 1-use forces from all camps to attack the target
+ }
+ }
+ }
+ }
+ }
+}
+//------ End of function Nation::ai_notify_reply ------//
+
+
+//----- Begin of function Nation::think_propose_friendly_treaty -----//
+//
+int Nation::think_propose_friendly_treaty()
+{
+ //--- think about which nation this nation should propose treaty to ---//
+
+ int curRating, bestRating=0, bestNationRecno=0;
+ NationRelation* nationRelation;
+
+ for( int i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationRelation = get_relation(i);
+
+ if( !nationRelation->has_contact || nationRelation->status >= NATION_FRIENDLY )
+ continue;
+
+ if( !should_diplomacy_retry(TALK_PROPOSE_FRIENDLY_TREATY, i) )
+ continue;
+
+ curRating = consider_friendly_treaty(i);
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestNationRecno = i;
+ }
+ }
+
+ if( bestNationRecno )
+ {
+ talk_res.ai_send_talk_msg(bestNationRecno, nation_recno, TALK_PROPOSE_FRIENDLY_TREATY );
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_propose_friendly_treaty ------//
+
+
+//----- Begin of function Nation::think_propose_alliance_treaty -----//
+//
+int Nation::think_propose_alliance_treaty()
+{
+ //--- think about which nation this nation should propose treaty to ---//
+
+ int curRating, bestRating=0, bestNationRecno=0;
+ NationRelation* nationRelation;
+
+ for( int i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationRelation = get_relation(i);
+
+ if( !nationRelation->has_contact || nationRelation->status == NATION_ALLIANCE )
+ continue;
+
+ if( !should_diplomacy_retry(TALK_PROPOSE_ALLIANCE_TREATY, i) )
+ continue;
+
+ curRating = consider_alliance_treaty(i);
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestNationRecno = i;
+ }
+ }
+
+ if( bestNationRecno )
+ {
+ talk_res.ai_send_talk_msg(bestNationRecno, nation_recno, TALK_PROPOSE_ALLIANCE_TREATY );
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_propose_alliance_treaty ------//
+
+
+//----- Begin of function Nation::think_request_cease_war -----//
+//
+int Nation::think_request_cease_war()
+{
+ Nation* nationPtr;
+ NationRelation* nationRelation;
+
+ for( int i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ nationRelation = get_relation(i);
+
+ if( nationRelation->status != NATION_HOSTILE )
+ continue;
+
+ if( !should_diplomacy_retry(TALK_REQUEST_CEASE_WAR, i) )
+ continue;
+
+ //----- think about if it should cease war with the nation ------//
+
+ if( consider_cease_war(i) > 0 )
+ {
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_REQUEST_CEASE_WAR);
+ }
+
+ //--------------------------------------------//
+ // The relation improves slowly if there is
+ // no attack. However, if there is any battles
+ // started between the two nations, the status will be
+ // set to hostile and ai_relation_level will be set to 0 again.
+ //--------------------------------------------//
+
+ else
+ {
+ change_ai_relation_level(i, 1);
+ }
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_request_cease_war ------//
+
+
+//----- Begin of function Nation::think_end_treaty -----//
+//
+int Nation::think_end_treaty()
+{
+ if( pref_honesty < 30 ) // never formally end a treaty if the honesty is < 30
+ return 0;
+
+ Nation* nationPtr;
+ NationRelation* nationRelation;
+
+ for( int i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationRelation = get_relation(i);
+
+ if( nationRelation->status < NATION_FRIENDLY )
+ continue;
+
+ if( nationRelation->ai_secret_attack ||
+ ( nationRelation->ai_relation_level < 30 && trade_rating(i) < 50 ) )
+ {
+ //--- don't change terminate treaty too soon ---//
+
+ if( info.game_date < nationRelation->last_change_status_date+60+pref_honesty/2 ) // only after 60 to 110 days
+ continue;
+
+ //----------------------------------------//
+
+ if( !talk_res.can_send_msg(i, nation_recno, nationRelation->status==NATION_FRIENDLY ? TALK_END_FRIENDLY_TREATY : TALK_END_ALLIANCE_TREATY) )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ //-----------------------------------------//
+ // What makes it tend to end treaty:
+ // -higher honesty
+ // -a larger overall power over the target nation.
+ //
+ // If honesty is > 50, if will end treaty
+ // if its power is equal to the enemy.
+ //
+ // If honesty is < 50, if will end treaty
+ // if its power is larger than the enemy.
+ //
+ // If honesty is > 50, if will end treaty
+ // even if its power is lower than the enemy.
+ //-----------------------------------------//
+
+ if( pref_honesty-50 > nationPtr->overall_rating - overall_rating )
+ {
+ if( nationRelation->status == NATION_FRIENDLY )
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_END_FRIENDLY_TREATY);
+ else
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_END_ALLIANCE_TREATY);
+
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_end_treaty ------//
+
+
+//----- Begin of function Nation::think_trade_treaty -----//
+//
+int Nation::think_trade_treaty()
+{
+ Nation* nationPtr;
+ NationRelation *ourRelation;
+
+ for( int i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ ourRelation = get_relation(i);
+
+ if( !ourRelation->has_contact )
+ continue;
+
+ //------- propose a trade treaty --------//
+
+ if( !ourRelation->trade_treaty )
+ {
+ if( consider_trade_treaty(i) > 0 )
+ {
+ if( should_diplomacy_retry(TALK_PROPOSE_TRADE_TREATY, i) )
+ {
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_PROPOSE_TRADE_TREATY);
+ ourRelation->ai_demand_trade_treaty = 0;
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_trade_treaty ------//
+
+
+//----- Begin of function Nation::think_request_buy_food -----//
+//
+int Nation::think_request_buy_food()
+{
+ //------ first see if we need to buy food ------//
+
+ int yearFoodChange = yearly_food_change();
+ int neededFoodLevel;
+
+ if( yearFoodChange > 0 )
+ {
+ if( food > 0 )
+ return 0;
+ else
+ neededFoodLevel = (int) -food; // if the food is negative
+ }
+ else
+ {
+ neededFoodLevel = -yearFoodChange * (100+pref_food_reserve) / 50;
+
+ if( food > neededFoodLevel ) // one to three times (based on pref_food_reserve) of the food needed in a year,
+ return 0;
+ }
+
+ //----- think about which nation to buy food from -----//
+
+ Nation *nationPtr, *bestNation=NULL;
+ int curRating, bestRating=0;
+ int relationStatus;
+
+ int i;
+ for( i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ if( nationPtr->food < 500 ) // if the nation is short of food itself. The minimum request purchase qty is 500
+ continue;
+
+ relationStatus = get_relation_status(i);
+
+ if( relationStatus == NATION_HOSTILE || !get_relation(i)->has_contact )
+ continue;
+
+ if( nationPtr->yearly_food_change() < 0 &&
+ nationPtr->food < 1500 )
+ {
+ continue;
+ }
+
+ if( !should_diplomacy_retry(TALK_REQUEST_BUY_FOOD, i) )
+ continue;
+
+ //-----------------------------------//
+
+ curRating = relationStatus*20 +
+ (int)nationPtr->food / 100 +
+ (int)nationPtr->yearly_food_change() / 10;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestNation = nationPtr;
+ }
+ }
+
+ if( !bestNation )
+ return 0;
+
+ //------------------------------------//
+
+ static short buyQtyArray[] = { 500, 1000, 2000, 4000 };
+
+ int buyQty=0, buyPrice;
+
+ for( i=3 ; i>=0 ; i-- )
+ {
+ if( bestNation->food/2 > buyQtyArray[i] )
+ {
+ buyQty = buyQtyArray[i];
+ break;
+ }
+ }
+
+ if( buyQty == 0 )
+ return 0;
+
+ //------- set the offering price ------//
+
+ if( food < neededFoodLevel/4 ) // if we need the food badly
+ {
+ buyPrice = 30;
+ }
+ else if( food < neededFoodLevel/3 )
+ {
+ buyPrice = 20;
+ }
+ else
+ {
+ if( bestNation->food > bestNation->all_population() * PERSON_FOOD_YEAR_CONSUMPTION * 5 && // if the nation has plenty of food
+ bestNation->cash < bestNation->fixed_expense_365days() / 2 ) // if the nation runs short of cash
+ {
+ buyPrice = 5;
+ }
+ else
+ buyPrice = 10;
+ }
+
+ talk_res.ai_send_talk_msg(bestNation->nation_recno, nation_recno, TALK_REQUEST_BUY_FOOD, buyQty, buyPrice);
+ return 1;
+}
+//------ End of function Nation::think_request_buy_food ------//
+
+
+//----- Begin of function Nation::think_declare_war -----//
+//
+int Nation::think_declare_war()
+{
+ NationRelation* nationRelation;
+ int rc=0;
+
+ //---- don't declare a new war if we already has enemies ---//
+
+ int i;
+ for( i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ if( get_relation(i)->status == NATION_HOSTILE )
+ return 0;
+ }
+
+ //------------------------------------------------//
+
+ int targetStrength, minStrength=0x1000, bestTargetNation=0;
+
+ for( i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationRelation = get_relation(i);
+
+ if( !nationRelation->has_contact )
+ continue;
+
+ if( nationRelation->status == NATION_HOSTILE ) // already at war
+ continue;
+
+ if( nationRelation->ai_relation_level >= 10 )
+ continue;
+
+ if( !ai_should_spend( 100-trade_rating(i) ) ) // if trade_rating is 0, importanceRating will be 100, if trade_rating is 100, importanceRating will be 0
+ continue;
+
+ //----------------------------------------//
+
+ Nation* targetNation = nation_array[i];
+
+ targetStrength = targetNation->military_rank_rating() +
+ targetNation->population_rank_rating()/2 +
+ targetNation->economic_rank_rating()/3;
+
+ if( targetStrength < minStrength )
+ {
+ minStrength = targetStrength;
+ bestTargetNation = i;
+ }
+ }
+
+ //------------------------------------------//
+
+ if( bestTargetNation )
+ {
+ if( should_diplomacy_retry(TALK_DECLARE_WAR, bestTargetNation) )
+ {
+ talk_res.ai_send_talk_msg(bestTargetNation, nation_recno, TALK_DECLARE_WAR);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_declare_war ------//
+
+
+//----- Begin of function Nation::think_give_tribute_aid -----//
+//
+// This function is called when a nation rejected our request
+// which is important to us and we want to give tribute to the
+// nation so it may accept next time.
+//
+// <TalkMsg*> rejectedMsg - the TalkMsg that has been rejected.
+//
+int Nation::think_give_tribute_aid(TalkMsg* rejectedMsg)
+{
+ //-----------get the talk id. ------------//
+
+ int talkId;
+ int talkNationRecno = rejectedMsg->to_nation_recno;
+ int rejectedTalkId = rejectedMsg->talk_id;
+ NationRelation* nationRelation = get_relation(talkNationRecno);
+
+ if( nationRelation->status >= NATION_FRIENDLY )
+ talkId = TALK_GIVE_AID;
+ else
+ talkId = TALK_GIVE_TRIBUTE;
+
+ //-------- don't give tribute too frequently -------//
+
+ if( info.game_date <
+ nationRelation->last_talk_reject_date_array[talkId-1] + 365 - pref_allying_tendency )
+ {
+ return 0;
+ }
+
+ //---- think if the nation should spend money now ----//
+
+ static short tributeAmountArray[] = { 500, 1000 };
+ int tributeAmount = tributeAmountArray[m.random(2)];
+
+ if( !ai_should_spend(0, (float) tributeAmount) ) // importance rating is 0
+ return 0;
+
+ //--------------------------------------//
+
+ Nation* talkNation = nation_array[talkNationRecno];
+ int rc;
+
+ if( rejectedTalkId == TALK_PROPOSE_TRADE_TREATY )
+ {
+ rc = ai_trade_with_rating(talkNationRecno) > 100-pref_trading_tendency/2;
+ }
+
+ else if ( rejectedTalkId == TALK_PROPOSE_FRIENDLY_TREATY ||
+ rejectedTalkId == TALK_PROPOSE_ALLIANCE_TREATY )
+ {
+ int curRating = talkNation->trade_rating(talkNationRecno) +
+ ai_trade_with_rating(talkNationRecno) +
+ talkNation->overall_rating - overall_rating;
+
+ int acceptRating = 200-pref_trading_tendency/4
+ -pref_allying_tendency/4;
+
+ rc = curRating >= acceptRating;
+ }
+
+ //--------------------------------------//
+
+ else if( rejectedTalkId == TALK_REQUEST_CEASE_WAR )
+ {
+ rc = talkNation->military_rank_rating() >
+ military_rank_rating() + (100-pref_peacefulness)/2;
+ }
+
+ //--------------------------------------//
+
+ if( rc )
+ {
+ //------ give tribute --------//
+
+ talk_res.ai_send_talk_msg(talkNationRecno, nation_recno, talkId, tributeAmount);
+
+ nationRelation->last_talk_reject_date_array[talkId-1] = info.game_date;
+
+ //------ request again after giving tribute ----//
+
+ nationRelation->last_talk_reject_date_array[rejectedTalkId-1] = 0; // reset the rejected talk id.
+
+ talk_res.ai_send_talk_msg(talkNationRecno, nation_recno, rejectedTalkId, rejectedMsg->talk_para1, rejectedMsg->talk_para2 );
+ }
+
+ return rc;
+}
+//------ End of function Nation::think_give_tribute_aid ------//
+
+
+//----- Begin of function Nation::think_demand_tribute_aid -----//
+//
+// Demand tribute when the nation's economy is good and its
+// military is weak.
+//
+int Nation::think_demand_tribute_aid()
+{
+ if( info.game_date < info.game_start_date + 180 + nation_recno*50 ) // don't ask for tribute too soon, as in the beginning, the ranking are all the same for all nations
+ return 0;
+
+ //--------------------------------------//
+
+ Nation* nationPtr;
+ int totalNation=nation_array.size();
+ int nationRecno=m.random(totalNation)+1;
+ int curRating, requestRating;
+ int talkId;
+ int ourMilitary = military_rank_rating();
+ int ourEconomy = economic_rank_rating();
+
+ for( int i=totalNation ; i>0 ; i-- )
+ {
+ if( ++nationRecno > totalNation )
+ nationRecno = 1;
+
+ if( nation_array.is_deleted(nationRecno) || nationRecno==nation_recno )
+ continue;
+
+ nationPtr = nation_array[nationRecno];
+
+ //-- only demand tribute from non-friendly nations --//
+
+ if( get_relation(nationRecno)->status <= NATION_NEUTRAL )
+ talkId = TALK_DEMAND_TRIBUTE;
+ else
+ talkId = TALK_DEMAND_AID;
+
+ //-----------------------------------------------//
+
+ float fixedExpense = fixed_expense_365days();
+
+ if( talkId == TALK_DEMAND_TRIBUTE )
+ {
+ if( !should_diplomacy_retry(talkId, nationRecno) )
+ continue;
+
+ curRating = ourMilitary - nationPtr->military_rank_rating();
+
+ if( curRating < 0 )
+ continue;
+
+ //----------------------------------------------//
+ //
+ // Some nation will actually consider the ability
+ // of the target nation to pay tribute, so nation
+ // will not and just ask anyway.
+ //
+ //----------------------------------------------//
+
+ if( pref_economic_development > 50 )
+ {
+ int addRating = nationPtr->economic_rank_rating()-ourEconomy;
+
+ if( addRating > 0 )
+ curRating += addRating;
+ }
+
+ requestRating = 20 + trade_rating(nationRecno)/2 +
+ (100-pref_peacefulness)/3;
+
+ if( cash < fixedExpense && fixedExpense != 0 )
+ requestRating -= int( (float) requestRating * cash / fixedExpense);
+
+ }
+ else
+ {
+ if( cash >= fixedExpense )
+ continue;
+
+ if( cash > fixedExpense * (50+pref_cash_reserve) / 300 && // if the nation is runing short of cash, don't wait a while until next retry, retry immediately
+ !should_diplomacy_retry(talkId, nationRecno) )
+ {
+ continue;
+ }
+
+ //----- only ask for aid when the nation is short of cash ----//
+
+ curRating = (ourMilitary - nationPtr->military_rank_rating())/2 +
+ ( nationPtr->economic_rank_rating()-ourEconomy );
+
+ requestRating = 20 + 50 * (int)(cash / fixedExpense);
+ }
+
+ //----- if this is a human player's nation -----//
+
+ if( !nationPtr->is_ai() )
+ {
+ switch( config.ai_aggressiveness )
+ {
+ case OPTION_LOW:
+ requestRating += 40; // don't go against the player too easily
+ break;
+
+ case OPTION_HIGH:
+ requestRating -= 20;
+ break;
+
+ case OPTION_VERY_HIGH:
+ requestRating -= 40;
+ break;
+ }
+
+ //--- if the nation has plenty of cash, demand from it ----//
+
+ if( nationPtr->cash > cash && config.ai_aggressiveness >= OPTION_HIGH )
+ {
+ requestRating -= (int) (nationPtr->cash - cash)/500;
+ }
+ }
+
+ //--------------------------------------//
+
+ if( curRating > requestRating )
+ {
+ int tributeAmount;
+
+ if( curRating - requestRating > 120 )
+ tributeAmount = 4000;
+
+ else if( curRating - requestRating > 80 )
+ tributeAmount = 3000;
+
+ else if( curRating - requestRating > 40 )
+ tributeAmount = 2000;
+
+ else if( curRating - requestRating > 20 )
+ tributeAmount = 1000;
+
+ else
+ tributeAmount = 500;
+
+ talk_res.ai_send_talk_msg(nationRecno, nation_recno, talkId, tributeAmount);
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_demand_tribute_aid ------//
+
+
+//----- Begin of function Nation::think_demand_tech -----//
+//
+int Nation::think_demand_tech()
+{
+ if( m.random(10) > 0 ) // only 1/10 chance of calling this function
+ return 0;
+
+ Nation* nationPtr;
+ int totalNation=nation_array.size();
+ int nationRecno=m.random(totalNation)+1;
+
+ for( int i=totalNation ; i>0 ; i-- )
+ {
+ if( ++nationRecno > totalNation )
+ nationRecno = 1;
+
+ if( nation_array.is_deleted(nationRecno) || nationRecno==nation_recno )
+ continue;
+
+ nationPtr = nation_array[nationRecno];
+
+ if( nationPtr->total_tech_level() == 0 )
+ continue;
+
+ if( !should_diplomacy_retry(TALK_DEMAND_TECH, nationRecno) )
+ continue;
+
+ //--- don't request from hostile or tense nations -----//
+
+ if( get_relation(nationRecno)->status < NATION_NEUTRAL )
+ continue;
+
+ //---- scan which tech that the nation has but we don't have ----//
+
+ int techId;
+ for( techId=1 ; techId<=tech_res.tech_count ; techId++ )
+ {
+ TechInfo *techInfo = tech_res[techId];
+
+ if( techInfo->get_nation_tech_level(nation_recno)==0 &&
+ techInfo->get_nation_tech_level(nationRecno) > 0 )
+ {
+ break;
+ }
+ }
+
+ if( techId > tech_res.tech_count )
+ continue;
+
+ //-------- send the message now ---------//
+
+ talk_res.ai_send_talk_msg(nationRecno, nation_recno, TALK_DEMAND_TECH, techId);
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_demand_tech ------//
+
+
+//----- Begin of function Nation::think_give_tech -----//
+//
+int Nation::think_give_tech()
+{
+ return 0;
+}
+//------ End of function Nation::think_give_tech ------//
+
+
+//----- Begin of function Nation::think_request_surrender -----//
+//
+int Nation::think_request_surrender()
+{
+ if( m.random(5) != 0 ) // don't do this too often
+ return 0;
+
+ //---- only do so when we have enough cash ----//
+
+ if( cash < fixed_expense_365days() + 5000 + 10000 * pref_cash_reserve / 100 )
+ return 0;
+
+ if( profit_365days() < 0 && cash < 20000 ) // don't ask if we are losing money and the cash isn't plenty
+ return 0;
+
+ //----- calculate the amount this nation can offer ----//
+
+ int offerAmount = (int)cash - min(5000, (int)fixed_expense_365days());
+
+ static int amtArray[] = { 5000, 7500, 10000, 15000, 20000, 30000, 40000, 50000 };
+
+ int i;
+ for( i=7 ; i>=0 ; i-- )
+ {
+ if( offerAmount >= amtArray[i] )
+ {
+ offerAmount = amtArray[i];
+ break;
+ }
+ }
+
+ if( i<0 )
+ return 0;
+
+ //---------------------------------------------//
+
+ Nation* nationPtr;
+ int ourOverallRankRating = overall_rank_rating();
+ int totalNation = nation_array.size();
+
+ int nationRecno = m.random(totalNation)+1;
+
+ for( i=0 ; i<totalNation ; i++ )
+ {
+ if( ++nationRecno > totalNation )
+ nationRecno = 1;
+
+ if( nation_array.is_deleted(nationRecno) || nation_recno==nationRecno )
+ continue;
+
+ nationPtr = nation_array[nationRecno];
+
+ //--- don't ask for a kingdom that is more powerful to surrender to us ---//
+
+ if( nationPtr->cash > 100 ) // unless it is running short of cash
+ {
+ if( nationPtr->overall_rank_rating() > ourOverallRankRating )
+ continue;
+ }
+
+ //-------------------------------------------//
+
+ if( !should_diplomacy_retry(TALK_REQUEST_SURRENDER, nationRecno) )
+ continue;
+
+ //-------------------------------------------//
+
+ talk_res.ai_send_talk_msg(nationRecno, nation_recno,
+ TALK_REQUEST_SURRENDER, offerAmount/10 ); // divide by 10 to cope with <short>'s upper limit
+
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_request_surrender ------//
+
diff --git a/OAI_ECO.cpp b/OAI_ECO.cpp
new file mode 100644
index 0000000..d6bd93c
--- /dev/null
+++ b/OAI_ECO.cpp
@@ -0,0 +1,131 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_ECO.CPP
+//Description: AI economy functions
+
+#include <ALL.h>
+#include <OUNIT.h>
+#include <OFIRMALL.h>
+#include <ONATION.h>
+
+
+//--------- Begin of function Nation::think_reduce_expense --------//
+
+void Nation::think_reduce_expense()
+{
+ if( true_profit_365days() > 0 || cash > 5000 * pref_cash_reserve / 100 )
+ return;
+
+ //-------- close down firms ---------//
+
+ int curRating, bestRating=0;
+ Firm *firmPtr, *bestFirm=NULL;
+
+ for( int i=firm_array.size() ; i>0 ; i-- )
+ {
+ if( firm_array.is_deleted(i) )
+ continue;
+
+ firmPtr = firm_array[i];
+
+ if( firmPtr->nation_recno != nation_recno )
+ continue;
+
+ if( firmPtr->firm_id == FIRM_WAR_FACTORY ||
+ firmPtr->firm_id == FIRM_RESEARCH )
+ {
+ curRating = 100-(int)firmPtr->productivity;
+ }
+ else
+ continue;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestFirm = firmPtr;
+ }
+ }
+
+ if( bestFirm )
+ bestFirm->ai_del_firm();
+}
+//---------- End of function Nation::think_reduce_expense --------//
+
+
+//----- Begin of function Nation::ai_should_spend -----//
+//
+// This function returns whether this nation should make
+// a new spending.
+//
+// <int> importanceRating - how important is the spending to the
+// nation.
+// [float] spendAmt - if this is not given, then it will
+// consider generally - whether the
+// nation should spend money generally.
+//
+int Nation::ai_should_spend(int importanceRating, float spendAmt)
+{
+ if( cash < spendAmt )
+ return 0;
+
+ float fixedExpense = fixed_expense_365days();
+ float stdCashLevel = max(fixedExpense,2000) * (150+pref_cash_reserve) / 100;
+ float trueProfit = true_profit_365days();
+
+ //----- if we are losing money, don't spend on non-important things -----//
+
+ if( trueProfit < 0 )
+ {
+ if( 400 * (-trueProfit) / fixedExpense > importanceRating )
+ return 0;
+ }
+
+ //--------------------------------------//
+
+ float curCashLevel = 100 * (cash-spendAmt) / (stdCashLevel*2);
+
+ return importanceRating >= (100-curCashLevel);
+}
+//------ End of function Nation::ai_should_spend ------//
+
+
+//----- Begin of function Nation::ai_should_spend_war -----//
+//
+// This function returns whether the nation should spend money
+// or war.
+//
+// <int> enemyMilitaryRating - the military_rank_rating() of the enemy
+// [int] considerCeaseFire - whether this function is called when considering ceasing fire
+// (default:0)
+//
+int Nation::ai_should_spend_war(int enemyMilitaryRating, int considerCeaseFire)
+{
+ int importanceRating = 30 + pref_military_development/5; // 30 to 50
+
+ importanceRating += military_rank_rating() - enemyMilitaryRating*2;
+
+ if( considerCeaseFire ) // only when we are very powerful, we will start a battle. So won't cease fire too soon after declaring war
+ importanceRating += 20; // less eary to return 0, for cease fire
+
+ return ai_should_spend(importanceRating);
+}
+//------ End of function Nation::ai_should_spend_war ------//
+
diff --git a/OAI_GRAN.cpp b/OAI_GRAN.cpp
new file mode 100644
index 0000000..64e974e
--- /dev/null
+++ b/OAI_GRAN.cpp
@@ -0,0 +1,1023 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_GRAN.CPP
+//Description: AI grand plans
+
+#include <OCONFIG.h>
+#include <OTALKRES.h>
+#include <ORACERES.h>
+#include <OREGIONS.h>
+#include <OF_CAMP.h>
+#include <ONATION.h>
+
+
+//----- Begin of function Nation::think_grand_plan -----//
+//
+void Nation::think_grand_plan()
+{
+ think_deal_with_all_enemy();
+
+ think_against_mine_monopoly();
+
+ think_ally_against_big_enemy();
+}
+//------ End of function Nation::think_grand_plan ------//
+
+
+//----- Begin of function Nation::total_alliance_military -----//
+//
+// Return the total power of this nation and its friendly/allied
+// nation.
+//
+int Nation::total_alliance_military()
+{
+ int totalPower = military_rank_rating();
+
+ for( int i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ switch( get_relation_status(i) )
+ {
+ case NATION_ALLIANCE:
+ totalPower += nation_array[i]->military_rank_rating() * 3 / 4; // 75%
+ break;
+/*
+ case NATION_FRIENDLY:
+ totalPower += nation_array[i]->military_rank_rating() / 2; //50%
+ break;
+*/
+ }
+ }
+
+ return totalPower;
+}
+//------ End of function Nation::total_alliance_military ------//
+
+
+//----- Begin of function Nation::total_enemy_military -----//
+//
+// Return the total power of this nation's enemies and potential
+// enemies.
+//
+int Nation::total_enemy_military()
+{
+ Nation *nationPtr;
+ int totalPower=0, relationStatus;
+// int relationStatus2, enemyRelationStatus;
+
+ for( int i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationPtr = nation_array[i];
+ relationStatus = get_relation_status(i);
+
+ if( relationStatus == NATION_HOSTILE )
+ {
+ totalPower += nationPtr->military_rank_rating();
+ }
+/*
+ else
+ {
+ //--- check this nation's status with our enemies ---//
+
+ enemyRelationStatus = 0;
+
+ for( int j=nation_array.size() ; j>0 ; j-- )
+ {
+ if( nation_array.is_deleted(j) || j==nation_recno )
+ continue;
+
+ if( get_relation_status(j) != NATION_HOSTILE ) // only if this is one of our enemies.
+ continue;
+
+ //--- check if it is allied or friendly to any of our enemies ---//
+
+ relationStatus2 = nationPtr->get_relation_status(j);
+
+ if( relationStatus2 > enemyRelationStatus ) // Friendly will replace none, Alliance will replace Friendly
+ enemyRelationStatus = relationStatus2;
+ }
+
+ if( enemyRelationStatus == NATION_ALLIANCE )
+ totalPower += nationPtr->military_rank_rating() * 3 / 4; // 75%
+
+ else if( enemyRelationStatus == NATION_FRIENDLY )
+ totalPower += nationPtr->military_rank_rating() / 2; // 50%
+ }
+*/
+ }
+
+ return totalPower;
+}
+//------ End of function Nation::total_enemy_military ------//
+
+
+//----- Begin of function Nation::total_enemy_count -----//
+//
+int Nation::total_enemy_count()
+{
+ int totalEnemy=0;
+
+ for( int i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ if( get_relation_status(i) == NATION_HOSTILE )
+ totalEnemy++;
+ }
+
+ return totalEnemy;
+}
+//------ End of function Nation::total_enemy_count ------//
+
+
+//----- Begin of function Nation::think_deal_with_all_enemy -----//
+//
+// Think about dealing with the enemy. The following are the
+// actions a nation can take to deal with its enemies.
+//
+// >ask our allies to attack the enemy.
+//
+// >try to break the enemy's existing alliance/friendly treaty with other
+// nations - to reduce its alliance strength.
+//
+// >convert enemy's allies to ours.
+//
+// >ask other nations to impose trade embargos on the enemy.
+//
+void Nation::think_deal_with_all_enemy()
+{
+ Nation* nationPtr;
+
+ int ourMilitary = military_rank_rating();
+ int enemyCount = total_enemy_count();
+
+ for( int i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || nation_recno == i )
+ continue;
+
+ if( get_relation_status(i) != NATION_HOSTILE )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ //------- think about eliminating the enemy ------//
+
+ int rc = 0;
+
+ if( nationPtr->total_population==0 ) // the enemy has no towns left
+ rc = 1;
+
+ if( enemyCount==1 &&
+ ourMilitary > 100-pref_military_courage/5 ) // 80 to 100
+ {
+ int enemyMilitary = nationPtr->military_rank_rating();
+
+ if( enemyMilitary < 20 && ai_should_spend_war(enemyMilitary) )
+ rc = 1;
+ }
+
+ if( rc )
+ {
+ if( think_eliminate_enemy_firm(i) )
+ continue;
+
+ if( think_eliminate_enemy_town(i) )
+ continue;
+
+ think_eliminate_enemy_unit(i);
+ continue;
+ }
+
+ //----- think about dealing with the enemy with diplomacy -----//
+
+ think_deal_with_one_enemy(i);
+ }
+}
+//------ End of function Nation::think_deal_with_all_enemy ------//
+
+
+//----- Begin of function Nation::think_deal_with_one_enemy -----//
+//
+void Nation::think_deal_with_one_enemy(int enemyNationRecno)
+{
+ Nation* nationPtr;
+ NationRelation* nationRelation;
+
+ for( int i=1 ; i<=nation_array.size() ; i++ )
+ {
+ if( nation_array.is_deleted(i) || nation_recno == i )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ nationRelation = nationPtr->get_relation(nation_recno);
+
+ //--- if this nation is already allied to us, request it to declare war with the enemy ---//
+
+ if( nationRelation->status == NATION_ALLIANCE &&
+ nationPtr->get_relation_status(enemyNationRecno) != NATION_HOSTILE )
+ {
+ if( should_diplomacy_retry(TALK_REQUEST_DECLARE_WAR, i) )
+ {
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_REQUEST_DECLARE_WAR, enemyNationRecno);
+ continue;
+ }
+ }
+
+ //---- if this nation is not friendly or alliance to our enemy ----//
+
+ if( nationPtr->get_relation_status(enemyNationRecno) < NATION_FRIENDLY )
+ {
+ //--- and this nation is neutral or friendly with us ---//
+
+ if( nationRelation->status >= NATION_NEUTRAL &&
+ nationPtr->get_relation(enemyNationRecno)->trade_treaty )
+ {
+ //--- ask it to join a trade embargo on the enemy ---//
+
+ if( should_diplomacy_retry(TALK_REQUEST_TRADE_EMBARGO, i) )
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_REQUEST_TRADE_EMBARGO, enemyNationRecno );
+ }
+ }
+ else //---- if this nation is friendly or alliance to our enemy ----//
+ {
+ //----- and this nation is not at war with us -----//
+
+ if( nationRelation->status != NATION_HOSTILE )
+ {
+ //--- if we do not have trade treaty with this nation, propose one ---//
+
+ if( !nationRelation->trade_treaty )
+ {
+ if( should_diplomacy_retry(TALK_PROPOSE_TRADE_TREATY, i) )
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_PROPOSE_TRADE_TREATY );
+ }
+ else //--- if we already have a trade treaty with this nation ---//
+ {
+ // if this nation is already friendly to us, propose an alliance treaty now --//
+
+ if( nationRelation->status == NATION_FRIENDLY )
+ {
+ if( should_diplomacy_retry(TALK_PROPOSE_ALLIANCE_TREATY, i) )
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_PROPOSE_ALLIANCE_TREATY );
+ }
+
+ //-- if the nation has significiant trade with us, propose a friendly treaty now --//
+
+ else if( nationPtr->trade_rating(nation_recno) > 10 ||
+ nationPtr->ai_trade_with_rating(nation_recno) >= 50 ) // or if the product complement each other very well
+ {
+ if( should_diplomacy_retry(TALK_PROPOSE_FRIENDLY_TREATY, i) )
+ talk_res.ai_send_talk_msg(i, nation_recno, TALK_PROPOSE_FRIENDLY_TREATY );
+ }
+ }
+ }
+ }
+ }
+}
+//------ End of function Nation::think_deal_with_one_enemy ------//
+
+
+//----- Begin of function Nation::think_surrender -----//
+//
+int Nation::think_surrender()
+{
+ //--- don't surrender if the nation still has a town ---//
+
+ int rc=0;
+
+ if( total_population == 0 )
+ rc = 1;
+
+ if( cash <= 0 && income_365days()==0 )
+ rc = 1;
+
+ if( !rc )
+ return 0;
+
+ //---- see if there is any nation worth getting our surrender ---//
+
+ Nation* nationPtr;
+ int curRating, bestRating=0, bestNationRecno=0;
+
+ if( !king_unit_recno ) // if there is no successor to the king, the nation will tend more to surrender
+ bestRating = -100;
+
+ for( int i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ if( nationPtr->cash <= 300 ) // don't surrender to an ecnomically handicapped nation
+ continue;
+
+ curRating = ai_surrender_to_rating(i);
+
+ //--- if the nation will tend to surrender if there is only a small number of units left ---//
+
+ curRating += 50 - total_unit_count*5;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestNationRecno = i;
+ }
+ }
+
+ //---------------------------------------//
+
+ if( bestNationRecno )
+ {
+ surrender(bestNationRecno);
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_surrender ------//
+
+
+//----- Begin of function Nation::think_unite_against_big_enemy -----//
+//
+int Nation::think_unite_against_big_enemy()
+{
+ if( info.game_date - info.game_start_date <
+ 365 * 3 * (100+pref_military_development) / 100 ) // only do this after 3 to 6 years into the game
+ {
+ return 0;
+ }
+
+ //-----------------------------------------------//
+
+ if( config.ai_aggressiveness < OPTION_HIGH )
+ return 0;
+
+ if( config.ai_aggressiveness == OPTION_HIGH )
+ {
+ if( m.random(10)!=0 )
+ return 0;
+ }
+ else // OPTION_VERY_HIGH
+ {
+ if( m.random(5)!=0 )
+ return 0;
+ }
+
+ //---------------------------------------//
+
+ int enemyNationRecno = nation_array.max_overall_nation_recno;
+
+ if( !enemyNationRecno )
+ return 0;
+
+ Nation* enemyNation = nation_array[enemyNationRecno];
+
+ //----- only against human players -----//
+
+ if( enemyNation->is_ai() )
+ return 0;
+
+ //---- find the overall rank rating of the second most powerful computer kingdom ---//
+
+ Nation* nationPtr;
+ int secondBestOverall=0, secondBestNationRecno=0;
+
+ for( int i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) || i==enemyNationRecno )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ if( !nationPtr->is_ai() ) // don't count human players
+ continue;
+
+ if( nationPtr->overall_rank_rating() > secondBestOverall )
+ {
+ secondBestOverall = nationPtr->overall_rank_rating();
+ secondBestNationRecno = i;
+ }
+ }
+
+ if( !secondBestNationRecno || secondBestNationRecno==nation_recno )
+ return 0;
+
+ //------- don't surrender to hostile nation -------//
+
+ if( get_relation_status(secondBestNationRecno) < NATION_NEUTRAL )
+ return 0;
+
+ //--- if all AI kingdoms are way behind the human players, unite to against the human player ---//
+
+ int compareRating;
+
+ if( config.ai_aggressiveness == OPTION_HIGH )
+ compareRating = 50;
+ else // OPTION_VERY_AGGRESSIVE
+ compareRating = 80;
+
+ if( secondBestOverall < compareRating &&
+ secondBestNationRecno != nation_recno )
+ {
+ surrender(secondBestNationRecno);
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_unite_against_big_enemy ------//
+
+
+//----- Begin of function Nation::ai_surrender_to_rating -----//
+//
+// return a rating on how much the nation will tend to surrender
+// to the specific nation.
+//
+int Nation::ai_surrender_to_rating(int nationRecno)
+{
+ Nation* nationPtr = nation_array[nationRecno];
+ NationRelation* nationRelation = get_relation(nationRecno);
+
+ //--- higher tendency to surrender to a powerful nation ---//
+
+ int curRating = nationPtr->overall_rank_rating() - overall_rank_rating();
+
+ curRating += (nationRelation->ai_relation_level-40);
+
+ curRating += (int) nationRelation->good_relation_duration_rating*3;
+
+ curRating += (int) nationPtr->reputation/2;
+
+ //------ shouldn't surrender to an enemy --------//
+
+ if( nationRelation->status == NATION_HOSTILE )
+ curRating -= 100;
+
+ //--- if the race of the kings are the same, the chance is higher ---//
+
+ if( race_res.is_same_race( nationPtr->race_id, race_id ) )
+ curRating += 20;
+
+ return curRating;
+}
+//------ End of function Nation::ai_surrender_to_rating ------//
+
+
+//----- Begin of function Nation::think_eliminate_enemy_town -----//
+//
+// This function is called to eliminate remaining enemy firms
+// when all enemy towns have been destroyed.
+//
+int Nation::think_eliminate_enemy_town(int enemyNationRecno)
+{
+ //---- look for enemy firms to attack ----//
+
+ int hasWar;
+ Town *townPtr;
+
+ for( int i=town_array.size() ; i>0 ; i-- )
+ {
+ if( town_array.is_deleted(i) )
+ continue;
+
+ townPtr = town_array[i];
+
+ if( townPtr->nation_recno != enemyNationRecno )
+ continue;
+
+ //--- only attack if we have any base town in the enemy town's region ---//
+
+ if( base_town_count_in_region(townPtr->region_id)==0 )
+ continue;
+
+ //----- take into account of the mobile units around this town -----//
+
+ int mobileCombatLevel = mobile_defense_combat_level(townPtr->center_x, townPtr->center_y, townPtr->nation_recno, 1, hasWar);
+
+ if( mobileCombatLevel == -1 ) // do not attack this town because a battle is already going on
+ continue;
+
+ //---- calculate the combat level of this target town ----//
+
+ int townCombatLevel = townPtr->protection_available();
+
+ return ai_attack_target(townPtr->loc_x1, townPtr->loc_y1, mobileCombatLevel + townCombatLevel);
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_eliminate_enemy_town ------//
+
+
+//----- Begin of function Nation::think_eliminate_enemy_firm -----//
+//
+// This function is called to eliminate remaining enemy firms
+// when all enemy towns have been destroyed.
+//
+int Nation::think_eliminate_enemy_firm(int enemyNationRecno)
+{
+ //---- look for enemy firms to attack ----//
+
+ int hasWar;
+ Firm *firmPtr;
+
+ for( int i=firm_array.size() ; i>0 ; i-- )
+ {
+ if( firm_array.is_deleted(i) )
+ continue;
+
+ firmPtr = firm_array[i];
+
+ if( firmPtr->nation_recno != enemyNationRecno )
+ continue;
+
+ //--- only attack if we have any base town in the enemy firm's region ---//
+
+ if( base_town_count_in_region(firmPtr->region_id)==0 )
+ continue;
+
+ //----- take into account of the mobile units around this town -----//
+
+ int mobileCombatLevel = mobile_defense_combat_level(firmPtr->center_x, firmPtr->center_y, firmPtr->nation_recno, 1, hasWar);
+
+ if( mobileCombatLevel == -1 ) // do not attack this town because a battle is already going on
+ continue;
+
+ //---- calculate the combat level of this target firm ----//
+
+ int firmCombatLevel;
+
+ if( firmPtr->firm_id == FIRM_CAMP ) // other civilian firms
+ firmCombatLevel = ((FirmCamp*)firmPtr)->total_combat_level();
+ else
+ firmCombatLevel = firmPtr->worker_count * 10; // civilian firms have very low combat level
+
+ return ai_attack_target(firmPtr->loc_x1, firmPtr->loc_y1, mobileCombatLevel + firmCombatLevel);
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_eliminate_enemy_firm ------//
+
+
+//----- Begin of function Nation::think_eliminate_enemy_unit -----//
+//
+// This function is called to eliminate remaining enemy firms
+// when all enemy towns have been destroyed.
+//
+int Nation::think_eliminate_enemy_unit(int enemyNationRecno)
+{
+ Unit *unitPtr;
+ int hasWar;
+
+ for( int i=unit_array.size() ; i>0 ; i-- )
+ {
+ if( unit_array.is_deleted(i) )
+ continue;
+
+ unitPtr = unit_array[i];
+
+ if( unitPtr->nation_recno != enemyNationRecno )
+ continue;
+
+ if( !unitPtr->is_visible() || unitPtr->mobile_type != UNIT_LAND ) // only deal with land units now
+ continue;
+
+ //--- only attack if we have any base town in the enemy unit's region ---//
+
+ if( base_town_count_in_region(unitPtr->region_id()) == 0 )
+ continue;
+
+ //----- take into account of the mobile units around this town -----//
+
+ int mobileCombatLevel = mobile_defense_combat_level(unitPtr->next_x_loc(), unitPtr->next_y_loc(), unitPtr->nation_recno, 1, hasWar);
+
+ if( mobileCombatLevel == -1 ) // do not attack this town because a battle is already going on
+ continue;
+
+ return ai_attack_target(unitPtr->next_x_loc(), unitPtr->next_y_loc(), mobileCombatLevel + (int) unitPtr->unit_power());
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_eliminate_enemy_unit ------//
+
+
+//----- Begin of function Nation::think_ally_against_big_enemy -----//
+//
+// Think about allying against a big enemy
+//
+int Nation::think_ally_against_big_enemy()
+{
+ if( info.game_date < info.game_start_date + 365 + nation_recno*70 ) // don't ask for tribute too soon, as in the beginning, the ranking are all the same for all nations
+ return 0;
+
+ //---------------------------------------//
+
+ int enemyNationRecno = nation_array.max_overall_nation_recno;
+
+ if( enemyNationRecno == nation_recno )
+ return 0;
+
+ //-- if AI aggressiveness > high, only deal against the player, but not other kingdoms ---//
+
+ if( config.ai_aggressiveness >= OPTION_HIGH )
+ {
+ if( nation_array[enemyNationRecno]->is_ai() )
+ return 0;
+ }
+
+ //-- if AI aggressiveness is low, don't do this against the human player --//
+
+ else if( config.ai_aggressiveness == OPTION_LOW )
+ {
+ if( !nation_array[enemyNationRecno]->is_ai() )
+ return 0;
+ }
+
+ //--- increase the ai_relation_level towards other nations except the enemy so we can ally against the enemy ---//
+
+ Nation* enemyNation = nation_array[enemyNationRecno];
+ int incRelationLevel = (100-overall_rank_rating())/10;
+
+ int i;
+ for( i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) )
+ continue;
+
+ if( i==nation_recno || i==enemyNationRecno )
+ continue;
+
+ int thisIncLevel = incRelationLevel * (100-get_relation(i)->ai_relation_level) / 100;
+
+ change_ai_relation_level( i, thisIncLevel );
+ }
+
+ //---- don't have all nations doing it the same time ----//
+
+ if( m.random(nation_array.ai_nation_count)==0 )
+ return 0;
+
+ //---- if the trade rating is high, stay war-less with it ----//
+
+ if( trade_rating(enemyNationRecno) +
+ ai_trade_with_rating(enemyNationRecno) > 100 - pref_trading_tendency/3 )
+ {
+ return 0;
+ }
+
+ //---- if the nation relation level is still high, then request aid/tribute ----//
+
+ NationRelation* nationRelation = get_relation(enemyNationRecno);
+
+ if( nationRelation->ai_relation_level > 30 )
+ {
+ int talkId;
+
+ if( nationRelation->status >= NATION_FRIENDLY )
+ talkId = TALK_DEMAND_AID;
+ else
+ talkId = TALK_DEMAND_TRIBUTE;
+
+ if( should_diplomacy_retry(talkId, enemyNationRecno) )
+ {
+ static short aidAmountArray[] = { 500, 1000, 2000 };
+
+ int aidAmount = aidAmountArray[m.random(3)];
+
+ talk_res.ai_send_talk_msg(enemyNationRecno, nation_recno, talkId, aidAmount);
+ }
+
+ return 0;
+ }
+
+ //-------------------------------------//
+
+ Nation* nationPtr;
+
+ NationRelation *ourNationRelation, *enemyNationRelation;
+
+ for( i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) )
+ continue;
+
+ if( i==nation_recno || i==enemyNationRecno )
+ continue;
+
+ nationPtr = nation_array[i];
+
+ ourNationRelation = get_relation(i);
+ enemyNationRelation = enemyNation->get_relation(i);
+
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_ally_against_big_enemy ------//
+
+
+//----- Begin of function Nation::ai_should_attack_friendly -----//
+//
+// This function returns whether this nation should attack
+// the given friendly nation.
+//
+// <int> friendlyNationRecno - the nation recno of the friendly nation.
+// <int> attackTemptation - a rating from 0 to 100 indicating
+// temptation of attacking the target.
+//
+int Nation::ai_should_attack_friendly(int friendlyNationRecno, int attackTemptation)
+{
+ Nation *friendlyNation = nation_array[friendlyNationRecno];
+ NationRelation *nationRelation = get_relation(friendlyNationRecno);
+
+ //--- don't change terminate treaty too soon ---//
+
+ if( info.game_date < nationRelation->last_change_status_date+60+pref_honesty/2 ) // only after 60 to 110 days
+ return 0;
+
+ //------------------------------------------------//
+
+ int resistanceRating = friendlyNation->military_rank_rating()
+ - military_rank_rating();
+
+ resistanceRating += nationRelation->ai_relation_level - 50;
+
+ resistanceRating += trade_rating(friendlyNationRecno);
+
+ return attackTemptation > resistanceRating;
+}
+//------ End of function Nation::ai_should_attack_friendly ------//
+
+
+//----- Begin of function Nation::ai_end_treaty -----//
+//
+// Terminate the treaty with the given nation.
+//
+// <int> nationRecno - the nation to terminate treaty with.
+//
+void Nation::ai_end_treaty(int nationRecno)
+{
+ NationRelation *nationRelation = get_relation(nationRecno);
+
+ err_when( nationRelation->status < NATION_FRIENDLY );
+
+ if( nationRelation->status == NATION_FRIENDLY )
+ {
+ talk_res.ai_send_talk_msg(nationRecno, nation_recno, TALK_END_FRIENDLY_TREATY, 0, 0, 1); // 1-force send
+ }
+ else if( nationRelation->status == NATION_ALLIANCE )
+ {
+ talk_res.ai_send_talk_msg(nationRecno, nation_recno, TALK_END_ALLIANCE_TREATY, 0, 0, 1);
+ }
+
+ err_when( nationRelation->status >= NATION_FRIENDLY ); // when the status is still friendly or alliance
+}
+//------ End of function Nation::ai_end_treaty ------//
+
+
+//----- Begin of function Nation::ai_has_enough_food -----//
+//
+// return whether this nation has enough food or not.
+//
+int Nation::ai_has_enough_food()
+{
+ return food > 2000 + 2000 * pref_food_reserve / 100
+ || yearly_food_change() > 0;
+}
+//------ End of function Nation::ai_has_enough_food ------//
+
+
+//----- Begin of function Nation::think_attack_enemy_firm -----//
+//
+// Think about attacking a specific type of firm of a specific enemy.
+//
+int Nation::think_attack_enemy_firm(int enemyNationRecno, int firmId)
+{
+ if( !largest_town_recno )
+ return 0;
+
+ Town* ourLargestTown = town_array[largest_town_recno];
+ Nation *nationPtr = nation_array[enemyNationRecno];
+ Firm *firmPtr, *targetFirm=NULL;
+ int curRating, bestRating=0, targetCombatLevel, hasWar;
+
+ for( int i=firm_array.size() ; i>0 ; i-- )
+ {
+ if( firm_array.is_deleted(i) )
+ continue;
+
+ firmPtr = firm_array[i];
+
+ if( firmPtr->firm_id != firmId ||
+ firmPtr->nation_recno != enemyNationRecno )
+ {
+ continue;
+ }
+
+ int combatLevel = enemy_firm_combat_level(firmPtr, 1, hasWar);
+
+ if( combatLevel==0 ) // no protection with this mine
+ {
+ targetFirm = firmPtr;
+ targetCombatLevel = combatLevel;
+ break;
+ }
+
+ curRating = world.distance_rating( firmPtr->center_x, firmPtr->center_y,
+ ourLargestTown->center_x, ourLargestTown->center_y );
+
+ curRating += 1000 - combatLevel/5;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ targetFirm = firmPtr;
+ targetCombatLevel = combatLevel;
+ }
+ }
+
+ if( !targetFirm )
+ return 0;
+
+ //---------------------------------------------//
+
+ int useAllCamp=1;
+
+ return ai_attack_target( targetFirm->loc_x1, targetFirm->loc_y1,
+ targetCombatLevel, 0, 0, 0, 0, useAllCamp );
+}
+//------ End of function Nation::think_attack_enemy_firm ------//
+
+
+//----- Begin of function Nation::think_against_mine_monopoly -----//
+//
+int Nation::think_against_mine_monopoly()
+{
+ //-- only think this after the game has been running for at least one year --//
+
+ if( config.ai_aggressiveness < OPTION_HIGH ) // only attack if aggressiveness >= high
+ return 0;
+
+ if( info.game_date - info.game_start_date > 365 )
+ return 0;
+
+ if( profit_365days() > 0 ) // if we are making a profit, don't attack
+ return 0;
+
+ //-- for high aggressiveness, it will check cash before attack, for very high aggressiveness, it won't check cash before attack ---//
+
+ if( config.ai_aggressiveness < OPTION_VERY_HIGH ) // only attack if aggressiveness >= high
+ {
+ if( cash > 2000 + 1000 * pref_cash_reserve / 100 ) // only attack if we run short of cash
+ return 0;
+ }
+
+ //--------------------------------------------------------//
+
+ if( !largest_town_recno )
+ return 0;
+
+ //--------------------------------------------------//
+
+ int baseRegionId = town_array[largest_town_recno]->region_id;
+
+ // ##### patch begin Gilbert 16/3 ########//
+//#ifdef AMPLUS
+ // no region stat (region is too small), don't care
+ if( !region_array[baseRegionId]->region_stat_id )
+ return 0;
+//#endif
+ // ##### end begin Gilbert 16/3 ########//
+
+ RegionStat* regionStat = region_array.get_region_stat(baseRegionId);
+
+ //---- if we already have a mine in this region ----//
+
+ if( regionStat->mine_nation_count_array[nation_recno-1] > 0 )
+ return 0;
+
+ //----- if there is no mine in this region -----//
+
+ if( regionStat->raw_count == 0 )
+ return 0;
+
+ //----- if enemies have occupied all mines -----//
+
+ int mineCount, totalMineCount=0;
+ int curRating, bestRating=0, targetNationRecno=0;
+
+ int i;
+ for( i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) )
+ continue;
+
+ //------ only deal with human players ------//
+
+ if( nation_array[i]->is_ai() || i==nation_recno )
+ continue;
+
+ //------------------------------------------//
+
+ mineCount = regionStat->mine_nation_count_array[i-1];
+ totalMineCount += mineCount;
+
+ curRating = mineCount * 100
+ - get_relation(i)->ai_relation_level
+ - trade_rating(i);
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ targetNationRecno = i;
+ }
+ }
+
+ if( !targetNationRecno )
+ return 0;
+
+ //--- if the relationship with this nation is still good, don't attack yet, ask for aid first ---//
+
+ NationRelation* nationRelation = get_relation(targetNationRecno);
+
+ if( nationRelation->ai_relation_level > 30 )
+ {
+ int talkId;
+
+ if( nationRelation->status >= NATION_FRIENDLY )
+ talkId = TALK_DEMAND_AID;
+ else
+ talkId = TALK_DEMAND_TRIBUTE;
+
+ if( should_diplomacy_retry(talkId, targetNationRecno) )
+ {
+ static short aidAmountArray[] = { 500, 1000, 2000 };
+
+ int aidAmount = aidAmountArray[m.random(3)];
+
+ talk_res.ai_send_talk_msg(targetNationRecno, nation_recno, talkId, aidAmount);
+ }
+
+ return 0;
+ }
+
+ //------- attack one of the target enemy's mines -------//
+
+ Firm* firmPtr;
+
+ for( i=firm_array.size() ; i>0 ; i-- )
+ {
+ if( firm_array.is_deleted(i) )
+ continue;
+
+ firmPtr = firm_array[i];
+
+ if( firmPtr->firm_id != FIRM_MINE ||
+ firmPtr->nation_recno != targetNationRecno ||
+ firmPtr->region_id != baseRegionId )
+ {
+ continue;
+ }
+
+ //--------------------------------------------//
+
+ int hasWar;
+ int targetCombatLevel = enemy_firm_combat_level(firmPtr, 1, hasWar);
+
+ return ai_attack_target( firmPtr->loc_x1, firmPtr->loc_y1,
+ targetCombatLevel, 0, 0, 0, 0, 1 ); // 1-use all camps
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_against_mine_monopoly ------//
+
diff --git a/OAI_INFO.cpp b/OAI_INFO.cpp
new file mode 100644
index 0000000..69a93fc
--- /dev/null
+++ b/OAI_INFO.cpp
@@ -0,0 +1,441 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+//Filename : OAI_INFO.CPP
+//Description : AI - A.I. info structure
+
+#include <OSYS.h>
+#include <OTOWN.h>
+#include <OF_MARK.h>
+#include <OU_CARA.h>
+#include <ONATION.h>
+
+
+//--------- Begin of function Nation::update_ai_firm_array --------//
+//
+// <int> firmId - determine which firm array to be returned
+//
+// <int> actionType: 1 - add a record to the array
+// 0 - no addition or deletion, just return
+// -1 - del a record from the array
+//
+// <int> actionRecno : the recno to be deleted, if actionType is -1.
+// the recno to be added, if actionType is 1.
+// <int> arrayCount: for return the count of the AI info array
+//
+short* Nation::update_ai_firm_array(int firmId, int actionType, int actionRecno, int& arrayCount)
+{
+ short* rc;
+
+ switch(firmId)
+ {
+ case FIRM_BASE:
+ rc = update_ai_array(ai_base_count, ai_base_size, &ai_base_array,
+ AI_BASE_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_base_count;
+ break;
+
+ case FIRM_CAMP:
+ rc = update_ai_array(ai_camp_count, ai_camp_size, &ai_camp_array,
+ AI_CAMP_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_camp_count;
+ break;
+
+ case FIRM_FACTORY:
+ rc = update_ai_array(ai_factory_count, ai_factory_size, &ai_factory_array,
+ AI_FACTORY_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_factory_count;
+ break;
+
+ case FIRM_MARKET:
+ rc = update_ai_array(ai_market_count, ai_market_size, &ai_market_array,
+ AI_MARKET_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_market_count;
+ break;
+
+ case FIRM_INN:
+ rc = update_ai_array(ai_inn_count, ai_inn_size, &ai_inn_array,
+ AI_INN_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_inn_count;
+ break;
+
+ case FIRM_MINE:
+ rc = update_ai_array(ai_mine_count, ai_mine_size, &ai_mine_array,
+ AI_MINE_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_mine_count;
+ break;
+
+ case FIRM_RESEARCH:
+ rc = update_ai_array(ai_research_count, ai_research_size, &ai_research_array,
+ AI_RESEARCH_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_research_count;
+ break;
+
+ case FIRM_WAR_FACTORY:
+ rc = update_ai_array(ai_war_count, ai_war_size, &ai_war_array,
+ AI_WAR_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_war_count;
+ break;
+
+ case FIRM_HARBOR:
+ rc = update_ai_array(ai_harbor_count, ai_harbor_size, &ai_harbor_array,
+ AI_HARBOR_INC_SIZE, actionType, actionRecno);
+ arrayCount = ai_harbor_count;
+ break;
+
+ default:
+ err_here();
+ return 0;
+ }
+
+ return rc;
+}
+//---------- End of function Nation::update_ai_firm_array --------//
+
+
+//--------- Begin of function Nation::update_ai_array --------//
+//
+// <short&> aiInfoCount - the count of the AI info array.
+// <short&> aiInfoSize - the size of the AI info array.
+// <short**> aiInfoArray - poniter to the AI info array.
+// <int> arrayIncSize - the increment size of the array.
+//
+// <int> actionType: 1 - add a record to the array
+// 0 - no addition or deletion, just return
+// -1 - del a record from the array
+//
+// [int] actionRecno : the recno to be deleted, if actionType is -1.
+// the recno to be added, if actionType is 1.
+//
+short* Nation::update_ai_array(short& aiInfoCount, short& aiInfoSize,
+ short** aiInfoArrayPtr, int arrayIncSize, int actionType, int actionRecno)
+{
+ err_when( aiInfoCount<0 );
+ err_when( aiInfoCount > aiInfoSize );
+ err_when( actionType<-1 || actionType>1 );
+
+ short* aiInfoArray = *aiInfoArrayPtr;
+
+ if( actionType == -1 )
+ {
+ short* aiInfoPtr = aiInfoArray;
+
+ for( int i=0 ; i<aiInfoCount ; i++, aiInfoPtr++ )
+ {
+ if( *aiInfoPtr == actionRecno )
+ {
+ if( i+1==aiInfoCount ) // the record to be deleted is the last record
+ {
+ *aiInfoPtr = 0;
+ }
+ else // the record to be deleted is not the last record, somewhere in the array
+ {
+ //---- copy the last record to this slot which has been marked for deletion
+
+ *aiInfoPtr = aiInfoArray[aiInfoCount-1];
+ aiInfoArray[aiInfoCount-1] = 0;
+ }
+
+ aiInfoCount--;
+ return aiInfoArray;
+ }
+ }
+
+ err_here(); // not found, this shouldn't happen.
+ }
+ else if( actionType == 1 )
+ {
+ if( aiInfoCount == aiInfoSize )
+ {
+ #ifdef DEBUG
+ short saveDate1 = aiInfoArray[0]; // for vertification of resizing that old data are kept
+ short saveDate2 = aiInfoArray[aiInfoCount-1];
+ #endif
+
+ aiInfoSize += arrayIncSize;
+
+ *aiInfoArrayPtr = (short*) mem_resize( aiInfoArray, aiInfoSize*sizeof(short) );
+
+ aiInfoArray = *aiInfoArrayPtr;
+
+ err_when( saveDate1 != aiInfoArray[0] ); // for vertification of resizing that old data are kept
+ err_when( saveDate2 != aiInfoArray[aiInfoCount-1] );
+ }
+
+ aiInfoArray[aiInfoCount++] = actionRecno;
+ }
+
+ return aiInfoArray;
+}
+//---------- End of function Nation::update_ai_array --------//
+
+
+//--------- Begin of function Nation::add_firm_info --------//
+//
+void Nation::add_firm_info(char firmId, short firmRecno)
+{
+ err_when( !firmId || !firmRecno );
+
+ int aiFirmCount;
+
+ update_ai_firm_array(firmId, 1, firmRecno, aiFirmCount);
+}
+//---------- End of function Nation::add_firm_info --------//
+
+
+//--------- Begin of function Nation::del_firm_info --------//
+
+void Nation::del_firm_info(char firmId, short firmRecno)
+{
+ err_when( !firmId || !firmRecno );
+
+ int aiFirmCount;
+
+ update_ai_firm_array(firmId, -1, firmRecno, aiFirmCount);
+}
+//---------- End of function Nation::del_firm_info --------//
+
+
+//--------- Begin of function Nation::update_ai_region --------//
+
+void Nation::update_ai_region()
+{
+ Town* townPtr;
+ int regionRecno;
+
+ memset( ai_region_array, 0, sizeof(ai_region_array) );
+ ai_region_count = 0;
+
+ for( int i=0 ; i<ai_town_count ; i++ )
+ {
+ townPtr = town_array[ ai_town_array[i] ];
+
+ //---- see if this region has been included -------//
+
+ regionRecno=0;
+
+ for( int j=0 ; j<ai_region_count ; j++ )
+ {
+ if( ai_region_array[j].region_id == townPtr->region_id )
+ {
+ regionRecno = j+1;
+ break;
+ }
+ }
+
+ if( !regionRecno ) // not included yet
+ {
+ if( ai_region_count == MAX_AI_REGION ) // no space for adding new region
+ continue;
+
+ err_when( ai_region_count > MAX_AI_REGION );
+
+ ai_region_array[ai_region_count++].region_id = townPtr->region_id;
+
+ regionRecno = ai_region_count;
+ }
+
+ //--- increase the town and base_town_count of the nation ---//
+
+ ai_region_array[regionRecno-1].town_count++;
+
+ if( townPtr->is_base_town )
+ ai_region_array[regionRecno-1].base_town_count++;
+ }
+}
+//---------- End of function Nation::update_ai_region --------//
+
+
+//--------- Begin of function Nation::add_town_info --------//
+
+void Nation::add_town_info(short townRecno)
+{
+ update_ai_array(ai_town_count, ai_town_size, &ai_town_array,
+ AI_TOWN_INC_SIZE, 1, townRecno);
+
+ update_ai_region();
+}
+//---------- End of function Nation::add_town_info --------//
+
+
+//--------- Begin of function Nation::del_town_info --------//
+
+void Nation::del_town_info(short townRecno)
+{
+ err_when( ai_base_town_count<0 );
+
+ //--- if this is a base town, decrease the base town counter ---//
+
+ if( town_array[townRecno]->is_base_town )
+ {
+ ai_base_town_count--;
+ err_when( ai_base_town_count<0 );
+ }
+
+ //------- delete the record from ai_town_array ------//
+
+ update_ai_array(ai_town_count, ai_town_size, &ai_town_array,
+ AI_TOWN_INC_SIZE, -1, townRecno);
+
+ update_ai_region();
+}
+//---------- End of function Nation::del_town_info --------//
+
+
+//--------- Begin of function Nation::add_general_info --------//
+
+void Nation::add_general_info(short unitRecno)
+{
+ Unit* unitPtr = unit_array[unitRecno];
+
+ err_when( unitPtr->rank_id != RANK_KING && unitPtr->rank_id != RANK_GENERAL );
+
+ update_ai_array(ai_general_count, ai_general_size,
+ &ai_general_array, AI_GENERAL_INC_SIZE, 1, unitRecno);
+}
+//---------- End of function Nation::add_general_info --------//
+
+
+//--------- Begin of function Nation::del_general_info --------//
+
+void Nation::del_general_info(short unitRecno)
+{
+ Unit* unitPtr = unit_array[unitRecno];
+
+ err_when( unitPtr->rank_id != RANK_KING && unitPtr->rank_id != RANK_GENERAL );
+
+ update_ai_array(ai_general_count, ai_general_size,
+ &ai_general_array, AI_GENERAL_INC_SIZE, -1, unitRecno);
+}
+//---------- End of function Nation::del_general_info --------//
+
+
+//--------- Begin of function Nation::add_caravan_info --------//
+
+void Nation::add_caravan_info(short unitRecno)
+{
+ update_ai_array(ai_caravan_count, ai_caravan_size, &ai_caravan_array,
+ AI_CARAVAN_INC_SIZE, 1, unitRecno);
+}
+//---------- End of function Nation::add_caravan_info --------//
+
+
+//--------- Begin of function Nation::del_caravan_info --------//
+
+void Nation::del_caravan_info(short unitRecno)
+{
+ update_ai_array(ai_caravan_count, ai_caravan_size, &ai_caravan_array,
+ AI_CARAVAN_INC_SIZE, -1, unitRecno);
+}
+//---------- End of function Nation::del_caravan_info --------//
+
+
+//--------- Begin of function Nation::is_caravan_exist --------//
+//
+// Check whether there is an existing caravan travelling along
+// the specific route.
+//
+// <int> firstStop, secondStop - firm recno of the first and second stops.
+// [int] setStopInterval - if this is given, then only caravans
+// that have been set stop within the given
+// days will be counted as existing ones.
+//
+int Nation::is_caravan_exist(int firstStop, int secondStop, int setStopInterval)
+{
+ UnitCaravan* unitCaravan;
+
+ for( int i=0; i<ai_caravan_count; i++ )
+ {
+ unitCaravan = (UnitCaravan*) unit_array[ ai_caravan_array[i] ];
+
+ if( ( unitCaravan->stop_array[0].firm_recno == firstStop &&
+ unitCaravan->stop_array[1].firm_recno == secondStop ) ||
+ ( unitCaravan->stop_array[1].firm_recno == firstStop &&
+ unitCaravan->stop_array[0].firm_recno == secondStop ) )
+ {
+ if( setStopInterval )
+ {
+ if( info.game_date - unitCaravan->last_set_stop_date < setStopInterval )
+ return unitCaravan->sprite_recno;
+ }
+ else
+ return unitCaravan->sprite_recno;
+ }
+ }
+
+ return 0;
+}
+//---------- End of function Nation::is_caravan_exist --------//
+
+
+//--------- Begin of function Nation::add_ship_info --------//
+
+void Nation::add_ship_info(short unitRecno)
+{
+ update_ai_array(ai_ship_count, ai_ship_size, &ai_ship_array,
+ AI_SHIP_INC_SIZE, 1, unitRecno);
+}
+//---------- End of function Nation::add_ship_info --------//
+
+
+//--------- Begin of function Nation::del_ship_info --------//
+
+void Nation::del_ship_info(short unitRecno)
+{
+ update_ai_array(ai_ship_count, ai_ship_size, &ai_ship_array,
+ AI_SHIP_INC_SIZE, -1, unitRecno);
+}
+//---------- End of function Nation::del_ship_info --------//
+
+
+//--------- Begin of function Nation::has_base_town_in_region --------//
+//
+// Return whether this nation has any base town in the given region.
+//
+int Nation::has_base_town_in_region(int regionId)
+{
+ for( int i=0 ; i<ai_region_count ; i++ )
+ {
+ if( ai_region_array[i].region_id == regionId )
+ return ai_region_array[i].base_town_count > 0;
+ }
+
+ return 0;
+}
+//---------- End of function Nation::has_base_town_in_region --------//
+
+
+//--------- Begin of function Nation::get_ai_region --------//
+//
+// Return the AIRegion of the given region id.
+//
+AIRegion* Nation::get_ai_region(int regionId)
+{
+ for( int i=0 ; i<ai_region_count ; i++ )
+ {
+ if( ai_region_array[i].region_id == regionId )
+ return ai_region_array+i;
+ }
+
+ return 0;
+}
+//---------- End of function Nation::get_ai_region --------//
+
diff --git a/OAI_MAIN.cpp b/OAI_MAIN.cpp
new file mode 100644
index 0000000..e03afb7
--- /dev/null
+++ b/OAI_MAIN.cpp
@@ -0,0 +1,671 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_MAIN.CPP
+//Description: AI - main functions
+
+#include <OSYS.h>
+#include <ONATION.h>
+#include <OWORLD.h>
+#include <OGAME.h>
+#include <OSPY.h>
+#include <OCONFIG.h>
+#include <OUNIT.h>
+#include <OFIRM.h>
+#include <OTOWN.h>
+#include <OTALKRES.h>
+#include <OF_MINE.h>
+#include <OINFO.h>
+#include <OLOG.h>
+
+//--------- Begin of function Nation::Nation --------//
+
+Nation::Nation() : action_array( sizeof(ActionNode), 30 )
+{
+ ai_town_array = NULL;
+}
+//---------- End of function Nation::Nation --------//
+
+
+//--------- Begin of function Nation::~Nation --------//
+
+Nation::~Nation()
+{
+ err_when( nation_recno ); // deinit() must be called first before this destructor is called
+}
+//---------- End of function Nation::~Nation --------//
+
+
+//--------- Begin of function Nation::init --------//
+void Nation::init(int nationType, int raceId, int colorSchemeId, DWORD playerId)
+{
+ NationBase::init(nationType, raceId, colorSchemeId, playerId);
+
+ //----- init other AI vars -----//
+
+ last_action_id = 0;
+
+ ai_capture_enemy_town_recno = 0;
+ ai_capture_enemy_town_start_attack_date = 0;
+ ai_last_defend_action_date = 0;
+
+ memset( firm_should_close_array, 0, sizeof(firm_should_close_array) );
+
+ ai_base_town_count = 0;
+
+ attack_camp_count = 0;
+
+ //------ init AI info arrays -----//
+
+ init_all_ai_info();
+
+ //----- init AI personality ----//
+
+ init_personalty();
+}
+//---------- End of function Nation::init --------//
+
+
+//--------- Begin of function Nation::deinit --------//
+
+void Nation::deinit()
+{
+ NationBase::deinit();
+
+ deinit_all_ai_info();
+}
+//---------- End of function Nation::deinit --------//
+
+
+//--------- Begin of function Nation::init_all_ai_info --------//
+
+void Nation::init_all_ai_info()
+{
+ err_when( ai_town_array );
+
+ init_ai_info(&ai_town_array, ai_town_count, ai_town_size, AI_TOWN_INIT_SIZE);
+
+ init_ai_info(&ai_base_array, ai_base_count, ai_base_size, AI_BASE_INIT_SIZE);
+ init_ai_info(&ai_mine_array, ai_mine_count, ai_mine_size, AI_MINE_INIT_SIZE);
+ init_ai_info(&ai_factory_array, ai_factory_count, ai_factory_size, AI_FACTORY_INIT_SIZE);
+ init_ai_info(&ai_market_array, ai_market_count, ai_market_size, AI_MARKET_INIT_SIZE);
+ init_ai_info(&ai_inn_array, ai_inn_count, ai_inn_size, AI_INN_INIT_SIZE);
+ init_ai_info(&ai_camp_array, ai_camp_count, ai_camp_size, AI_CAMP_INIT_SIZE);
+ init_ai_info(&ai_research_array, ai_research_count, ai_research_size, AI_RESEARCH_INIT_SIZE);
+ init_ai_info(&ai_war_array, ai_war_count, ai_war_size, AI_WAR_INIT_SIZE);
+ init_ai_info(&ai_harbor_array, ai_harbor_count, ai_harbor_size, AI_HARBOR_INIT_SIZE);
+
+ init_ai_info(&ai_caravan_array, ai_caravan_count, ai_caravan_size, AI_CARAVAN_INIT_SIZE);
+ init_ai_info(&ai_ship_array, ai_ship_count, ai_ship_size, AI_SHIP_INIT_SIZE);
+ init_ai_info(&ai_general_array, ai_general_count, ai_general_size, AI_GENERAL_INIT_SIZE);
+}
+//---------- End of function Nation::init_all_ai_info --------//
+
+
+//--------- Begin of function Nation::init_ai_info --------//
+//
+// <short**> aiInfoArrayPtr - poniter to the AI info array.
+// <short&> aiInfoCount - the count of the AI info array.
+// <short&> aiInfoSize - the size of the AI info array.
+// <int> arrayInitSize - the init size of the array.
+//
+void Nation::init_ai_info(short** aiInfoArrayPtr, short& aiInfoCount, short& aiInfoSize, int arrayInitSize )
+{
+ *aiInfoArrayPtr = (short*) mem_add( sizeof(short) * arrayInitSize );
+
+ memset( *aiInfoArrayPtr, 0, sizeof(short) * arrayInitSize );
+
+ aiInfoCount = 0;
+ aiInfoSize = arrayInitSize;
+}
+//---------- End of function Nation::init_ai_info --------//
+
+
+//--------- Begin of function Nation::deinit_all_ai_info --------//
+
+void Nation::deinit_all_ai_info()
+{
+ err_when( !ai_town_array );
+
+ //------- debug checking -------//
+
+#ifdef DEBUG
+ if( !sys.signal_exit_flag )
+ {
+ err_when( ai_town_count > 0 );
+ err_when( ai_base_town_count > 0 );
+
+ err_when( ai_base_count > 0 );
+ err_when( ai_mine_count > 0 );
+ err_when( ai_factory_count > 0 );
+ err_when( ai_market_count > 0 );
+ err_when( ai_inn_count > 0 );
+ err_when( ai_camp_count > 0 );
+ err_when( ai_research_count > 0 );
+ err_when( ai_war_count > 0 );
+ err_when( ai_harbor_count > 0 );
+
+ err_when( ai_caravan_count > 0 );
+ err_when( ai_ship_count > 0 );
+ err_when( ai_general_count > 0 );
+ }
+#endif
+
+ //------- release array from memory -------//
+
+ mem_del(ai_town_array);
+
+ mem_del(ai_base_array);
+ mem_del(ai_mine_array);
+ mem_del(ai_factory_array);
+ mem_del(ai_market_array);
+ mem_del(ai_inn_array);
+ mem_del(ai_camp_array);
+ mem_del(ai_research_array);
+ mem_del(ai_war_array);
+ mem_del(ai_harbor_array);
+
+ mem_del(ai_caravan_array);
+ mem_del(ai_ship_array);
+ mem_del(ai_general_array);
+}
+//---------- End of function Nation::deinit_all_ai_info --------//
+
+
+//--------- Begin of function Nation::init_personalty --------//
+void Nation::init_personalty()
+{
+ pref_force_projection = m.random(101);
+ pref_military_development = m.random(101);
+ pref_economic_development = 100-pref_military_development;
+ pref_inc_pop_by_capture = m.random(101);
+ pref_inc_pop_by_growth = 100-pref_inc_pop_by_capture;
+ pref_peacefulness = m.random(101);
+ pref_military_courage = m.random(101);
+ pref_territorial_cohesiveness = m.random(101);
+ pref_trading_tendency = m.random(101);
+ pref_allying_tendency = m.random(101);
+ pref_honesty = m.random(101);
+ pref_town_harmony = m.random(101);
+ pref_loyalty_concern = m.random(101);
+ pref_forgiveness = m.random(101);
+ pref_collect_tax = m.random(101);
+ pref_hire_unit = m.random(101);
+ pref_use_weapon = m.random(101);
+ pref_keep_general = m.random(101);
+ pref_keep_skilled_unit = m.random(101);
+ pref_diplomacy_retry = m.random(101);
+ pref_attack_monster = m.random(101);
+ pref_spy = m.random(101);
+ pref_counter_spy = m.random(101);
+ pref_cash_reserve = m.random(101);
+ pref_food_reserve = m.random(101);
+ pref_use_marine = m.random(101);
+ pref_unit_chase_distance = 15+m.random(15);
+ pref_repair_concern = m.random(101);
+ pref_scout = m.random(101);
+}
+//---------- End of function Nation::init_personalty --------//
+
+
+//--------- Begin of function Nation::process_ai --------//
+void Nation::process_ai()
+{
+ //-*********** simulate aat ************-//
+#ifdef DEBUG
+ if(debug_sim_game_type)
+ return;
+#endif
+ //-*********** simulate aat ************-//
+
+ if( config.disable_ai_flag || game.game_mode == GAME_TEST )
+ return;
+
+ //---- if the king has just been killed ----//
+
+ int nationRecno = nation_recno;
+
+ if( !king_unit_recno )
+ {
+ if( think_succeed_king() )
+ return;
+
+ if( think_surrender() )
+ return;
+
+ defeated();
+ return;
+ }
+
+ //-------- process main AI actions ---------//
+
+ process_ai_main();
+
+ if( nation_array.is_deleted(nationRecno) ) // the nation can have surrendered
+ return;
+
+ //------ process queued diplomatic messges first --------//
+
+ // ##### begin Gilbert 4/10 ######//
+ if( (info.game_date-nation_recno)%3 == 0 )
+ {
+ LOG_MSG("begin process_action(0,ACTION_AI_PROCESS_TALK_MSG)");
+ process_action(0, ACTION_AI_PROCESS_TALK_MSG);
+ LOG_MSG("end process_action(0,ACTION_AI_PROCESS_TALK_MSG)");
+ LOG_MSG(m.get_random_seed());
+
+ if( nation_array.is_deleted(nationRecno) ) // the nation can have surrendered
+ return;
+ }
+
+ // ##### end Gilbert 4/10 ######//
+
+ //--------- process queued actions ----------//
+
+ // ##### begin Gilbert 4/10 ######//
+ if( (info.game_date-nation_recno)%3 == 0 )
+ {
+ LOG_MSG("begin process_action()");
+ process_action();
+ LOG_MSG("end process_action()");
+ LOG_MSG(m.get_random_seed());
+
+ if( nation_array.is_deleted(nationRecno) ) // the nation can have surrendered
+ return;
+ }
+ // ##### end Gilbert 4/10 ######//
+
+ //--- process action that are on-going and need continous checking ---//
+
+ process_on_going_action();
+
+ //--------- cheat ---------//
+ //
+ // In tutorial mode only so that your opponent won't surrender
+ // and you won't go to the end game screen.
+ //
+ //-------------------------//
+
+ if( game.game_mode == GAME_TUTORIAL )
+ {
+ if( cash < 100 )
+ add_cheat( (float)200+m.random(500) );
+
+ if( food < 100 )
+ food += 1000;
+ }
+
+ //----- think about updating relationship with other nations -----//
+
+ if( info.game_date%360 == nation_recno%360 )
+ ai_improve_relation();
+
+ //------ think about surrendering -------//
+
+ if( info.game_date%60 == nation_recno%60 )
+ {
+ if( think_surrender() )
+ return;
+
+ if( think_unite_against_big_enemy() )
+ return;
+ }
+}
+//---------- End of function Nation::process_ai --------//
+
+
+//--------- Begin of function Nation::process_on_going_action --------//
+//
+// Process action that are on-going and need continous checking.
+//
+void Nation::process_on_going_action()
+{
+ //--- if the nation is in the process of trying to capture an enemy town ---//
+
+ if( ai_capture_enemy_town_recno )
+ {
+ if( info.game_date%5 == nation_recno%5 )
+ think_capturing_enemy_town();
+ }
+
+ //----- if the nation is in the process of attacking a target ----//
+
+ if( attack_camp_count > 0 )
+ ai_attack_target_execute(1);
+}
+//---------- End of function Nation::process_on_going_action --------//
+
+
+//------- Begin of function Nation::process_ai_main --------//
+//
+void Nation::process_ai_main()
+{
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ String debugStr;
+ debugStr = "Nation ";
+ debugStr += nation_recno;
+#endif
+
+ static short intervalDaysArray[] = { 90, 30, 15, 15 };
+
+ int intervalDays = intervalDaysArray[config.ai_aggressiveness-OPTION_LOW];
+
+ if( game.game_mode == GAME_TUTORIAL )
+ intervalDays = 120;
+
+ switch( (info.game_date-nation_recno*4) % intervalDays )
+ {
+ case 0:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_build_firm";
+#endif
+ think_build_firm();
+ break;
+
+ case 1:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_trading";
+#endif
+ think_trading();
+ break;
+
+ case 2:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_capture";
+#endif
+ think_capture();
+ break;
+
+ case 3:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_explore";
+#endif
+ think_explore();
+ break;
+
+ case 4: // think about expanding its military force
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_military";
+#endif
+ think_military();
+ break;
+
+ case 5:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_secret_attack";
+#endif
+ think_secret_attack();
+ break;
+
+ case 6:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_attack_monster";
+#endif
+ think_attack_monster();
+ break;
+
+ case 7:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_diplomacy";
+#endif
+ think_diplomacy();
+ break;
+
+ case 8:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_marine";
+#endif
+ think_marine();
+ break;
+
+ case 9:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_grand_plan";
+#endif
+ think_grand_plan();
+ break;
+
+ case 10:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_reduce_expense";
+#endif
+ think_reduce_expense();
+ break;
+
+ case 11:
+#if defined(DEBUG) && defined(ENABLE_LOG)
+ debugStr += " think_town";
+#endif
+ think_town();
+ break;
+ }
+
+ LOG_MSG(debugStr);
+ LOG_MSG(m.get_random_seed());
+}
+//---------- End of function Nation::process_ai_main --------//
+
+
+//--------- Begin of function Nation::think_explore --------//
+
+void Nation::think_explore()
+{
+}
+//---------- End of function Nation::think_explore --------//
+
+
+//-------- Begin of function Nation::think_succeed_king --------//
+//
+// return: <int> 1 - a unit succeed the king
+// 0 - no unit available for succeeding the king,
+// the nation is defeated.
+//
+int Nation::think_succeed_king()
+{
+ int i, curRating, bestRating=0;
+ Unit *unitPtr, *bestUnitPtr=NULL;
+ Firm *firmPtr, *bestFirmPtr=NULL;
+ int bestWorkerId=0;
+
+ //---- try to find the best successor from mobile units ----//
+
+ for( i=unit_array.size() ; i>0 ; i-- )
+ {
+ if( unit_array.is_deleted(i) )
+ continue;
+
+ unitPtr = unit_array[i];
+
+ if( unitPtr->nation_recno != nation_recno || !unitPtr->race_id )
+ continue;
+
+ if( !unitPtr->is_visible() && unitPtr->unit_mode != UNIT_MODE_OVERSEE )
+ continue;
+
+ err_when( unitPtr->skill.combat_level<= 0 );
+
+ curRating = 0;
+
+ if( unitPtr->race_id == race_id )
+ curRating += 50;
+
+ if( unitPtr->rank_id == RANK_GENERAL )
+ curRating += 50;
+
+ if( unitPtr->skill.skill_id == SKILL_LEADING )
+ curRating += unitPtr->skill.skill_level;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestUnitPtr = unitPtr;
+ }
+ }
+
+ //---- try to find the best successor from military camps ----//
+
+ for( i=firm_array.size() ; i>0 ; i-- )
+ {
+ if( firm_array.is_deleted(i) )
+ continue;
+
+ firmPtr = firm_array[i];
+
+ if( firmPtr->nation_recno != nation_recno )
+ continue;
+
+ //------ only military camps -------//
+
+ if( firmPtr->firm_id == FIRM_CAMP )
+ {
+ Worker* workerPtr = firmPtr->worker_array;
+
+ for(int j=1 ; j<=firmPtr->worker_count ; j++, workerPtr++ )
+ {
+ if( !workerPtr->race_id )
+ continue;
+
+ curRating = 0;
+
+ if( workerPtr->race_id == race_id )
+ curRating += 50;
+
+ if( workerPtr->rank_id == RANK_GENERAL )
+ curRating += 50;
+
+ if( workerPtr->skill_id == SKILL_LEADING )
+ curRating += workerPtr->skill_level;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestUnitPtr = NULL;
+ bestFirmPtr = firmPtr;
+ bestWorkerId = j;
+ }
+ }
+ }
+ }
+
+ //------- if the best successor is a mobile unit -------//
+
+ if( bestUnitPtr )
+ {
+ //-- if the unit is in a command base or seat of power, mobilize it --//
+
+ if( !bestUnitPtr->is_visible() )
+ {
+ err_when( bestUnitPtr->unit_mode != UNIT_MODE_OVERSEE );
+
+ firm_array[bestUnitPtr->unit_mode_para]->mobilize_overseer();
+
+ err_when( bestUnitPtr->skill.combat_level<= 0 );
+ }
+
+ //---------- succeed the king -------------//
+
+ if( bestUnitPtr->is_visible() ) // it may still be not visible if there is no space for the unit to be mobilized
+ {
+ if( bestUnitPtr->spy_recno && bestUnitPtr->true_nation_recno() == nation_recno ) // if this is a spy and he's our spy
+ spy_array[bestUnitPtr->spy_recno]->drop_spy_identity(); // revert the spy to a normal unit
+
+ succeed_king( bestUnitPtr->sprite_recno );
+ return 1;
+ }
+ }
+
+ //------- if the best successor is a soldier in a camp -------//
+
+ if( bestFirmPtr )
+ {
+ int unitRecno = bestFirmPtr->mobilize_worker(bestWorkerId, COMMAND_AI);
+
+ if( unitRecno )
+ {
+ succeed_king( unitRecno );
+ return 1;
+ }
+ }
+
+ //--- if stil not found here, then try to locate the sucessor from villages ---//
+
+ Town* townPtr;
+
+ for( i=town_array.size() ; i>0 ; i-- )
+ {
+ if( town_array.is_deleted(i) )
+ continue;
+
+ townPtr = town_array[i];
+
+ if( townPtr->nation_recno != nation_recno )
+ continue;
+
+ if( townPtr->recruitable_race_pop(race_id, 0) > 0 ) // if this town has people with the same race as the original king
+ {
+ int unitRecno = townPtr->mobilize_town_people(race_id, 1, 0); // 1-dec pop, 0-don't mobilize spies
+
+ if( unitRecno )
+ {
+ succeed_king( unitRecno );
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+//---------- End of function Nation::think_succeed_king ---------//
+
+
+//--------- Begin of function Nation::ai_improve_relation --------//
+//
+// This function is called once every year.
+//
+void Nation::ai_improve_relation()
+{
+ NationRelation* nationRelation;
+
+ for( int i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) )
+ continue;
+
+ nationRelation = get_relation(i);
+
+ if( nationRelation->status == NATION_HOSTILE )
+ continue;
+
+ //--- It improves the AI relation with nations that have trade with us. ---//
+
+ change_ai_relation_level( i, trade_rating(i) / 10 );
+
+ //--- decrease the started_war_on_us_count once per year, gradually forgiving other nations' wrong doing ---//
+
+ if( nationRelation->started_war_on_us_count > 0
+ && m.random(5-pref_forgiveness/20) > 0 )
+ {
+ nationRelation->started_war_on_us_count--;
+ }
+ }
+}
+//---------- End of function Nation::ai_improve_relation --------//
+
+
diff --git a/OAI_MAR2.cpp b/OAI_MAR2.cpp
new file mode 100644
index 0000000..6465629
--- /dev/null
+++ b/OAI_MAR2.cpp
@@ -0,0 +1,452 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_MAR2.CPP
+//Description: AI functions for processing AI marine actions
+
+#include <OTOWN.h>
+#include <OREGIONS.h>
+#include <OU_MARI.h>
+#include <OF_HARB.h>
+#include <ONATION.h>
+
+//-------- Begin of function Nation::ai_sea_travel -------//
+//
+// action_x_loc, action_y_loc - location of the destination
+//
+// group_unit_array - array of units that will do the sea travel
+// instance_count - no. of units in the array
+//
+// action_para - the action of units after they have arrived the region,
+// it is one of the following:
+//
+// SEA_ACTION_SETTLE - settle into a town
+// SEA_ACTION_BUILD_CAMP - build and assign to a firm
+// SEA_ACTION_ASSIGN_TO_FIRM - assign to a firm
+// SEA_ACTION_MOVE - just move to the destination
+// (this include moving to the location of a battle field.)
+//
+//---------------------------------------------//
+//
+// Procedures:
+// * 1. Locate a ship, build one if cannot locate any.
+// * 2. Assign the units to the ship.
+// 3. Move the ship to the destination region.
+// 4. Units disembark on the coast.
+// 5. Units move to the destination.
+//
+// This function deal with the 1st and 2nd procedures,
+// when they are finished, action ACTION_AI_SEA_TRAVEL2
+// will be added.
+//
+//---------------------------------------------//
+//
+int Nation::ai_sea_travel(ActionNode* actionNode)
+{
+ err_when( actionNode->instance_count < 1 ||
+ actionNode->instance_count > ActionNode::MAX_ACTION_GROUP_UNIT );
+
+ Unit* unitPtr = unit_array[actionNode->group_unit_array[0]];
+
+ err_when( unitPtr->nation_recno != nation_recno );
+ err_when( !unitPtr->is_visible() );
+
+ //---- figure out the sea region id which the ship should appear ----//
+
+ int unitRegionId = world.get_region_id(unitPtr->next_x_loc(), unitPtr->next_y_loc());
+ int destRegionId = world.get_region_id(actionNode->action_x_loc, actionNode->action_y_loc);
+
+ int seaRegionId = region_array.get_sea_path_region_id(unitRegionId, destRegionId);
+
+ //------- 1. try to locate a ship --------//
+
+ int shipUnitRecno = ai_find_transport_ship(seaRegionId, unitPtr->next_x_loc(), unitPtr->next_y_loc());
+
+ if( !shipUnitRecno )
+ return -1; // must return -1 instead of 0 as the action must be executed immediately otherwise the units will be assigned with other action and the unit list may no longer be valid
+
+ //---- if this ship is in the harbor, sail it out ----//
+
+ UnitMarine* unitMarine = (UnitMarine*) unit_array[shipUnitRecno];
+
+ if( unitMarine->unit_mode == UNIT_MODE_IN_HARBOR )
+ {
+ FirmHarbor* firmHarbor = (FirmHarbor*) firm_array[unitMarine->unit_mode_para];
+
+ firmHarbor->sail_ship(unitMarine->sprite_recno, COMMAND_AI);
+ }
+
+ if( !unitMarine->is_visible() ) // no space in the sea for placing the ship
+ return -1;
+
+ //------ 2. Assign the units to the ship -------//
+
+ unitMarine->ai_action_id = actionNode->action_id;
+
+ err_when( unit_res[unitMarine->unit_id]->unit_class != UNIT_CLASS_SHIP );
+
+ // ##### patch begin Gilbert 5/8 #######//
+ unit_array.assign_to_ship(unitMarine->next_x_loc(), unitMarine->next_y_loc(), 0, actionNode->group_unit_array, actionNode->instance_count, COMMAND_AI, unitMarine->sprite_recno );
+ // ##### patch end Gilbert 5/8 #######//
+
+ for( int i=0 ; i<actionNode->instance_count ; i++ )
+ unit_array[ actionNode->group_unit_array[i] ]->ai_action_id = actionNode->action_id;
+
+ actionNode->instance_count++; // +1 for the ship
+
+ actionNode->processing_instance_count = actionNode->instance_count-1; // -1 because when we return 1, it will be increased by 1 automatically
+ actionNode->action_para2 = 0; // reset it, it is set in Nation::action_finished()
+
+ return 1;
+}
+//-------- End of function Nation::ai_sea_travel -------//
+
+
+//-------- Begin of function Nation::ai_sea_travel2 -------//
+//
+// action_x_loc, action_y_loc - location of the destination
+// action_para2 - the recno of the ship
+// (this is set when the ship has moved to the beach,
+// the function responsible for setting this is Nation::action_finished() )
+//
+//---------------------------------------------//
+//
+// Procedures:
+// 1. Locate a ship, build one if cannot locate any.
+// 2. Assign the units to the ship.
+// * 3. Move the ship to the destination region.
+// 4. Units disembark on the coast.
+// 5. Units move to the destination.
+//
+//---------------------------------------------//
+//
+int Nation::ai_sea_travel2(ActionNode* actionNode)
+{
+ if( unit_array.is_deleted(actionNode->action_para2) )
+ return -1;
+
+ UnitMarine* unitMarine = (UnitMarine*) unit_array[actionNode->action_para2];
+
+ if( unit_res[unitMarine->unit_id]->unit_class != UNIT_CLASS_SHIP )
+ return -1;
+
+ if( unitMarine->nation_recno != nation_recno )
+ return -1;
+
+ //--------------------------------------------------------//
+
+ int realDestXLoc, realDestYLoc; // reference vars for returning vars.
+
+ unitMarine->ship_to_beach( actionNode->action_x_loc, actionNode->action_y_loc, realDestXLoc, realDestYLoc ); // the real destination the ship is moving towards.
+ unitMarine->ai_action_id = actionNode->action_id;
+
+ return 1;
+}
+//-------- End of function Nation::ai_sea_travel2 -------//
+
+
+//-------- Begin of function Nation::ai_sea_travel3 -------//
+//
+// action_x_loc, action_y_loc - location of the destination
+// action_para2 - the recno of the ship
+//
+//---------------------------------------------//
+//
+// Procedures:
+// 1. Locate a ship, build one if cannot locate any.
+// 2. Assign the units to the ship.
+// 3. Move the ship to the destination region.
+// * 4. Units disembark on the coast.
+// * 5. Units move to the destination.
+//
+//---------------------------------------------//
+//
+int Nation::ai_sea_travel3(ActionNode* actionNode)
+{
+ if( unit_array.is_deleted(actionNode->action_para2) )
+ return -1;
+
+ UnitMarine* unitMarine = (UnitMarine*) unit_array[actionNode->action_para2];
+
+ if( unit_res[unitMarine->unit_id]->unit_class != UNIT_CLASS_SHIP )
+ return -1;
+
+ if( unitMarine->nation_recno != nation_recno )
+ return -1;
+
+ //-------- 4. Units disembark on the coast. -------//
+
+ if( !unitMarine->can_unload_unit() )
+ return 0;
+
+ //--- make a copy of the recnos of the unit on the ship ---//
+
+ short unitRecnoArray[MAX_UNIT_IN_SHIP];
+ short unitCount;
+
+ memcpy( unitRecnoArray, unitMarine->unit_recno_array, sizeof(unitRecnoArray) );
+ unitCount = unitMarine->unit_count;
+
+ unitMarine->unload_all_units(COMMAND_AI); // unload all units now
+
+ return 1; // finish the action.
+
+/*
+ //---------- 5. Validate all units ----------//
+
+ for( int i=unitCount-1 ; i>=0 ; i-- )
+ {
+ if( unit_array.is_deleted( unitRecnoArray[i] ) ||
+ unit_array[ unitRecnoArray[i] ]->nation_recno != nation_recno )
+ {
+ err_when( unitCount > MAX_UNIT_IN_SHIP );
+
+ m.del_array_rec( unitRecnoArray, unitCount, sizeof(unitRecnoArray[0]), i+1 );
+ unitCount--;
+ }
+ }
+
+ if( unitCount==0 )
+ return -1;
+
+ err_when( unitCount < 0 );
+
+ //--- 6. Unit actions after they have arrived the destination region ----//
+
+ int destXLoc = actionNode->action_x_loc;
+ int destYLoc = actionNode->action_y_loc;
+ Location* locPtr = world.get_loc(destXLoc, destYLoc);
+
+ switch(actionNode->action_para)
+ {
+ case SEA_ACTION_SETTLE:
+ if( locPtr->is_town() && town_array[locPtr->town_recno()]->nation_recno == nation_recno )
+ {
+ Town *townPtr = town_array[locPtr->town_recno()];
+ unit_array.assign(townPtr->loc_x1, townPtr->loc_y1, 0, COMMAND_AI, unitRecnoArray, unitCount); // assign to an existing town
+ }
+ else //-- if there is no town there, the unit will try to settle, if there is no space for settle, settle() will just have the units move to the destination
+ {
+ unit_array.settle(destXLoc, destYLoc, 0, COMMAND_AI, unitRecnoArray, unitCount); // settle as a new town
+ }
+ break;
+
+ case SEA_ACTION_BUILD_CAMP:
+ {
+ Unit* unitPtr = unit_array[ unitRecnoArray[0] ];
+
+ unitPtr->build_firm(destXLoc, destYLoc, FIRM_CAMP, COMMAND_AI );
+
+ unitPtr->ai_action_id = actionNode->action_id;
+ actionNode->processing_instance_count++;
+ break;
+ }
+
+ case SEA_ACTION_ASSIGN_TO_FIRM:
+ if( check_firm_ready(destXLoc, destYLoc) )
+ unit_array.assign(destXLoc, destYLoc, 0, COMMAND_AI, unitRecnoArray, unitCount);
+ break;
+
+ case SEA_ACTION_MOVE:
+ unit_array.move_to(destXLoc, destYLoc, 0, unitRecnoArray, unitCount, COMMAND_AI);
+ break;
+
+ case SEA_ACTION_NONE: // just transport them to the specific region and disemark and wait for their own actions
+ break;
+ }
+
+ //---------- set the action id. of the units ---------//
+
+ if( actionNode->action_para != SEA_ACTION_BUILD_CAMP ) // with the exception of SEA_ACTION_BUILD_CAMP, units in all other actions are immediately executed
+ {
+ for( int i=unitCount-1 ; i>=0 ; i-- )
+ {
+ unit_array[ unitRecnoArray[i] ]->ai_action_id = actionNode->action_id;
+ actionNode->processing_instance_count++;
+ }
+ }
+
+ //---------------------------------------------//
+
+ actionNode->processing_instance_count--; // decrease it by one as it will be increased in process_action()
+
+ actionNode->instance_count = actionNode->processing_instance_count+1; // set the instance count so process_action() won't cause error.
+
+ return 1;
+*/
+}
+//-------- End of function Nation::ai_sea_travel3 -------//
+
+
+//-------- Begin of function Nation::ai_find_transport_ship -------//
+//
+// Locate a ship for transporting units.
+//
+// <int> seaRegionId - region id. of the sea which the ship should appear in.
+// <int> unitXLoc, unitYLoc - the location of the units to be picked up,
+// try to select a harbor close to this location.
+// [int] findBest - whether need to find the best ship or just return
+// one if there is one found. (default: 1)
+//
+// return: <int> the recno of the ship located.
+//
+int Nation::ai_find_transport_ship(int seaRegionId, int unitXLoc, int unitYLoc, int findBest)
+{
+ //------- locate a suitable ship --------//
+
+ UnitMarine* unitMarine;
+ int curRating, bestRating=0, bestUnitRecno=0;
+
+ for( int i=0 ; i<ai_ship_count ; i++ )
+ {
+ unitMarine = (UnitMarine*) unit_array[ ai_ship_array[i] ];
+
+ err_when( unit_res[unitMarine->unit_id]->unit_class != UNIT_CLASS_SHIP );
+
+ if( unitMarine->unit_count > 0 || // if there are already units in the ship
+ unit_res[unitMarine->unit_id]->carry_unit_capacity==0 ) // if the ship does not carry units
+ {
+ continue;
+ }
+
+ //------- if this ship is in the harbor ---------//
+
+ if( unitMarine->unit_mode == UNIT_MODE_IN_HARBOR )
+ {
+ FirmHarbor* firmHarbor = (FirmHarbor*) firm_array[unitMarine->unit_mode_para];
+
+ err_when( firmHarbor->firm_id != FIRM_HARBOR );
+
+ if( firmHarbor->sea_region_id != seaRegionId )
+ continue;
+ }
+
+ //--------- if this ship is on the sea ----------//
+
+ else
+ {
+ if( !unitMarine->is_ai_all_stop() )
+ continue;
+
+ if( unitMarine->region_id() != seaRegionId )
+ continue;
+
+ err_when( !unitMarine->is_visible() );
+
+ if( !unitMarine->is_visible() )
+ continue;
+ }
+
+ //--------- check if the sea region is matched ---------//
+
+ if( !findBest ) // return immediately when a suitable one is found
+ return unitMarine->sprite_recno;
+
+ curRating = world.distance_rating( unitXLoc, unitYLoc,
+ unitMarine->next_x_loc(), unitMarine->next_y_loc() );
+
+ curRating += (int)unitMarine->hit_points/10 // damage
+ + (int)unitMarine->max_hit_points/10; // ship class
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestUnitRecno = unitMarine->sprite_recno;
+ }
+ }
+
+ return bestUnitRecno;
+}
+//-------- End of function Nation::ai_find_transport_ship -------//
+
+
+//-------- Begin of function Nation::ai_build_ship -------//
+//
+// <int> seaRegionId - region id. of the sea the ship will sail on.
+//
+// <int> preferXLoc, preferYLoc - prefer selecting a harbor that
+// is close to this location.
+//
+// <int> needTransportUnit - 1 if need to transport units
+// 0 if the ship is for trading
+//
+// return: <int> the recno of the ship that the harbor has building.
+//
+int Nation::ai_build_ship(int seaRegionId, int preferXLoc, int preferYLoc, int needTransportUnit)
+{
+ //------ select the harbor for building ship -----//
+
+ FirmHarbor *firmHarbor, *bestHarbor=NULL;
+ int curRating, bestRating=0;
+
+ for( int i=0 ; i<ai_harbor_count ; i++ )
+ {
+ firmHarbor = (FirmHarbor*) firm_array[ ai_harbor_array[i] ];
+
+ err_when( firmHarbor->nation_recno != nation_recno );
+
+ if( !firmHarbor->can_build_ship() )
+ continue;
+
+ if( firmHarbor->sea_region_id != seaRegionId )
+ continue;
+
+ curRating = world.distance_rating( preferXLoc, preferYLoc,
+ firmHarbor->center_x, firmHarbor->center_y );
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestHarbor = firmHarbor;
+ }
+ }
+
+ if( !bestHarbor )
+ return 0;
+
+ //------ think about the type of ship to build -----//
+
+ int unitId;
+
+ if( needTransportUnit )
+ {
+ if( unit_res[UNIT_GALLEON]->get_nation_tech_level(nation_recno) > 0 )
+ unitId = UNIT_GALLEON;
+
+ else if( unit_res[UNIT_CARAVEL]->get_nation_tech_level(nation_recno) > 0 )
+ unitId = UNIT_GALLEON;
+
+ else
+ unitId = UNIT_VESSEL;
+ }
+ else
+ {
+ if( unit_res[UNIT_GALLEON]->get_nation_tech_level(nation_recno) > 0 )
+ unitId = UNIT_GALLEON;
+ else // don't use Caravel as it can only transport 5 units at a time
+ unitId = UNIT_TRANSPORT;
+ }
+
+ bestHarbor->build_ship( unitId, COMMAND_AI );
+
+ return 1;
+}
+//-------- End of function Nation::ai_build_ship -------//
+
diff --git a/OAI_MAR3.cpp b/OAI_MAR3.cpp
new file mode 100644
index 0000000..49561b0
--- /dev/null
+++ b/OAI_MAR3.cpp
@@ -0,0 +1,446 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_MAR3.CPP
+//Description: AI functions on sea exploration, trading
+
+#include <OTOWN.h>
+#include <OREGIONS.h>
+#include <OU_MARI.h>
+#include <OUNITRES.h>
+#include <OSITE.h>
+#include <OF_HARB.h>
+#include <OF_CAMP.h>
+#include <ONATION.h>
+
+
+//------ Begin of function Nation::ai_settle_to_region ------//
+//
+// <int> destXLoc, destYLoc - the location of the destination to settle.
+// <int> seaActionId - SEA_ACTION_???
+//
+int Nation::ai_settle_to_region(int destXLoc, int destYLoc, int seaActionId)
+{
+ #define SETTLE_REGION_UNIT_COUNT 9 // no. of units to move to settle on a new region each time
+
+ //---- think about which town to recruit the people -----//
+
+ int destRegionId = world.get_region_id(destXLoc, destYLoc);
+ int curRating, bestRating=0, seaRegionId;
+ Town *townPtr, *bestTown=NULL;
+
+ for( int i=0 ; i<ai_town_count ; i++ )
+ {
+ townPtr = town_array[ ai_town_array[i] ];
+
+ if( townPtr->has_linked_own_camp==0 ) // if there is no command base linked to this town, we cannot recruit any peasants from it
+ continue;
+
+ if( townPtr->jobless_population < SETTLE_REGION_UNIT_COUNT ) // don't get peasant from this town if the jobless population is less than 20
+ continue;
+
+ //--- only send units from this region if we have a harbor in that region ---//
+
+ // ###### patch begin Gilbert 16/3 #######//
+ // region_stat_id of a region may be zero
+ if(
+//#ifdef AMPLUS
+ region_array[townPtr->region_id]->region_stat_id == 0 ||
+//#endif
+ region_array.get_region_stat(townPtr->region_id)->harbor_nation_count_array[nation_recno-1] == 0 )
+ // ###### patch end Gilbert 16/3 #######//
+ continue;
+
+ curRating = world.distance_rating(destXLoc, destYLoc, townPtr->center_x, townPtr->center_y);
+
+ curRating += townPtr->jobless_population;
+
+ curRating += townPtr->average_loyalty(); // select a town with high loyalty
+
+ if( curRating <= bestRating )
+ continue;
+
+ //------- see if we have ships ready currently -----//
+
+ seaRegionId = region_array.get_sea_path_region_id(townPtr->region_id, destRegionId);
+
+ if( !ai_find_transport_ship(seaRegionId, townPtr->center_x, townPtr->center_y, 0) ) // 0-don't have to find the best, return immediately whenever a suitable one is found
+ continue;
+
+ bestRating = curRating;
+ bestTown = townPtr;
+ }
+
+ if( !bestTown )
+ return 0;
+
+ //------- try to recruit 9 units from one of our towns --------//
+
+ short recruitedUnitArray[SETTLE_REGION_UNIT_COUNT];
+ int recruitedCount=0;
+ int raceId = bestTown->majority_race();
+ int unitRecno;
+ int loopCount=0;
+
+ while( recruitedCount < SETTLE_REGION_UNIT_COUNT )
+ {
+ err_when( ++loopCount > 100 );
+
+ if( bestTown->recruitable_race_pop( raceId, 1 ) )
+ {
+ unitRecno = bestTown->recruit(-1, raceId, COMMAND_AI);
+
+ if( !unitRecno ) // no space for new unit
+ break;
+
+ err_when( unit_array.is_deleted(unitRecno) );
+ err_when( !unit_array[unitRecno]->is_visible() );
+
+ recruitedUnitArray[recruitedCount++] = unitRecno;
+ }
+ else
+ {
+ raceId = bestTown->pick_random_race(0, 1); // 0-recruitable only, 1-will also pick spies
+
+ if( !raceId )
+ break;
+ }
+ }
+
+ //--- if due to some reasons that the no. of units recruited is less than half of what we need, do not continue to sea travel.
+
+ if( recruitedCount < SETTLE_REGION_UNIT_COUNT/2 )
+ return 0;
+
+ int actionRecno = add_action( destXLoc, destYLoc, 0, 0, ACTION_AI_SEA_TRAVEL, seaActionId,
+ recruitedCount, 0, 0, recruitedUnitArray );
+
+ if( actionRecno ) // must process it immediately otherwise the recruited units will do something else
+ return process_action(actionRecno);
+ else
+ return 0;
+}
+//------ End of function Nation::ai_settle_to_region -------//
+
+
+//------ Begin of function Nation::ai_patrol_to_region ------//
+//
+// Look for a military camp for moving all of its soldiers to
+// a new region and set up a new military camp to host the soldiers.
+//
+// <int> destXLoc, destYLoc - the location of the destination to set up
+// a military camp.
+// <int> seaActionId - SEA_ACTION_???
+//
+int Nation::ai_patrol_to_region(int destXLoc, int destYLoc, int seaActionId)
+{
+ #define SETTLE_REGION_UNIT_COUNT 9 // no. of units to move to settle on a new region each time
+
+ //---- think about which town to recruit the people -----//
+
+ int destRegionId = world.get_region_id(destXLoc, destYLoc);
+ int curRating, bestRating=0, seaRegionId;
+ int kingRecno = nation_array[nation_recno]->king_unit_recno;
+ FirmCamp *firmCamp, *bestCamp=NULL;
+
+ for( int i=0 ; i<ai_camp_count ; i++ )
+ {
+ firmCamp = (FirmCamp*) firm_array[ ai_camp_array[i] ];
+
+ if( !(firmCamp->overseer_recno && firmCamp->worker_count==MAX_WORKER) ) // only when the camp is filled with workers
+ continue;
+
+ if( firmCamp->ai_capture_town_recno ) // the base is trying to capture an independent town
+ continue;
+
+ if( firmCamp->is_attack_camp )
+ continue;
+
+ if( firmCamp->overseer_recno == kingRecno ) // if the king oversees this firm
+ continue;
+
+ //--- only send units from this region if we have a harbor in that region ---//
+
+ // ####### patch begin Gilbert 16/3 ########//
+ if(
+//#ifdef AMPLUS
+ region_array[firmCamp->region_id]->region_stat_id == 0 ||
+//#endif
+ region_array.get_region_stat(firmCamp->region_id)->harbor_nation_count_array[nation_recno-1] == 0 )
+ continue;
+ // ####### patch end Gilbert 16/3 ########//
+
+ curRating = world.distance_rating(destXLoc, destYLoc, firmCamp->center_x, firmCamp->center_y);
+
+ if( curRating <= bestRating )
+ continue;
+
+ //------- see if we have ships ready currently -----//
+
+ seaRegionId = region_array.get_sea_path_region_id(firmCamp->region_id, destRegionId);
+
+ if( !ai_find_transport_ship(seaRegionId, firmCamp->center_x, firmCamp->center_y, 0) ) // 0-don't have to find the best, return immediately whenever a suitable one is found
+ continue;
+
+ bestRating = curRating;
+ bestCamp = firmCamp;
+ }
+
+ if( !bestCamp )
+ return 0;
+
+ //----- patrol the camp troop ajnd assign it to a ship -----//
+
+ bestCamp->patrol();
+
+ if( bestCamp->patrol_unit_count > 0 ) // there could be chances that there are no some for mobilizing the units
+ {
+ int actionRecno = add_action( destXLoc, destYLoc, 0, 0, ACTION_AI_SEA_TRAVEL, seaActionId,
+ bestCamp->patrol_unit_count, 0, 0, bestCamp->patrol_unit_array );
+
+ if( actionRecno ) // must process it immediately otherwise the recruited units will do something else
+ return process_action(actionRecno);
+ }
+
+ return 0;
+}
+//------ End of function Nation::ai_patrol_to_region -------//
+
+
+
+//------- Begin of function Nation::has_trade_ship -----------//
+//
+// Whether this nation already has a trade ship travelling between
+// the two given harbors.
+//
+// return: <int> recno of the ship.
+// 0 - if there isn't any yet.
+//
+int Nation::has_trade_ship(int firmRecno1, int firmRecno2)
+{
+ UnitMarine* unitMarine;
+
+ for( int i=ai_ship_count-1 ; i>=0 ; i-- )
+ {
+ unitMarine = (UnitMarine*) unit_array[ ai_ship_array[i] ];
+
+ err_when( unit_res[ unitMarine->unit_id ]->unit_class != UNIT_CLASS_SHIP );
+
+ if( unitMarine->stop_defined_num < 2 )
+ continue;
+
+ if( ( unitMarine->stop_array[0].firm_recno == firmRecno1 &&
+ unitMarine->stop_array[1].firm_recno == firmRecno2 ) ||
+ ( unitMarine->stop_array[1].firm_recno == firmRecno1 &&
+ unitMarine->stop_array[0].firm_recno == firmRecno2 ) )
+ {
+ return unitMarine->sprite_recno;
+ }
+ }
+
+ return 0;
+}
+//--------- End of function Nation::has_trade_ship -----------//
+
+
+//------ Begin of function Nation::think_move_to_region_with_mine ------//
+//
+// Think about moving to a region with mines and settle a town next to
+// the mine.
+//
+int Nation::think_move_to_region_with_mine()
+{
+ if( total_jobless_population < 30 )
+ return 0;
+
+ //---------------------------------------------------//
+
+ int curRating, bestRating=0, bestRegionId=0;
+ RegionStat* regionStat = region_array.region_stat_array;
+
+ int i;
+ for( i=0 ; i<region_array.region_stat_count ; i++, regionStat++ )
+ {
+ if( regionStat->town_nation_count_array[nation_recno-1] > 0 ) // if we already have towns there
+ continue;
+
+ if( regionStat->raw_count==0 )
+ continue;
+
+ //-- if we have already build one camp there, just waiting for sending a few peasants there, then process it first --//
+
+ if( regionStat->camp_nation_count_array[nation_recno-1] > 0 )
+ {
+ bestRegionId = regionStat->region_id;
+ break;
+ }
+
+ //-----------------------------------------------//
+
+ curRating = regionStat->raw_count*3 - regionStat->nation_presence_count;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestRegionId = regionStat->region_id;
+ }
+ }
+
+ if( !bestRegionId )
+ return 0;
+
+ //----- select the raw site to acquire -----//
+
+ Site* sitePtr;
+
+ for( i=site_array.size() ; i>0 ; i-- )
+ {
+ if( site_array.is_deleted(i) )
+ continue;
+
+ sitePtr = site_array[i];
+
+ if( sitePtr->region_id == bestRegionId )
+ break;
+ }
+
+ if( i==0 )
+ return 0;
+
+ //----- decide the location of the settlement -----//
+
+ return ai_build_camp_town_next_to( sitePtr->map_x_loc-1, sitePtr->map_y_loc-1,
+ sitePtr->map_x_loc+1, sitePtr->map_y_loc+1 );
+}
+//------ End of function Nation::think_move_to_region_with_mine -------//
+
+
+//------ Begin of function Nation::ai_build_camp_town_next_to ------//
+//
+// Build a new camp and settle a new town next to the given location.
+//
+// <int> xLoc1, yLoc1, xLoc2, yLoc2 - the location that the new camp
+// and town should be built next to.
+//
+int Nation::ai_build_camp_town_next_to(int xLoc1, int yLoc1, int xLoc2, int yLoc2)
+{
+ //---- first see if we already have a camp in the region ---//
+
+ int regionId = world.get_region_id(xLoc1, yLoc1);
+
+ // ##### patch begin Gilbert 16/3 #######//
+ //#ifdef AMPLUS
+ if( region_array[regionId]->region_stat_id == 0)
+ return 0;
+ //#endif
+ // ##### patch end Gilbert 16/3 #######//
+
+ if( region_array.get_region_stat(regionId)->camp_nation_count_array[nation_recno-1] == 0 )
+ {
+ //--- if we don't have one yet, build one next to the destination ---//
+
+ if( !world.locate_space( xLoc1, yLoc1, xLoc2, yLoc2,
+ 3, 3, UNIT_LAND, regionId, 1 ) ) // 1-locating the space for building
+ {
+ return 0;
+ }
+
+ if( !world.can_build_firm( xLoc1, yLoc1, FIRM_CAMP ) )
+ return 0;
+
+ return ai_patrol_to_region(xLoc1, yLoc1, SEA_ACTION_BUILD_CAMP);
+ }
+ else //-- if there's already a camp there, then set people there to settle --//
+ {
+ FirmCamp* firmCamp;
+
+ for( int i=0 ; i<ai_camp_count ; i++ )
+ {
+ firmCamp = (FirmCamp*) firm_array[ ai_camp_array[i] ];
+
+ if( firmCamp->region_id != regionId )
+ continue;
+
+ xLoc1 = firmCamp->loc_x1;
+ yLoc1 = firmCamp->loc_y1;
+ xLoc2 = firmCamp->loc_x2;
+ yLoc2 = firmCamp->loc_y2;
+
+ if( world.locate_space( xLoc1, yLoc1, xLoc2, yLoc2,
+ STD_TOWN_LOC_WIDTH, STD_TOWN_LOC_HEIGHT, UNIT_LAND, regionId, 1 ) ) // 1-locating the space for building
+ {
+ if( world.can_build_town( xLoc1, yLoc1 ) )
+ return ai_settle_to_region(xLoc1, yLoc1, SEA_ACTION_SETTLE);
+ }
+ }
+ }
+
+ return 0;
+}
+//------ End of function Nation::ai_build_camp_town_next_to -------//
+
+
+//------ Begin of function Nation::ai_sea_attack_target ------//
+//
+// <int> targetXLoc, targetYLoc - the location of the target.
+//
+int Nation::ai_sea_attack_target(int targetXLoc, int targetYLoc)
+{
+ UnitMarine* unitMarine;
+ int targetRegionId = world.get_region_id(targetXLoc, targetYLoc);
+ int rc = 0;
+
+ for( int i=0 ; i<ai_ship_count ; i++ )
+ {
+ unitMarine = (UnitMarine*) unit_array[ ai_ship_array[i] ];
+
+ if( unitMarine->attack_count==0 )
+ continue;
+
+ if( !unitMarine->is_ai_all_stop() )
+ continue;
+
+ //----- if the ship is in the harbor now -----//
+
+ if( unitMarine->unit_mode == UNIT_MODE_IN_HARBOR )
+ {
+ FirmHarbor* firmHarbor = (FirmHarbor*) firm_array[unitMarine->unit_mode_para];
+
+ if( firmHarbor->sea_region_id != targetRegionId )
+ continue;
+
+ firmHarbor->sail_ship(unitMarine->sprite_recno, COMMAND_AI);
+ }
+
+ if( !unitMarine->is_visible() ) // no space in the sea for placing the ship
+ continue;
+
+ if( unitMarine->region_id() != targetRegionId )
+ continue;
+
+ //------ order the ship to attack the target ------//
+
+ unitMarine->attack_unit( targetXLoc, targetYLoc );
+ rc = 1;
+ }
+
+ return rc;
+}
+//-------- End of function Nation::ai_sea_attack_target -------//
diff --git a/OAI_MARI.cpp b/OAI_MARI.cpp
new file mode 100644
index 0000000..22b9548
--- /dev/null
+++ b/OAI_MARI.cpp
@@ -0,0 +1,664 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_MARI.CPP
+//Description: AI functions on sea exploration, trading
+
+#include <OTOWN.h>
+#include <OREGIONS.h>
+#include <OU_MARI.h>
+#include <OUNITRES.h>
+#include <OSITE.h>
+#include <OF_HARB.h>
+#include <OF_CAMP.h>
+#include <ONATION.h>
+
+//--------- Begin of function Nation::think_marine --------//
+//
+void Nation::think_marine()
+{
+ if( pref_use_marine < 50 ) // don't use marine at all
+ return;
+
+ if( !ai_should_spend(20+pref_use_marine/2) ) // 20 to 70 importance rating
+ return;
+
+ //--- think over building harbor network ---//
+
+ think_build_harbor_network();
+
+ if( ai_harbor_count == 0 )
+ return;
+
+ //------ think about sea attack enemies -------//
+
+ if( m.random(3)==0 ) // 33% chance
+ {
+ if( think_sea_attack_enemy() )
+ return;
+ }
+
+ //---- check if it is safe for sea travel now ----//
+
+ if( !ai_is_sea_travel_safe() )
+ return;
+
+ //----- think over moving between regions -----//
+
+ think_move_between_region();
+
+// think_move_to_region_with_mine();
+}
+//---------- End of function Nation::think_marine --------//
+
+
+//----- Begin of function Nation::think_build_harbor_network ----//
+//
+// Think about thinking a harbor network so that we have harbors
+// from every region to any other regions.
+//
+int Nation::think_build_harbor_network()
+{
+ //--- only build one harbor at a time, to avoid double building ---//
+
+ if( is_action_exist( ACTION_AI_BUILD_FIRM, FIRM_HARBOR ) )
+ return 0;
+
+ //--------------------------------------------//
+
+ RegionStat* regionStat = region_array.region_stat_array;
+ RegionPath* regionPath;
+
+ for( int i=0 ; i<region_array.region_stat_count ; i++, regionStat++ )
+ {
+ //--- only build on those regions that this nation has base towns ---//
+
+ if( !regionStat->base_town_nation_count_array[nation_recno-1] )
+ continue;
+
+ if( regionStat->harbor_nation_count_array[nation_recno-1] > 0 ) // if we already have a harbor in this region
+ continue;
+
+ err_when( regionStat->harbor_nation_count_array[nation_recno-1] > 1 ); // this shouldn't happen if the AI works properly
+
+ //-----------------------------------------------------------------------//
+ //
+ // Scan thru all regions which this region can be connected to thru sea.
+ // If one of them is worth our landing, then builld a harbor in this
+ // region so we can sail to that region.
+ //
+ //-----------------------------------------------------------------------//
+
+ regionPath = regionStat->reachable_region_array;
+
+ for( int j=0 ; j<regionStat->reachable_region_count ; j++, regionPath++ )
+ {
+ err_when( regionPath->land_region_stat_id == i+1 ); // pointing back to its own
+
+ //--------------------------------------//
+
+ if( ai_harbor_count == 0 && // if we have already built one harbor, then we should continue to build others asa single harbor isn't useful
+ ai_should_sail_to_rating(regionPath->land_region_stat_id) <= 0 )
+ {
+ continue;
+ }
+
+ //--------- build a harbor now ---------//
+
+ if( ai_build_harbor( regionStat->region_id, regionPath->sea_region_id ) )
+ return 1;
+ }
+ }
+
+ return 0;
+}
+//----- End of function Nation::think_build_harbor_network ----//
+
+
+//----- Begin of function Nation::ai_should_sail_to_rating ----//
+//
+int Nation::ai_should_sail_to_rating(int regionStatId)
+{
+ RegionStat* regionStat = region_array.get_region_stat2(regionStatId);
+
+ int curRating;
+
+ curRating = regionStat->raw_count * 100
+ + regionStat->independent_town_count * 20
+ + regionStat->nation_presence_count * 30;
+/*
+ - (regionStat->total_town_count - regionStat->town_nation_count_array[nation_recno-1] ) * 10 // towns of other nations
+ - (regionStat->total_firm_count - regionStat->firm_nation_count_array[nation_recno-1] ) * 5 // firms of other nations
+ - (regionStat->total_unit_count - regionStat->unit_nation_count_array[nation_recno-1] ) * 2 // units of other nations
+ - regionStat->independent_unit_count * 2; // monsters or rebel units
+*/
+ return curRating > 0;
+}
+//----- End of function Nation::ai_should_sail_to_rating ----//
+
+
+//--------- Begin of function Nation::ai_build_harbor --------//
+//
+// Build a harbor across the given land and sea region id.
+//
+// <int> landRegionId - the land region id.
+// <int> seaRegionId - the sea region id.
+//
+// return: <int> 1 - a suitable location is found and the
+// building action has been queued.
+// 0 - not suitable location is found.
+//
+int Nation::ai_build_harbor(int landRegionId, int seaRegionId)
+{
+ #define ADEQUATE_ENEMY_HARBOR_DISTANCE 10
+
+ //---- randomly pick a base town of this nation ----//
+
+ Town* townPtr;
+ int townSeq = m.random(ai_town_count);
+
+ int i;
+ for( i=0 ; i<ai_town_count ; i++ )
+ {
+ if( ++townSeq >= ai_town_count )
+ townSeq=0;
+
+ townPtr = town_array[ ai_town_array[townSeq] ];
+
+ if( townPtr->is_base_town && landRegionId==townPtr->region_id )
+ break;
+ }
+
+ if( i==ai_town_count ) // not found
+ return 0;
+
+ int homeXLoc = townPtr->center_x;
+ int homeYLoc = townPtr->center_y;
+
+ //---- scan out from the town and find the nearest suitable location to build the harbor ----//
+
+ int xOffset, yOffset;
+ int xLoc, yLoc, bestXLoc= -1, bestYLoc= -1, maxEnemyDistance=0;
+ Location* locPtr;
+
+ for( i=2 ; i<MAX_WORLD_X_LOC*MAX_WORLD_Y_LOC ; i++ )
+ {
+ m.cal_move_around_a_point(i, MAX_WORLD_X_LOC, MAX_WORLD_Y_LOC, xOffset, yOffset);
+
+ xLoc = homeXLoc + xOffset;
+ yLoc = homeYLoc + yOffset;
+
+ xLoc = max(0, xLoc);
+ xLoc = min(MAX_WORLD_X_LOC-1, xLoc);
+
+ yLoc = max(0, yLoc);
+ yLoc = min(MAX_WORLD_Y_LOC-1, yLoc);
+
+ locPtr = world.get_loc(xLoc, yLoc);
+
+ if( !locPtr->can_build_whole_harbor() )
+ continue;
+
+ if( !world.is_adjacent_region(xLoc, yLoc, seaRegionId) )
+ continue;
+
+ if( !world.can_build_firm(xLoc, yLoc, FIRM_HARBOR) )
+ continue;
+
+ //--------------------------------------//
+
+ int enemyDistance = closest_enemy_firm_distance(FIRM_HARBOR, xLoc, yLoc);
+
+ if( enemyDistance > maxEnemyDistance )
+ {
+ maxEnemyDistance = enemyDistance;
+ bestXLoc = xLoc;
+ bestYLoc = yLoc;
+
+ if( enemyDistance >= ADEQUATE_ENEMY_HARBOR_DISTANCE )
+ break;
+ }
+ }
+
+ //--------------------------------//
+
+ if( bestXLoc >= 0 )
+ {
+ add_action(xLoc, yLoc, homeXLoc, homeYLoc, ACTION_AI_BUILD_FIRM, FIRM_HARBOR);
+ return 1;
+ }
+
+ return 0;
+}
+//---------- End of function Nation::ai_build_harbor --------//
+
+
+//--------- Begin of function Nation::closest_enemy_firm_distance --------//
+//
+// Return how close is the cloeset enemy harbor to the given location.
+//
+// <int> firmId - firm id.
+// <int> xLoc, yLoc - the given location
+//
+int Nation::closest_enemy_firm_distance(int firmId, int xLoc, int yLoc)
+{
+ int curDistance, minDistance=0x7FFF;
+
+ for( int i=firm_array.size() ; i>0 ; i-- )
+ {
+ if( firm_array.is_deleted(i) )
+ continue;
+
+ Firm* firmPtr = firm_array[i];
+
+ if( firmPtr->firm_id != firmId ||
+ firmPtr->nation_recno == nation_recno ) // belonging to own nation, not enemy nation
+ {
+ continue;
+ }
+
+ curDistance = m.points_distance(firmPtr->center_x, firmPtr->center_y, xLoc, yLoc);
+
+ if( curDistance < minDistance )
+ minDistance = curDistance;
+ }
+
+ return minDistance;
+}
+//---------- End of function Nation::closest_enemy_firm_distance --------//
+
+
+//------ Begin of function Nation::think_move_between_region ------//
+//
+int Nation::think_move_between_region()
+{
+ if( think_move_people_between_region() )
+ return 1;
+
+ if( think_move_troop_between_region() )
+ return 1;
+
+ return 0;
+}
+//------ End of function Nation::think_move_between_region -------//
+
+
+//------ Begin of function Nation::think_move_troop_between_region ------//
+//
+// Thing about moving units between regions
+//
+int Nation::think_move_troop_between_region()
+{
+ //----- find the region with the least population -----//
+
+ int campCount, maxCampCount=0, minCampCount=0x1000;
+ int maxRegionId=0, minRegionId=0;
+ RegionStat* regionStat = region_array.region_stat_array;
+ int curRating, minRegionRating=0;
+
+ int i;
+ for( i=0 ; i<region_array.region_stat_count ; i++, regionStat++ )
+ {
+ if( regionStat->nation_presence_count==0 &&
+ regionStat->independent_town_count==0 &&
+ regionStat->raw_count==0 )
+ {
+ continue;
+ }
+
+ campCount = regionStat->camp_nation_count_array[nation_recno-1];
+
+ if( campCount > maxCampCount )
+ {
+ maxCampCount = campCount;
+ maxRegionId = regionStat->region_id;
+ }
+
+ if( campCount <= minCampCount )
+ {
+ curRating = ai_should_sail_to_rating(i+1);
+
+ if( campCount < minCampCount || curRating >= minRegionRating )
+ {
+ minCampCount = campCount;
+ minRegionId = regionStat->region_id;
+ minRegionRating = curRating;
+ }
+ }
+ }
+
+ if( !maxRegionId || !minRegionId || maxRegionId==minRegionId )
+ return 0;
+
+ //----- only move if the difference is big enough ------//
+
+ int minJoblessPop = region_array.get_region_stat(minRegionId)->nation_jobless_population_array[nation_recno-1];
+ int maxJoblessPop = region_array.get_region_stat(maxRegionId)->nation_jobless_population_array[nation_recno-1];
+
+ if( pref_use_marine < 90 ) // if > 90, it will ignore all these and move anyway
+ {
+ if( minCampCount==0 )
+ {
+ if( maxJoblessPop - minJoblessPop < 200 - pref_use_marine ) // 150 to 200 (pref_use_marine is always >= 50, if it is < 50, marine functions are not called at all
+ return 0;
+ }
+ else
+ {
+ if( maxJoblessPop - minJoblessPop < 150 - pref_use_marine ) // 100 to 150 (pref_use_marine is always >= 50, if it is < 50, marine functions are not called at all
+ return 0;
+ }
+ }
+ else
+ {
+ if( maxJoblessPop < 20 ) // don't move if we only have a few jobless people
+ return 0;
+ }
+
+ //------------ see if we have any camps in the region -----------//
+
+ int destRegionId = minRegionId;
+ Firm* firmPtr;
+
+ for( i=ai_camp_count-1 ; i>=0 ; i-- )
+ {
+ firmPtr = firm_array[ai_camp_array[i]];
+
+ if( firmPtr->region_id == destRegionId &&
+ !firmPtr->under_construction ) // if it's under construction there may be unit waiting outside of the camp
+ {
+ //--- if there is one, must move the troop close to it ---//
+
+ return ai_patrol_to_region(firmPtr->center_x, firmPtr->center_y, SEA_ACTION_NONE);
+ }
+ }
+ //----- if we don't have any camps in the region, build one ----//
+
+ int xLoc=0, yLoc=0;
+ FirmInfo* firmInfo = firm_res[FIRM_CAMP];
+
+ if(world.locate_space_random(xLoc, yLoc, MAX_WORLD_X_LOC-1,
+ MAX_WORLD_Y_LOC-1, firmInfo->loc_width, firmInfo->loc_height,
+ MAX_WORLD_X_LOC*MAX_WORLD_Y_LOC, destRegionId, 1))
+ {
+ return ai_patrol_to_region(xLoc, yLoc, SEA_ACTION_BUILD_CAMP);
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_move_troop_between_region -------//
+
+
+//------ Begin of function Nation::think_move_people_between_region ------//
+//
+// Thing about moving units between regions
+//
+int Nation::think_move_people_between_region()
+{
+ //----- find the region with the least population -----//
+
+ int joblessPop, maxJoblessPop=0, minJoblessPop=0x1000;
+ int maxRegionId=0, minRegionId=0;
+ RegionStat* regionStat = region_array.region_stat_array;
+
+ int i;
+ for( i=0 ; i<region_array.region_stat_count ; i++, regionStat++ )
+ {
+ //--- only move to regions in which we have camps ---//
+
+ if( regionStat->camp_nation_count_array[nation_recno-1] == 0 )
+ continue;
+
+ joblessPop = regionStat->nation_jobless_population_array[nation_recno-1];
+
+ if( joblessPop > maxJoblessPop )
+ {
+ maxJoblessPop = joblessPop;
+ maxRegionId = regionStat->region_id;
+ }
+
+ if( joblessPop < minJoblessPop )
+ {
+ minJoblessPop = joblessPop;
+ minRegionId = regionStat->region_id;
+ }
+ }
+
+ if( !maxRegionId || !minRegionId || maxRegionId==minRegionId )
+ return 0;
+
+ //----- only move if the difference is big enough ------//
+
+ if( pref_use_marine < 90 ) // if > 90, it will ignore all these and move anyway
+ {
+ if( maxJoblessPop - minJoblessPop < 150 - pref_use_marine ) // 100 to 150 (pref_use_marine is always >= 50, if it is < 50, marine functions are not called at all
+ return 0;
+ }
+ else
+ {
+ if( maxJoblessPop < 20 ) // don't move if we only have a few jobless people
+ return 0;
+ }
+
+ //------------ see if we have any towns in the region -----------//
+
+ int destRegionId = minRegionId;
+ Town* townPtr;
+
+ for( i=ai_town_count-1 ; i>=0 ; i-- )
+ {
+ townPtr = town_array[ai_town_array[i]];
+
+ if( townPtr->region_id == destRegionId )
+ {
+ //--- if there is one, must move the people to it ---//
+
+ return ai_settle_to_region(townPtr->center_x, townPtr->center_y, SEA_ACTION_NONE);
+ }
+ }
+
+ //----- if we don't have any towns in the region, settle one ----//
+
+ int xLoc=0, yLoc=0;
+
+ if(world.locate_space_random(xLoc, yLoc, MAX_WORLD_X_LOC-1,
+ MAX_WORLD_Y_LOC-1, STD_TOWN_LOC_WIDTH, STD_TOWN_LOC_HEIGHT,
+ MAX_WORLD_X_LOC*MAX_WORLD_Y_LOC, destRegionId, 1))
+ {
+ return ai_settle_to_region(xLoc, yLoc, SEA_ACTION_SETTLE);
+ }
+
+ return 0;
+}
+//------ End of function Nation::think_move_people_between_region -------//
+
+
+//------ Begin of function Nation::ai_is_sea_travel_safe ------//
+//
+// return: <int> 1 - it's safe for sea travel
+// 0 - it's not safe for sea travel
+//
+int Nation::ai_is_sea_travel_safe()
+{
+ //--- count the no. of battle ships owned by each nation ---//
+
+ Unit* unitPtr;
+ short nationShipCountArray[MAX_NATION];
+
+ memset( nationShipCountArray, 0, sizeof(nationShipCountArray) );
+
+ int i;
+ for( i=unit_array.size() ; i>0 ; i-- )
+ {
+ if( unit_array.is_deleted(i) )
+ continue;
+
+ unitPtr = unit_array[i];
+
+ if( unitPtr->unit_id != UNIT_CARAVEL &&
+ unitPtr->unit_id != UNIT_GALLEON )
+ {
+ continue;
+ }
+
+ err_when( unitPtr->nation_recno < 1 || unitPtr->nation_recno > MAX_NATION );
+
+ nationShipCountArray[unitPtr->nation_recno-1]++;
+ }
+
+ //--- compare the no. of ships of ours and those of the human players ---//
+
+ int ourBattleShipCount = nationShipCountArray[nation_recno-1];
+ int nationRecno = m.random(nation_array.size())+1;
+
+ for( i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( ++nationRecno > nation_array.size() )
+ nationRecno = 1;
+
+ if( nation_array.is_deleted(nationRecno) )
+ continue;
+
+ if( get_relation(nationRecno)->status != NATION_HOSTILE ) // only check enemies
+ continue;
+
+ if( nation_array[nationRecno]->is_ai() ) // only check human players
+ continue;
+
+ //-- if enemy has battle ships, it is not safe for sea travel, destroy them first ---//
+
+ if( nationShipCountArray[nationRecno-1] > 0 )
+ {
+ //--- if enemy ships significantly outnumber ours, don't do any sea travel ---//
+
+ if( nationShipCountArray[nationRecno-1] - ourBattleShipCount >
+ pref_military_courage/3 ) // 0 to 3
+ {
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+//----- End of function Nation::ai_is_sea_travel_safe -----//
+
+
+//------ Begin of function Nation::max_human_battle_ship_count ------//
+//
+// return: <int> the number of ships owned by the human player who
+// is strongest on sea power.
+//
+int Nation::max_human_battle_ship_count()
+{
+ //--- count the no. of battle ships owned by each nation ---//
+
+ Unit* unitPtr;
+ short nationShipCountArray[MAX_NATION];
+
+ memset( nationShipCountArray, 0, sizeof(nationShipCountArray) );
+
+ int i;
+ for( i=unit_array.size() ; i>0 ; i-- )
+ {
+ if( unit_array.is_deleted(i) )
+ continue;
+
+ unitPtr = unit_array[i];
+
+ if( unitPtr->unit_id != UNIT_CARAVEL &&
+ unitPtr->unit_id != UNIT_GALLEON )
+ {
+ continue;
+ }
+
+ err_when( unitPtr->nation_recno < 1 || unitPtr->nation_recno > MAX_NATION );
+
+ nationShipCountArray[unitPtr->nation_recno-1]++;
+ }
+
+ //--- compare the no. of ships of ours and those of the human players ---//
+
+ int maxShipCount=0;
+
+ for( i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) )
+ continue;
+
+ if( nation_array[i]->is_ai() ) // only check human players
+ continue;
+
+ //-- if enemy has battle ships, it is not safe for sea travel, destroy them first ---//
+
+ if( nationShipCountArray[i-1] > maxShipCount )
+ {
+ maxShipCount = nationShipCountArray[i-1];
+ }
+ }
+
+ return maxShipCount;
+}
+//----- End of function Nation::max_human_battle_ship_count -----//
+
+
+//------ Begin of function Nation::think_sea_attack_enemy ------//
+//
+// Think about attacking enemy harbors and ships.
+//
+int Nation::think_sea_attack_enemy()
+{
+ if( total_ship_combat_level < 700 - (pref_military_courage + pref_use_marine)*2 ) // 300 to 700
+ return 0;
+
+ //-----------------------------------------//
+
+ int totalFirm = firm_array.size();
+ int firmRecno = m.random(totalFirm)+1;
+ Firm* firmPtr;
+
+ for( int i=0 ; i<totalFirm ; i++ )
+ {
+ if( ++firmRecno > totalFirm )
+ firmRecno = 1;
+
+ if( firm_array.is_deleted(firmRecno) )
+ continue;
+
+ firmPtr = firm_array[firmRecno];
+
+ if( firmPtr->firm_id != FIRM_HARBOR )
+ continue;
+
+ if( get_relation_status(firmPtr->nation_recno) != NATION_HOSTILE )
+ continue;
+
+ //--- if the AI has more powerful fleets than the enemy ---//
+
+ if( total_ship_combat_level >
+ nation_array[firmPtr->nation_recno]->total_ship_combat_level )
+ {
+ ai_sea_attack_target(firmPtr->center_x, firmPtr->center_y);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+//----- End of function Nation::think_sea_attack_enemy -----//
diff --git a/OAI_MILI.cpp b/OAI_MILI.cpp
new file mode 100644
index 0000000..13473e0
--- /dev/null
+++ b/OAI_MILI.cpp
@@ -0,0 +1,188 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_MILI.CPP
+//Description: AI - think about expanding the military force
+
+#include <ALL.h>
+#include <OTOWN.h>
+#include <OF_CAMP.h>
+#include <ONATION.h>
+
+//--------- Begin of function Nation::think_military --------//
+
+void Nation::think_military()
+{
+ //---- don't build new camp if we our food consumption > production ----//
+
+ if( yearly_food_change() < 0 )
+ {
+ think_close_camp(); // think about closing down an existing one
+ return;
+ }
+
+ //--- think about whether it should expand now ---//
+
+ if( !ai_should_expand_military() && !ai_is_troop_need_new_camp() )
+ return;
+
+ //----- think about where to expand -----//
+
+ int bestRating=0, curRating;
+ Town *townPtr, *bestTownPtr=NULL;
+
+ for( int i=ai_town_count-1 ; i>=0 ; i-- )
+ {
+ townPtr = town_array[ ai_town_array[i] ];
+
+ if( !townPtr->is_base_town ) // only expand on base towns
+ continue;
+
+ if( townPtr->no_neighbor_space ) // if there is no space in the neighbor area for building a new firm.
+ continue;
+
+ curRating = townPtr->population; //**BUGHERE, to be modified.
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestTownPtr = townPtr;
+ }
+ }
+
+ if( !bestTownPtr )
+ return;
+
+ //--------- queue building the camp now ---------//
+
+ short buildXLoc, buildYLoc;
+
+ if( !find_best_firm_loc(FIRM_CAMP, bestTownPtr->loc_x1, bestTownPtr->loc_y1, buildXLoc, buildYLoc) )
+ {
+ bestTownPtr->no_neighbor_space = 1;
+ return;
+ }
+
+ add_action(buildXLoc, buildYLoc, bestTownPtr->loc_x1, bestTownPtr->loc_y1, ACTION_AI_BUILD_FIRM, FIRM_CAMP);
+}
+
+//--------- Begin of function Nation::think_military --------//
+
+
+//--------- Begin of function Nation::ai_should_expand_military --------//
+
+int Nation::ai_should_expand_military()
+{
+ //----- don't expand if it is losing money -----//
+
+ if( true_profit_365days() < 0 )
+ return 0;
+
+ //----- expand if it has enough cash -----//
+
+ if( !ai_should_spend(pref_military_development/2) )
+ return 0;
+
+ //--- check whether the current military force is already big enough ---//
+
+ if( ai_camp_count * 9 > total_jobless_population * (50+pref_military_development) / 150 ) // (50 to 150) / 150
+ return 0;
+
+ //---- see if any of the camps are still needed soldiers -----//
+
+ int i, soldierCount, freeSpaceCount=0;
+ FirmCamp* firmCamp;
+
+ for( i=ai_camp_count-1 ; i>=0 ; i-- )
+ {
+ firmCamp = (FirmCamp*) firm_array[ ai_camp_array[i] ];
+
+ if( firmCamp->should_close_flag ) // exclude those going to be closed down
+ continue;
+
+ //---- only build a new one when existing ones are all full ----//
+
+ soldierCount = (firmCamp->overseer_recno>0) + firmCamp->worker_count +
+ firmCamp->patrol_unit_count;
+
+ freeSpaceCount += 9-soldierCount;
+
+ if( firmCamp->ai_recruiting_soldier )
+ return 0;
+ }
+
+ return freeSpaceCount < 9; // build build new ones when the existing ones are almost full
+}
+//--------- Begin of function Nation::ai_should_expand_military --------//
+
+
+//--------- Begin of function Nation::ai_is_troop_need_new_camp --------//
+
+int Nation::ai_is_troop_need_new_camp()
+{
+ //--- check whether the current military force is already big enough ---//
+
+ if( ai_has_too_many_camp() )
+ return 0;
+
+ //----- expand if it has enough cash -----//
+
+ if( !ai_should_spend(50+pref_military_development/2) )
+ return 0;
+
+ //----- if existing camps can already host all units ----//
+
+ int neededSoldierSpace = total_human_count+total_weapon_count - ai_camp_count*MAX_WORKER;
+ int neededGeneralSpace = total_general_count - ai_camp_count;
+
+ return neededSoldierSpace >= 9 + 20 * (100-pref_military_development) / 200 &&
+ neededGeneralSpace >= 2 + 4 * (100-pref_military_development) / 200;
+}
+//--------- Begin of function Nation::ai_is_troop_need_new_camp --------//
+
+
+//--------- Begin of function Nation::ai_has_too_many_camp --------//
+
+int Nation::ai_has_too_many_camp()
+{
+ return ai_camp_count * 9 > total_jobless_population * (150+pref_military_development) / 150; // (150 to 250) / 150
+}
+//--------- Begin of function Nation::ai_has_too_many_camp --------//
+
+
+//--------- Begin of function Nation::think_close_camp --------//
+
+int Nation::think_close_camp()
+{
+ return 0;
+
+/*
+ FirmCamp *firmCamp, *closeCamp=NULL;
+ int curRating, bestRating=0;
+
+ for( int i=ai_camp_count-1 ; i>=0 ; i-- )
+ {
+ firmCamp = (FirmCamp*) firm_array[ ai_camp_array[i] ];
+
+ curRating = world.distance_rating(firmCamp->center_x, firmCamp->center_y
+*/
+}
+//--------- Begin of function Nation::think_close_camp --------//
+
diff --git a/OAI_MONS.cpp b/OAI_MONS.cpp
new file mode 100644
index 0000000..9f6db95
--- /dev/null
+++ b/OAI_MONS.cpp
@@ -0,0 +1,141 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_MONS.CPP
+//Description: AI functions for dealing with the monsters.
+
+#include <OF_MONS.h>
+#include <ONATION.h>
+#include <OCONFIG.h>
+#include <OMONSRES.h>
+
+//--------- Begin of function Nation::think_attack_monster --------//
+
+int Nation::think_attack_monster()
+{
+ if( config.monster_type == OPTION_MONSTER_NONE ) // no monsters in the game
+ return 0;
+
+ //--- if the AI has run out of money and is currently cheating, it will have a urgent need to attack monsters to get money ---//
+
+ int useAllCamp = income_365days(INCOME_CHEAT) > 0;
+
+ if( !useAllCamp ) // use all camps to attack the monster
+ {
+ if( !is_at_war() )
+ {
+ if( cash < 500 && military_rank_rating() >= 75-pref_attack_monster/4 ) // 50 to 75
+ useAllCamp = 1 ;
+ }
+ }
+
+ if( !useAllCamp )
+ {
+ if( military_rank_rating() < 50-pref_attack_monster/4 ) // don't attack if the military strength is too low, 25 to 50
+ return 0;
+ }
+
+ //------- select a monster target ---------//
+
+ int targetCombatLevel;
+
+ int targetFirmRecno = think_monster_target(targetCombatLevel);
+
+ if( !targetFirmRecno )
+ return 0;
+
+ targetCombatLevel = targetCombatLevel * 150 / 100; // X 150%
+
+ //--- we need to use veteran soldiers to attack powerful monsters ---//
+
+ FirmMonster* targetFirm = (FirmMonster*) firm_array[targetFirmRecno];
+
+ int monsterLevel = monster_res[targetFirm->monster_id]->level;
+
+ int attackerMinCombatLevel = 0;
+
+ if( targetCombatLevel > 100 ) // if the nation's cash runs very low, it will attack anyway
+ attackerMinCombatLevel = 20 + monsterLevel*3;
+
+ //--- call ai_attack_target() to attack the target town ---//
+
+ return ai_attack_target(targetFirm->loc_x1, targetFirm->loc_y1, targetCombatLevel, 0, 0, attackerMinCombatLevel, 0, useAllCamp );
+}
+//---------- End of function Nation::think_attack_monster --------//
+
+
+//--------- Begin of function Nation::think_monster_target --------//
+//
+// Think about the best monster target in the given region.
+//
+// <int&> targetCombatLevel - var for returning the total combat level of the target firm.
+//
+int Nation::think_monster_target(int& targetCombatLevel)
+{
+ if( !largest_town_recno )
+ return 0;
+
+ Town* largestTown = town_array[largest_town_recno];
+ Firm* firmPtr;
+ int combatLevel;
+ int curRating, bestRating= -10000, bestFirmRecno=0, hasWar;
+
+ for( int firmRecno=firm_array.size() ; firmRecno>0 ; firmRecno-- )
+ {
+ if( firm_array.is_deleted(firmRecno) )
+ continue;
+
+ firmPtr = firm_array[firmRecno];
+
+ if( firmPtr->firm_id != FIRM_MONSTER ||
+ firmPtr->region_id != largestTown->region_id )
+ {
+ continue;
+ }
+
+ //----- take into account of the mobile units around this town -----//
+
+ int mobileCombatLevel = mobile_defense_combat_level(firmPtr->center_x, firmPtr->center_y, firmPtr->nation_recno, 1, hasWar);
+
+ if( mobileCombatLevel == -1 ) // do not attack this town because a battle is already going on
+ continue;
+
+ curRating = 3 * m.points_distance( largestTown->center_x, largestTown->center_y,
+ firmPtr->center_x, firmPtr->center_y );
+
+ combatLevel = mobileCombatLevel +
+ ((FirmMonster*)firmPtr)->total_combat_level();
+
+ curRating -= combatLevel;
+
+ //---------------------------------//
+
+ if( curRating > bestRating )
+ {
+ targetCombatLevel = combatLevel;
+ bestRating = curRating;
+ bestFirmRecno = firmRecno;
+ }
+ }
+
+ return bestFirmRecno;
+}
+//---------- End of function Nation::think_monster_target --------//
+
diff --git a/OAI_QUER.cpp b/OAI_QUER.cpp
new file mode 100644
index 0000000..329629e
--- /dev/null
+++ b/OAI_QUER.cpp
@@ -0,0 +1,108 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_QUER.CPP
+//Description: AI - query functions
+
+#include <ALL.h>
+#include <OTOWN.h>
+#include <OFIRM.h>
+#include <ONATION.h>
+
+
+//--------- Begin of function Nation::check_firm_ready --------//
+//
+// <short> xLoc, yLoc - locatino of the firm.
+// [int] firmId - id. of the firm. If not given, don't
+// verify the firm id. (default: 0)
+//
+// return 1 means firm exists and belongs to our nation
+// return 0 otherwise
+//
+int Nation::check_firm_ready(short xLoc, short yLoc, int firmId)
+{
+ Location *locPtr = world.get_loc(xLoc, yLoc);
+
+ if(!locPtr->is_firm())
+ return 0; // no firm there
+
+ short firmRecno = locPtr->firm_recno();
+
+ if(firm_array.is_deleted(firmRecno))
+ return 0; // firm deleted
+
+ Firm *firmPtr = firm_array[firmRecno];
+
+ if(firmPtr->nation_recno!=nation_recno)
+ return 0; // firm changed nation
+
+ if( firmId && firmPtr->firm_id!=firmId )
+ return 0;
+
+ return 1;
+}
+//---------- End of function Nation::check_firm_ready --------//
+
+
+//--------- Begin of function Nation::check_town_ready --------//
+//
+// return 1 means town exists and belongs to our nation
+// return 0 otherwise
+//
+int Nation::check_town_ready(short xLoc, short yLoc)
+{
+ Location *locPtr = world.get_loc(xLoc, yLoc);
+
+ if(!locPtr->is_town())
+ return 0; // no town there
+
+ short townRecno = locPtr->town_recno();
+
+ if(town_array.is_deleted(townRecno))
+ return 0; // town deleted
+
+ Town *townPtr = town_array[townRecno];
+
+ if(townPtr->nation_recno!=nation_recno)
+ return 0; // town changed nation
+
+ return 1;
+}
+//---------- End of function Nation::check_town_ready --------//
+
+
+//--------- Begin of function Nation::can_ai_build --------//
+//
+// Whether the AI can build the specific firm type next to
+// the current firm.
+//
+int Nation::can_ai_build(int firmId)
+{
+ //------- check whether the AI has enough cash -----//
+
+ if( cash < firm_res[firmId]->setup_cost )
+ return 0;
+
+ return 1;
+}
+//--------- End of function Nation::can_ai_build --------//
+
+
+
diff --git a/OAI_SEEK.cpp b/OAI_SEEK.cpp
new file mode 100644
index 0000000..f8068c6
--- /dev/null
+++ b/OAI_SEEK.cpp
@@ -0,0 +1,755 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_SEEK.CPP
+//Description: AI - action progressing functions
+
+#include <ALL.h>
+#include <OUNIT.h>
+#include <OF_MINE.h>
+#include <OF_FACT.h>
+#include <OF_HARB.h>
+#include <OTOWN.h>
+#include <OSITE.h>
+#include <ONATION.h>
+
+//--------- Begin of function Nation::seek_mine --------//
+//
+// <short&> xLoc, yLoc - reference vars for returning the building
+// location.
+// <short&> refXLoc, refYLoc - reference vars for returning the exact
+// location of the raw material.
+//
+// return: <int> >0 the raw id. of the site.
+// ==0 no suitable site found.
+//
+int Nation::seek_mine(short& xLoc, short& yLoc, short& refXLoc, short& refYLoc)
+{
+ err_when( site_array.untapped_raw_count < 0 );
+
+ if( site_array.untapped_raw_count==0 )
+ return 0;
+
+ int raw_kind_mined[MAX_RAW], i;
+ Firm *firmPtr;
+ FirmMine *minePtr;
+
+ //-----------------------------------------------------------------//
+ // count each kind of raw material that is being mined
+ //-----------------------------------------------------------------//
+
+ memset(raw_kind_mined, 0, sizeof(int)*MAX_RAW);
+
+ for(i=0; i<ai_mine_count; i++)
+ {
+ firmPtr = firm_array[ai_mine_array[i]];
+ minePtr = firmPtr->cast_to_FirmMine();
+
+ if( minePtr->raw_id>=1 && minePtr->raw_id<=MAX_RAW )
+ raw_kind_mined[minePtr->raw_id-1]++;
+ }
+
+ //-------------------- define parameters ----------------------//
+
+ FirmInfo *firmInfo = firm_res[FIRM_MINE];
+ Location *locPtr, *siteLocPtr;
+ Site *sitePtr;
+ Town *townPtr;
+ int nearSite[MAX_RAW], minDist[MAX_RAW], townWithMine[MAX_RAW];
+ short buildXLoc[MAX_RAW], buildYLoc[MAX_RAW];
+ short dist;
+ int canBuild, connected, allHave;
+ int j, k, siteId;
+
+ memset(townWithMine, 0, sizeof(int)*MAX_RAW);
+ memset(nearSite, 0, sizeof(int)*MAX_RAW);
+ memset(buildXLoc, 0, sizeof(short)*MAX_RAW);
+ memset(buildYLoc, 0, sizeof(short)*MAX_RAW);
+
+ for(i=0; i<MAX_RAW; i++)
+ minDist[i] = 0x7FFFFF;
+
+ //--------------------------------------------//
+ // scan for the site array
+ //--------------------------------------------//
+ for(i=site_array.size(); i>0; i--)
+ {
+ if(site_array.is_deleted(i))
+ continue;
+
+ sitePtr = site_array[i];
+
+ if( sitePtr->site_type != SITE_RAW )
+ continue;
+
+ siteLocPtr = world.get_loc(sitePtr->map_x_loc, sitePtr->map_y_loc);
+
+ if(!siteLocPtr->can_build_firm())
+ continue;
+
+ siteId = sitePtr->object_id - 1;
+ err_when(siteId<0 || siteId>MAX_RAW);
+
+ if(townWithMine[siteId])
+ continue; // a site connected to town is found before
+
+ //--------------------------------------------//
+ // continue if action to this site already exist
+ //--------------------------------------------//
+
+ if(is_action_exist(-1, -1, sitePtr->map_x_loc, sitePtr->map_y_loc, ACTION_AI_BUILD_FIRM, FIRM_MINE))
+ continue;
+
+ for(j=0; j<ai_town_count; j++)
+ {
+ townPtr = town_array[ai_town_array[j]];
+ locPtr = world.get_loc(townPtr->loc_x1, townPtr->loc_y1);
+
+ //-********* codes to move to other territory ***********-//
+ if(siteLocPtr->region_id!=locPtr->region_id)
+ continue; // not on the same territory
+
+ dist = m.points_distance(sitePtr->map_x_loc, sitePtr->map_y_loc, townPtr->center_x, townPtr->center_y);
+
+ //-------------------------------------------------------------------------//
+ // check whether a mine is already connected to this town, if so, use it
+ //-------------------------------------------------------------------------//
+ for(connected=0, k=townPtr->linked_firm_count-1; k>=0; k--)
+ {
+ err_when(!townPtr->linked_firm_array[k] || firm_array.is_deleted(townPtr->linked_firm_array[k]));
+ firmPtr = firm_array[townPtr->linked_firm_array[k]];
+
+ if(firmPtr->nation_recno==nation_recno && firmPtr->firm_id==FIRM_MINE)
+ {
+ connected++;
+ break;
+ }
+ }
+
+ //-------------------------------------------------------------------------//
+ // calculate the minimum distance from own towns
+ //-------------------------------------------------------------------------//
+ if(dist<minDist[siteId] || (connected && dist<=EFFECTIVE_FIRM_TOWN_DISTANCE))
+ {
+ //------ can build or not ----------//
+ canBuild = 0;
+
+ for(int ix=sitePtr->map_x_loc-firmInfo->loc_width+1; ix<=sitePtr->map_x_loc && !canBuild; ix++)
+ {
+ if(ix<0 || ix>=MAX_WORLD_X_LOC)
+ continue;
+
+ for(int iy=sitePtr->map_y_loc-firmInfo->loc_height+1; iy<=sitePtr->map_y_loc && !canBuild; iy++)
+ {
+ if(iy<0 || iy>=MAX_WORLD_Y_LOC)
+ continue;
+
+ if(world.can_build_firm(ix, iy, FIRM_MINE))
+ {
+ canBuild++;
+ buildXLoc[siteId] = ix;
+ buildYLoc[siteId] = iy;
+ break;
+ }
+ }
+ }
+
+ if(canBuild)
+ {
+ nearSite[siteId] = i;
+ minDist[siteId] = dist;
+
+ if(connected && dist<=EFFECTIVE_FIRM_TOWN_DISTANCE)
+ townWithMine[siteId]++;
+ }
+ }
+ }
+
+ for(allHave=1, j=0; j<MAX_RAW; j++)
+ {
+ if(!townWithMine[j])//if(!nearSite[j])
+ {
+ allHave = 0;
+ break;
+ }
+ }
+
+ if(allHave)
+ break; // sites of each raw material have been found
+ }
+
+ //---------------------------------------------------------------------------//
+ // determine which raw material is the best choice to build
+ // Note: a better sorting algorithm should be used if there are many kind of
+ // raw material
+ //---------------------------------------------------------------------------//
+ int weight, pos; // weight is the such kind of material mined, pos is the position in the array
+ int siteRecno = 0; // siteRecno is the recno of site to build
+ int withoutThisRaw = 0; // withoutThisRaw shows that this raw material is strongly recommended
+ int closestDist=0x7FFFFF;
+
+ for(pos=-1, weight=0x7FFFFF, j=0 ;j<MAX_RAW; j++)
+ {
+ if(!nearSite[j])
+ continue; // no such kind of raw material
+
+ if(!raw_kind_mined[j]) // no such kind of material and there is a possible site
+ {
+ if(withoutThisRaw)
+ {
+ if(minDist[j]<closestDist) // more than one kind of material we don't have
+ {
+ siteRecno = nearSite[j];
+ closestDist = minDist[j];
+ pos = j;
+ }
+ }
+ else
+ {
+ siteRecno = nearSite[j];
+ closestDist = minDist[j];
+ withoutThisRaw++;
+ pos = j;
+ }
+ }
+ else if(!withoutThisRaw && weight>raw_kind_mined[j]) // scan for the kind of material with least num of this site
+ {
+ weight = raw_kind_mined[j];
+ pos = j;
+ }
+ }
+
+ if(!siteRecno && pos>=0)
+ siteRecno = nearSite[pos];
+
+ if(siteRecno)
+ {
+ sitePtr = site_array[siteRecno];
+ xLoc = buildXLoc[pos];
+ yLoc = buildYLoc[pos];
+ refXLoc = sitePtr->map_x_loc;
+ refYLoc = sitePtr->map_y_loc;
+
+ err_when((xLoc-refXLoc)>=firm_res[FIRM_MINE]->loc_width || (yLoc-refYLoc)>=firm_res[FIRM_MINE]->loc_height);
+ //--------------------------------------------------------------//
+ // do some adjustment such that the firm will be built far away
+ // from other firms by at least one step.
+ //--------------------------------------------------------------//
+ seek_best_build_mine_location(xLoc, yLoc, sitePtr->map_x_loc, sitePtr->map_y_loc);
+
+ err_when((xLoc-refXLoc)>=firm_res[FIRM_MINE]->loc_width || (yLoc-refYLoc)>=firm_res[FIRM_MINE]->loc_height);
+ return sitePtr->object_id; // the raw id.
+ }
+
+ return 0;
+}
+//---------- End of function Nation::seek_mine --------//
+
+
+//----- Begin of function Nation::seek_best_build_mine_location -----//
+//
+// <short&> xLoc - a possible x location to build the mine, the final location is also returned by this reference
+// <short&> yLoc - a possible y location to build the mine.
+//
+// <short> mapXLoc - the x location of the raw material
+// <short> mapYLoc - the y location of the raw material
+//
+void Nation::seek_best_build_mine_location(short& xLoc, short& yLoc, short mapXLoc, short mapYLoc)
+{
+ //--------------- define parameters -----------------//
+
+ FirmInfo *firmInfo = firm_res[FIRM_MINE];
+ int weight = 0, maxWeight = 0;
+ short xLeftLimit = mapXLoc-firmInfo->loc_width+1;
+ short yLeftLimit = mapYLoc-firmInfo->loc_height+1;
+ short resultXLoc = xLoc;
+ short resultYLoc = yLoc;
+ short ix, iy;
+
+ for(ix=xLeftLimit; ix<=mapXLoc; ix++)
+ {
+ if(ix<0 || ix>=MAX_WORLD_X_LOC)
+ continue;
+
+ for(iy=yLeftLimit; iy<=mapYLoc; iy++)
+ {
+ if(iy<0 || iy>=MAX_WORLD_Y_LOC)
+ continue;
+
+ //---------------------------------------------------------------//
+ // remove previous checked and useless locaton
+ // Since all the possible location is checked from the top left
+ // to the bottom right, the previous checked location should all
+ // be impossible to build the mine.
+ //---------------------------------------------------------------//
+ if(ix<xLoc && iy<yLoc)
+ continue;
+
+ if(world.can_build_firm(ix, iy, FIRM_MINE))
+ {
+ //----------------------------------------//
+ // calculate weight
+ //----------------------------------------//
+ weight = 0;
+ cal_location_score(ix, iy, firmInfo->loc_width, firmInfo->loc_height, weight);
+
+ if(weight>maxWeight)
+ {
+ resultXLoc = ix;
+ resultYLoc = iy;
+
+ if(weight == MAX_SCORE) // very good locaton, stop checking
+ break;
+
+ maxWeight = weight;
+ }
+ }
+ }
+ }
+
+ xLoc = resultXLoc;
+ yLoc = resultYLoc;
+}
+//---------- End of function Nation::seek_best_build_mine_location --------//
+
+
+//--------- Begin of function Nation::cal_location_score --------//
+// Called by seek_best_build_mine_location() to calculate a specifed
+// location score to build the mine.
+//
+// <short> x1 - the upper left corner x location of the firm
+// <short> y1 - the upper left corner y location of the firm
+// <short> width - the width of the firm
+// <short> height - the height of the firm
+// <short&> score - return the location score
+//
+void Nation::cal_location_score(short x1, short y1, short width, short height, int& score)
+{
+ //-----------------------------------------------------------------//
+ // the score is calculated as follows, for instance, the firm is
+ // 2x2 in dimension
+ //
+ // LU U U RU L--left, R--right, U--upper, O--lower
+ // L x x R
+ // L x x R
+ // LO O O RO
+ //
+ // if any L can build, score += 1, if all set of L can build the total
+ // score of this edge is 100. For each corner, the score is 50 if
+ // can build. Thus, the max. score == 600
+ //
+ //-----------------------------------------------------------------//
+ short x, y, i, count;
+ score = 0;
+
+ //---------- left edge ---------//
+ if((x=x1-1)>=0)
+ {
+ for(i=0, count=0; i<height; i++)
+ {
+ if(world.get_loc(x, y1+i)->can_build_firm())
+ count++;
+ }
+
+ score += (count==height) ? 100 : count;
+ }
+
+ //---------- upper edge ---------//
+ if((y=y1-1)>=0)
+ {
+ for(i=0, count=0; i<width; i++)
+ {
+ if(world.get_loc(x1+i, y)->can_build_firm())
+ count++;
+ }
+
+ score += (count==width) ? 100 : count;
+ }
+
+ //---------- right edge ---------//
+ if((x=x1+width)<MAX_WORLD_X_LOC)
+ {
+ for(i=0, count=0; i<height; i++)
+ {
+ if(world.get_loc(x, y1+i)->can_build_firm())
+ count++;
+ }
+
+ score += (count==height) ? 100 : count;
+ }
+
+ //----------- lower edge -----------//
+
+ if((y=y1+height)<MAX_WORLD_Y_LOC)
+ {
+ for(i=0, count=0; i<width; i++)
+ {
+ if(world.get_loc(x1+i, y)->can_build_firm())
+ count++;
+ }
+
+ score += (count==width) ? 100 : count;
+ }
+
+ //------------------------------------------//
+ // extra score
+ //------------------------------------------//
+
+ //------- upper left corner -------//
+ if(x1>0 && y1>0 && world.get_loc(x1-1, y1-1)->can_build_firm())
+ score += 50;
+
+ //------- upper right corner ---------//
+ if(x1<MAX_WORLD_X_LOC-1 && y1>0 && world.get_loc(x1+1, y1-1)->can_build_firm())
+ score += 50;
+
+ //----------- lower left corner ----------//
+ if(x1>0 && y1<MAX_WORLD_Y_LOC-1 && world.get_loc(x1-1, y1+1)->can_build_firm())
+ score += 50;
+
+ //------- lower right corner ---------//
+ if(x1<MAX_WORLD_X_LOC-1 && y1<MAX_WORLD_Y_LOC-1 && world.get_loc(x1+1, y1+1)->can_build_firm())
+ score += 50;
+}
+//---------- End of function Nation::cal_location_score --------//
+
+
+//----------- Begin of function Nation::find_best_firm_loc ----------//
+//
+// Determine the location of a new firm. It's best to have the
+// new firm within the refective range of: towns, factories and
+// mines.
+//
+// <short> buildFirmId - id. of the firm to be built
+// <short> refXLoc, refYLoc - either the location of a town or a firm,
+// the market must be built next to it.
+// <short&> resultXLoc, resultYLoc - result location of the firm.
+//
+// return: <int> 1 - succeed, 0 - fail
+//
+int Nation::find_best_firm_loc(short buildFirmId, short refXLoc, short refYLoc, short& resultXLoc, short& resultYLoc)
+{
+ Location *locPtr = world.get_loc(refXLoc, refYLoc);
+ short centerX, centerY, refX1, refY1, refX2, refY2;
+
+ //-------- get the refective area ---------//
+
+ int originFirmRecno=0, originTownRecno=0;
+ Firm* firmPtr;
+ Town* townPtr;
+
+ BYTE buildRegionId = locPtr->region_id;
+ int buildIsPlateau = locPtr->is_plateau();
+
+ if( locPtr->is_firm() )
+ {
+ originFirmRecno = locPtr->firm_recno();
+
+ firmPtr = firm_array[originFirmRecno];
+
+ centerX = firmPtr->center_x;
+ centerY = firmPtr->center_y;
+
+ refX1 = centerX - EFFECTIVE_FIRM_FIRM_DISTANCE;
+ refY1 = centerY - EFFECTIVE_FIRM_FIRM_DISTANCE;
+ refX2 = centerX + EFFECTIVE_FIRM_FIRM_DISTANCE;
+ refY2 = centerY + EFFECTIVE_FIRM_FIRM_DISTANCE;
+
+ if( firmPtr->firm_id == FIRM_HARBOR )
+ {
+ buildRegionId = ((FirmHarbor*)firmPtr)->land_region_id;
+ buildIsPlateau = 0;
+ }
+ }
+ else if( locPtr->is_town() )
+ {
+ originTownRecno = locPtr->town_recno();
+
+ townPtr = town_array[originTownRecno];
+
+ centerX = townPtr->center_x;
+ centerY = townPtr->center_y;
+
+ refX1 = centerX - EFFECTIVE_FIRM_TOWN_DISTANCE;
+ refY1 = centerY - EFFECTIVE_FIRM_TOWN_DISTANCE;
+ refX2 = centerX + EFFECTIVE_FIRM_TOWN_DISTANCE;
+ refY2 = centerY + EFFECTIVE_FIRM_TOWN_DISTANCE;
+ }
+ else
+ err_here();
+
+ //------------------------------------------------------//
+
+ FirmInfo* firmInfo = firm_res[buildFirmId];
+ int firmLocWidth = firmInfo->loc_width;
+ int firmLocHeight = firmInfo->loc_height;
+
+ refX1 -= firmLocWidth/2; // since we use loc_x1 as the building reference, we need to shift it so it will match the use of center_x in effective distance
+ refY1 -= firmLocHeight/2;
+ refX1 = max(0, refX1);
+ refY1 = max(0, refY1);
+
+ if( refX2 - firmLocWidth/2 >= MAX_WORLD_X_LOC )
+ refX2 = MAX_WORLD_X_LOC-1;
+ else
+ refX2 -= firmLocWidth/2;
+
+ if( refY2 - firmLocHeight/2 >= MAX_WORLD_Y_LOC )
+ refY2 = MAX_WORLD_Y_LOC-1;
+ else
+ refY2 -= firmLocHeight/2;
+
+ err_when( refX2 >= MAX_WORLD_X_LOC );
+ err_when( refY2 >= MAX_WORLD_Y_LOC );
+
+ //-------- build a matrix on the refective area ---------//
+
+ int refWidth=refX2-refX1+1, refHeight=refY2-refY1+1;
+ short* refMatrix = (short*) mem_add( sizeof(short) * refWidth * refHeight );
+ short* refMatrixPtr;
+
+ //------ initialize the weights of the matrix ------//
+
+ int xLoc, yLoc; // inner locations in the matrix receives more weights than outer locations do
+ int t1, t2;
+
+ for( yLoc=refY1 ; yLoc<=refY2 ; yLoc++ )
+ {
+ refMatrixPtr = refMatrix + (yLoc-refY1)*refWidth;
+ locPtr = world.get_loc( refX1, yLoc );
+
+ for( xLoc=refX1 ; xLoc<=refX2 ; xLoc++, refMatrixPtr++, locPtr++ )
+ {
+ t1 = abs(xLoc-centerX);
+ t2 = abs(yLoc-centerY);
+
+ if( locPtr->region_id != buildRegionId ||
+ locPtr->is_plateau() != buildIsPlateau ||
+ locPtr->is_power_off() )
+ {
+ *refMatrixPtr = -1000;
+ }
+ else
+ {
+ *refMatrixPtr = 10-max(t1, t2); // it's negative value, and the value is lower for the outer ones
+ }
+ }
+ }
+
+ //----- calculate weights of the locations in the matrix ----//
+
+ int xLocB, yLocB, weightAdd, weightReduce;
+ short refBX1, refBY1, refBX2, refBY2;
+ short refCX1, refCY1, refCX2, refCY2;
+
+ for( yLoc=refY1 ; yLoc<=refY2 ; yLoc++ )
+ {
+ locPtr = world.get_loc(refX1, yLoc);
+
+ for( xLoc=refX1 ; xLoc<=refX2 ; xLoc++, locPtr++ )
+ {
+ if( locPtr->region_id != buildRegionId ||
+ locPtr->is_plateau() != buildIsPlateau ||
+ locPtr->is_power_off() )
+ {
+ continue;
+ }
+
+ //------- if there is a firm on the location ------//
+
+ weightAdd = 0;
+ weightReduce = 0;
+
+ if( locPtr->is_firm() )
+ {
+ firmPtr = firm_array[locPtr->firm_recno()];
+
+ if( buildFirmId==FIRM_MARKET || buildFirmId==FIRM_FACTORY ) // only factories & market places need building close to other firms
+ {
+ int rc = 1;
+
+ if( firmPtr->nation_recno != nation_recno )
+ rc = 0;
+
+ //----- check if the firm is of the right type ----//
+
+ if( buildFirmId==FIRM_MARKET ) // build a market place close to mines and factories
+ {
+ if( firmPtr->firm_id!=FIRM_MINE && firmPtr->firm_id!=FIRM_FACTORY ) // market places should be built close to factories and mines and they are the only two firms that influence the location of the market place
+ rc = 0;
+ }
+ else if( buildFirmId==FIRM_FACTORY ) // build a factory close to mines and market places
+ {
+ if( firmPtr->firm_id!=FIRM_MINE && firmPtr->firm_id!=FIRM_MARKET ) // market places should be built close to factories and mines and they are the only two firms that influence the location of the market place
+ rc = 0;
+ }
+
+ //------------------------------------------/
+
+ if( rc )
+ {
+ refBX1 = firmPtr->center_x - EFFECTIVE_FIRM_FIRM_DISTANCE;
+ refBY1 = firmPtr->center_y - EFFECTIVE_FIRM_FIRM_DISTANCE;
+ refBX2 = firmPtr->center_x + EFFECTIVE_FIRM_FIRM_DISTANCE;
+ refBY2 = firmPtr->center_y + EFFECTIVE_FIRM_FIRM_DISTANCE;
+
+ weightAdd = 30;
+ }
+ }
+
+ refCX1 = firmPtr->loc_x1-1; // add negative weights on space around this firm
+ refCY1 = firmPtr->loc_y1-1; // so to prevent firms from building right next to the firm
+ refCX2 = firmPtr->loc_x2+1; // and leave some space for walking path.
+ refCY2 = firmPtr->loc_y2+1;
+
+ weightReduce = 20;
+ }
+
+ //------- if there is a town on the location ------//
+
+ else if( locPtr->is_town() )
+ {
+ townPtr = town_array[locPtr->town_recno()];
+
+ refBX1 = townPtr->center_x - EFFECTIVE_FIRM_TOWN_DISTANCE;
+ refBY1 = townPtr->center_y - EFFECTIVE_FIRM_TOWN_DISTANCE;
+ refBX2 = townPtr->center_x + EFFECTIVE_FIRM_TOWN_DISTANCE;
+ refBY2 = townPtr->center_y + EFFECTIVE_FIRM_TOWN_DISTANCE;
+
+ weightAdd = townPtr->population*2;
+
+ //----- if the town is not our own -----//
+
+ if(townPtr->nation_recno != nation_recno)
+ {
+ if( townPtr->nation_recno==0 ) // it's an independent town
+ weightAdd = weightAdd * (100-townPtr->average_resistance(nation_recno)) / 100;
+ else // more friendly nations get higher weights
+ {
+ int relationStatus = get_relation_status(townPtr->nation_recno);
+
+ if( relationStatus >= NATION_NEUTRAL )
+ weightAdd = weightAdd * (relationStatus-NATION_NEUTRAL+1) / 4;
+ }
+ }
+
+ refCX1 = townPtr->loc_x1-1; // add negative weights on space around this firm
+ refCY1 = townPtr->loc_y1-1; // so to prevent firms from building right next to the firm
+ refCX2 = townPtr->loc_x2+1; // and leave some space for walking path.
+ refCY2 = townPtr->loc_y2+1;
+
+ weightReduce = 100;
+ }
+ else
+ continue;
+
+ //------ add weights to the matrix ------//
+
+ if( weightAdd )
+ {
+ for( yLocB=max(refY1,refBY1) ; yLocB<=min(refY2,refBY2) ; yLocB++ )
+ {
+ xLocB = max(refX1,refBX1);
+ refMatrixPtr = refMatrix + (yLocB-refY1)*refWidth + (xLocB-refX1);
+
+ for( ; xLocB<=min(refX2,refBX2) ; xLocB++ )
+ {
+ *refMatrixPtr++ += weightAdd;
+ }
+ }
+ }
+
+ //------ reduce weights from the matrix ------//
+
+ if( weightReduce )
+ {
+ for( yLocB=max(refY1,refCY1) ; yLocB<=min(refY2,refCY2) ; yLocB++ )
+ {
+ xLocB = max(refX1,refCX1);
+ refMatrixPtr = refMatrix + (yLocB-refY1)*refWidth + (xLocB-refX1);
+
+ for( ; xLocB<=min(refX2,refCX2) ; xLocB++ )
+ {
+ *refMatrixPtr++ -= weightReduce;
+ }
+ }
+ }
+ }
+ }
+
+ //------ select the best building site in the matrix -------//
+
+ resultXLoc = -1;
+ resultYLoc = -1;
+
+ short thisWeight, bestWeight=0;
+
+ refX2 -= firmLocWidth-1; // do not scan beyond the border
+ refY2 -= firmLocHeight-1;
+
+ for( yLoc=refY1 ; yLoc<=refY2 ; yLoc++ )
+ {
+ for( xLoc=refX1 ; xLoc<=refX2 ; xLoc++ )
+ {
+ if( world.get_region_id(xLoc, yLoc) != buildRegionId ||
+ !world.can_build_firm(xLoc, yLoc, buildFirmId) )
+ {
+ continue;
+ }
+
+ //---- calculate the average weight of a firm area ----//
+
+ int totalWeight=0;
+
+ refMatrixPtr = refMatrix + (yLoc-refY1)*refWidth + (xLoc-refX1);
+
+ for( int yCount=0 ; yCount<firmLocHeight ; yCount++ )
+ {
+ for( int xCount=0 ; xCount<firmLocWidth ; xCount++ )
+ {
+ totalWeight += *refMatrixPtr++;
+ }
+
+ refMatrixPtr += refWidth-firmLocWidth;
+ }
+
+ //------- compare the weights --------//
+
+ thisWeight = totalWeight / (firmLocWidth*firmLocHeight);
+
+ if( thisWeight > bestWeight )
+ {
+ bestWeight = thisWeight;
+
+ resultXLoc = xLoc;
+ resultYLoc = yLoc;
+ }
+ }
+ }
+
+ //------ release the refective matrix -----//
+
+ mem_del( refMatrix );
+
+ return resultXLoc >= 0;
+}
+//-------- End of function Nation::find_best_firm_loc --------//
+
+
diff --git a/OAI_SPY.cpp b/OAI_SPY.cpp
new file mode 100644
index 0000000..3940852
--- /dev/null
+++ b/OAI_SPY.cpp
@@ -0,0 +1,508 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_SPY.CPP
+//Description: AI - Spy activities
+
+#include <ALL.h>
+#include <OFIRM.h>
+#include <OTOWN.h>
+#include <OUNIT.h>
+#include <ONATION.h>
+#include <OSPY.h>
+
+//--------- Begin of function Nation::think_spy --------//
+
+void Nation::think_spy()
+{
+
+
+}
+//---------- End of function Nation::think_spy --------//
+
+
+//--------- Begin of function Nation::ai_assign_spy_to_town --------//
+//
+// Think about sending spies to the specific town.
+//
+// <int> townRecno - recno of the town
+// [int] raceId - race id. of the spy
+// (default: majority_race() of the tonw)
+//
+// return: <int> 1 - a spy is assigned successfully.
+// 0 - failure.
+//
+int Nation::ai_assign_spy_to_town(int townRecno, int raceId)
+{
+ Town* townPtr = town_array[townRecno];
+
+ if( townPtr->population >= MAX_TOWN_POPULATION )
+ return 0;
+
+ if( !raceId )
+ raceId = townPtr->majority_race();
+
+ int mobileOnly = townPtr->nation_recno == nation_recno; // if assign to own towns/firms, only get mobile spies, don't get spies from existing towns/firms as that will result in a loop effect
+
+ return ai_assign_spy( townPtr->loc_x1, townPtr->loc_y1, raceId, mobileOnly );
+}
+//---------- End of function Nation::ai_assign_spy_to_town --------//
+
+
+//--------- Begin of function Nation::ai_assign_spy_to_firm --------//
+//
+// Think about sending spies to the specific firm.
+//
+// return: <int> 1 - a spy is assigned successfully.
+// 0 - failure.
+//
+int Nation::ai_assign_spy_to_firm(int firmRecno)
+{
+ Firm* firmPtr = firm_array[firmRecno];
+
+ err_when( !firmPtr->worker_array );
+
+ //---- check if the firm is full or not -----//
+
+ if( firmPtr->nation_recno == nation_recno ) // if it's our own firm
+ {
+ if( firmPtr->is_worker_full() ) // use is_worker_full() for own firms as it take into account of units patrolling outside
+ return 0;
+ }
+ else
+ {
+ if( firmPtr->worker_count == MAX_WORKER )
+ return 0;
+ }
+
+ //------ look for an existing spy -------//
+
+ int raceId = firmPtr->majority_race();
+ int mobileOnly = firmPtr->nation_recno == nation_recno; // if assign to own firms/firms, only get mobile spies, don't get spies from existing firms/firms as that will result in a loop effect
+
+ return ai_assign_spy( firmPtr->loc_x1, firmPtr->loc_y1, raceId, mobileOnly );
+}
+//---------- End of function Nation::ai_assign_spy_to_firm --------//
+
+
+//--------- Begin of function Nation::ai_assign_spy --------//
+//
+// Try to locate an existing spy for use.
+//
+// <int> targetXLoc, targetYLoc - the target location
+// [int] spyRaceId - if specified, only spies of this race
+// will be located. (default:0)
+// [int] mobileOnly - get mobile spies only. (default:0)
+//
+int Nation::ai_assign_spy(int targetXLoc, int targetYLoc, int spyRaceId, int mobileOnly)
+{
+ int unitRecno=0;
+
+ //---- try to find an existing spy ----//
+
+ Spy* spyPtr = ai_find_spy( targetXLoc, targetYLoc, spyRaceId, mobileOnly );
+
+ if( spyPtr )
+ unitRecno = spyPtr->mobilize_spy();
+
+ //--- if not successful, then try to hire one ---//
+
+ if( !unitRecno )
+ unitRecno = hire_unit(SKILL_SPYING, spyRaceId, targetXLoc, targetYLoc);
+
+ //--- if cannot hire one, try to train one ----//
+
+ int trainTownRecno=0;
+
+ if( !unitRecno )
+ unitRecno = train_unit(SKILL_SPYING, spyRaceId, targetXLoc, targetYLoc, trainTownRecno);
+
+ if( !unitRecno )
+ return 0;
+
+ //------ get the spy object of the unit ------//
+
+ Unit* unitPtr = unit_array[unitRecno];
+
+ err_when( !unitPtr->spy_recno );
+
+ spyPtr = spy_array[unitPtr->spy_recno];
+
+ //------- get the nation of the assign destination -----//
+
+ Location* locPtr = world.get_loc(targetXLoc, targetYLoc);
+ int cloakedNationRecno;
+
+ if( locPtr->is_firm() )
+ {
+ Firm* firmPtr = firm_array[locPtr->firm_recno()];
+
+ err_when( firmPtr->nation_recno==0 ); // cannot assign to a monster firm
+
+ cloakedNationRecno = firmPtr->nation_recno;
+ }
+ else if( locPtr->is_town() )
+ {
+ Town* townPtr = town_array[locPtr->town_recno()];
+
+ cloakedNationRecno = townPtr->nation_recno;
+ }
+ else
+ {
+ return 0; // the original firm or town has been destroyed or sold
+ }
+
+ //------- Add the assign spy action --------//
+
+ int actionRecno = add_action( targetXLoc, targetYLoc,
+ -1, -1, ACTION_AI_ASSIGN_SPY, cloakedNationRecno, 1, unitRecno );
+
+ if( !actionRecno )
+ return 0;
+
+ //------ if the unit is under training ------//
+
+ if( trainTownRecno )
+ town_array[trainTownRecno]->train_unit_action_id = get_action(actionRecno)->action_id;
+
+ return 1;
+}
+//---------- End of function Nation::ai_assign_spy --------//
+
+
+//--------- Begin of function Nation::ai_find_spy --------//
+//
+// Try to locate an existing spy for use.
+//
+// <int> targetXLoc, targetYLoc - the target location
+// [int] spyRaceId - if specified, only spies of this race
+// will be located. (default:0)
+// [int] mobileOnly - get mobile spies only. (default:0)
+//
+Spy* Nation::ai_find_spy(int targetXLoc, int targetYLoc, int spyRaceId, int mobileOnly)
+{
+ //--- first check if we have an existing spy ready for the mission ---//
+
+ Spy *spyPtr, *bestSpy=NULL;
+ int curRating, bestRating=0;
+ int spyXLoc, spyYLoc;
+ int targetRegionId = world.get_region_id(targetXLoc, targetYLoc);
+
+ for(int i=spy_array.size(); i>0; i--)
+ {
+ if(spy_array.is_deleted(i))
+ continue;
+
+ spyPtr = spy_array[i];
+
+ if( !spyPtr->true_nation_recno != nation_recno )
+ continue;
+
+ if( spyRaceId && spyRaceId != race_id )
+ continue;
+
+ if( spyPtr->spy_place == SPY_MOBILE )
+ {
+ Unit* unitPtr = unit_array[spyPtr->spy_place_para];
+
+ if( !unitPtr->is_ai_all_stop() )
+ continue;
+
+ spyXLoc = unitPtr->next_x_loc();
+ spyYLoc = unitPtr->next_y_loc();
+ }
+ else
+ {
+ if( mobileOnly )
+ continue;
+
+ if( spyPtr->spy_place == SPY_FIRM )
+ {
+ Firm* firmPtr = firm_array[spyPtr->spy_place_para];
+
+ if( firmPtr->nation_recno != nation_recno ) // only get spies from our own firms
+ continue;
+
+ spyXLoc = firmPtr->center_x;
+ spyYLoc = firmPtr->center_y;
+ }
+ else if( spyPtr->spy_place == SPY_TOWN )
+ {
+ Town* townPtr = town_array[spyPtr->spy_place_para];
+
+ if( townPtr->nation_recno != nation_recno ) // only get spies from our own towns
+ continue;
+
+ spyXLoc = townPtr->center_x;
+ spyYLoc = townPtr->center_y;
+ }
+ else
+ continue; // in ships or undefined
+ }
+
+ //--- check if the region ids are matched ---//
+
+ if( world.get_region_id(spyXLoc, spyYLoc) != targetRegionId )
+ continue;
+
+ //----------------------------------------//
+
+ curRating = world.distance_rating(targetXLoc, targetYLoc, spyXLoc, spyYLoc);
+ curRating += spyPtr->spy_skill + spyPtr->spy_loyalty/2;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestSpy = spyPtr;
+ }
+ }
+
+ return bestSpy;
+}
+//---------- End of function Nation::ai_find_spy --------//
+
+
+//----- Begin of function Nation::ai_assign_spy -----//
+//
+// action_x_loc, action_y_loc - location of the target firm or town
+// ref_x_loc, ref_y_loc - not used
+// unit_recno - unit recno of the spy
+// action_para - the cloak nation recno the spy should set to.
+//
+int Nation::ai_assign_spy(ActionNode* actionNode)
+{
+ if(!seek_path.total_node_avail)
+ return 0;
+
+ if( unit_array.is_deleted(actionNode->unit_recno) )
+ return -1;
+
+ Unit* spyUnit = unit_array[actionNode->unit_recno];
+
+ if( !spyUnit->is_visible() ) // it's still under training, not available yet
+ return -1;
+
+ if( !spyUnit->spy_recno || spyUnit->true_nation_recno() != nation_recno )
+ return -1;
+
+ //------ change the cloak of the spy ------//
+
+ int newFlag;
+ Spy* spyPtr = spy_array[spyUnit->spy_recno];
+
+ if( reputation < 0 ) // if the nation's reputation is negative, use sneak mode to avoid chance of being uncovered and further damage the reputation
+ newFlag = m.random( 2+(-(int)reputation)/5 )==0; // 2 to 22
+ else
+ newFlag = m.random(2)==0; // 50% chance of being 1
+
+ spyPtr->notify_cloaked_nation_flag = newFlag;
+
+ if( !spyUnit->can_spy_change_nation() ) // if the spy can't change nation recno now
+ {
+ int destXLoc = spyUnit->next_x_loc() + m.random(20) - 10;
+ int destYLoc = spyUnit->next_y_loc() + m.random(20) - 10;
+
+ destXLoc = max(0, destXLoc);
+ destXLoc = min(MAX_WORLD_X_LOC-1, destXLoc);
+
+ destYLoc = max(0, destYLoc);
+ destYLoc = min(MAX_WORLD_Y_LOC-1, destXLoc);
+
+ spyUnit->move_to( destXLoc, destYLoc );
+
+ actionNode->retry_count++; // never give up
+ return 0; // return now and try again later
+ }
+
+ spyUnit->spy_change_nation(actionNode->action_para,COMMAND_AI);
+
+ //------- assign the spy to the target -------//
+
+ spyUnit->assign(actionNode->action_x_loc, actionNode->action_y_loc);
+
+ //----------------------------------------------------------------//
+ // Since the spy has already changed its cloaked nation recno
+ // we cannot set the ai_action_id of the unit as when it needs
+ // to call action_finished() or action_failure() it will
+ // use the cloaked nation recno, which is incorrect.
+ // So we just return -1, noting that the action has been completed.
+ //----------------------------------------------------------------//
+
+ return -1;
+}
+//----- End of function Nation::ai_assign_spy -----//
+
+
+//-------- Begin of function Nation::think_assign_spy_target_camp --------//
+//
+// Think about planting spies into independent towns and enemy towns.
+//
+int Nation::think_assign_spy_target_camp(int raceId, int regionId)
+{
+ Firm *firmPtr;
+ int curRating, bestRating=0, bestFirmRecno=0;
+
+ for( int firmRecno=firm_array.size() ; firmRecno>0 ; firmRecno-- )
+ {
+ if( firm_array.is_deleted(firmRecno) )
+ continue;
+
+ firmPtr = firm_array[firmRecno];
+
+ if( firmPtr->nation_recno == nation_recno ) // don't assign to own firm
+ continue;
+
+ if( firmPtr->region_id != regionId )
+ continue;
+
+ if( firmPtr->overseer_recno == 0 ||
+ firmPtr->worker_count == MAX_WORKER )
+ {
+ continue;
+ }
+
+ if( firmPtr->majority_race() != raceId )
+ continue;
+
+ //---------------------------------//
+
+ Unit* overseerUnit = unit_array[firmPtr->overseer_recno];
+
+ if( overseerUnit->spy_recno ) // if the overseer is already a spy
+ continue;
+
+ curRating = 100 - overseerUnit->loyalty;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestFirmRecno = firmRecno;
+ }
+ }
+
+ return bestFirmRecno;
+}
+//-------- End of function Nation::think_assign_spy_target_camp --------//
+
+
+//-------- Begin of function Nation::think_assign_spy_target_town --------//
+//
+// Think about planting spies into independent towns and enemy towns.
+//
+int Nation::think_assign_spy_target_town(int raceId, int regionId)
+{
+ Town *townPtr;
+ int townCount = town_array.size();
+ int townRecno = m.random(townCount)+1;
+
+ for( int i=town_array.size() ; i>0 ; i-- )
+ {
+ if( ++townRecno > townCount )
+ townRecno = 1;
+
+ if( town_array.is_deleted(townRecno) )
+ continue;
+
+ townPtr = town_array[townRecno];
+
+ if( townPtr->nation_recno == nation_recno ) // don't assign to own firm
+ continue;
+
+ if( townPtr->region_id != regionId )
+ continue;
+
+ if( townPtr->population > MAX_TOWN_POPULATION-5 ) // -5 so that even if we assign too many spies to a town at the same time, there will still room for them
+ continue;
+
+ //---- for player towns, don't assign too frequently ----//
+
+ if( !townPtr->ai_town )
+ {
+ if( m.random(3) != 0 )
+ continue;
+ }
+
+ //----------------------------------------//
+
+ if( townPtr->nation_recno )
+ {
+ if( townPtr->race_loyalty_array[raceId-1] < MIN_NATION_DEFEND_LOYALTY ) // no need to assign spies to these towns as they are already very low
+ continue;
+ }
+ else
+ {
+ if( townPtr->race_resistance_array[raceId-1][nation_recno-1] < MIN_INDEPENDENT_DEFEND_LOYALTY ) // no need to assign spies to these towns as they are already very low
+ continue;
+ }
+
+ if( townPtr->majority_race() != raceId )
+ continue;
+
+ return townRecno;
+ }
+
+ return 0;
+}
+//-------- End of function Nation::think_assign_spy_target_town --------//
+
+
+//-------- Begin of function Nation::think_assign_spy_own_town --------//
+//
+// Think about planting spies into independent towns and enemy towns.
+//
+int Nation::think_assign_spy_own_town(int raceId, int regionId)
+{
+ Town *townPtr;
+ int townCount = town_array.size();
+ int townRecno = m.random(townCount)+1;
+ int spyCount;
+
+ for( int i=town_array.size() ; i>0 ; i-- )
+ {
+ if( ++townRecno > townCount )
+ townRecno = 1;
+
+ if( town_array.is_deleted(townRecno) )
+ continue;
+
+ townPtr = town_array[townRecno];
+
+ if( townPtr->nation_recno != nation_recno ) // only assign to own firm
+ continue;
+
+ if( townPtr->region_id != regionId )
+ continue;
+
+ if( townPtr->population > MAX_TOWN_POPULATION-5 )
+ continue;
+
+ if( townPtr->majority_race() != raceId )
+ continue;
+
+ int curSpyLevel = spy_array.total_spy_skill_level( SPY_TOWN, townRecno, nation_recno, spyCount );
+ int neededSpyLevel = townPtr->needed_anti_spy_level();
+
+ if( neededSpyLevel > curSpyLevel + 30 )
+ return townRecno;
+ }
+
+ return 0;
+}
+//-------- End of function Nation::think_assign_spy_own_town --------//
diff --git a/OAI_TALK.cpp b/OAI_TALK.cpp
new file mode 100644
index 0000000..92e2f2d
--- /dev/null
+++ b/OAI_TALK.cpp
@@ -0,0 +1,1111 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_TALK.CPP
+//Description: AI routines on diplomacy.
+
+#include <OCONFIG.h>
+#include <OTALKRES.h>
+#include <OTECHRES.h>
+#include <OF_MARK.h>
+#include <ONATION.h>
+
+
+//-------- Declare static functions ---------//
+
+static int has_sent_same_msg(TalkMsg* talkMsg);
+
+
+//----- Begin of function Nation::ai_process_talk_msg -----//
+//
+// action_para - recno of the message in talk_res.talk_msg_array.
+//
+int Nation::ai_process_talk_msg(ActionNode* actionNode)
+{
+ if( talk_res.is_talk_msg_deleted(actionNode->action_para) ) // if the talk message has been deleted
+ return -1;
+
+ TalkMsg* talkMsg = talk_res.get_talk_msg(actionNode->action_para);
+
+ err_when( talkMsg->talk_id < 1 || talkMsg->talk_id > MAX_TALK_TYPE );
+
+ err_when( talkMsg->from_nation_recno == nation_recno );
+ err_when( talkMsg->to_nation_recno != nation_recno );
+
+ if( !talkMsg->is_valid_to_reply() ) // if it is no longer valid
+ return -1;
+
+ //----- call the consider function -------//
+
+ if( talkMsg->reply_type == REPLY_WAITING )
+ {
+ int rc = consider_talk_msg(talkMsg);
+
+ if( rc==1 ) // if rc is not 1 or 0, than the consider function have processed itself, no need to call reply_talk_msg() here
+ talk_res.reply_talk_msg( actionNode->action_para, REPLY_ACCEPT, COMMAND_AI );
+
+ else if( rc==0 )
+ talk_res.reply_talk_msg( actionNode->action_para, REPLY_REJECT, COMMAND_AI );
+
+ // don't reply if rc is neither 0 or 1
+ }
+ else
+ err_here();
+
+ return -1; // always return -1 to remove the action from action_array.
+}
+//------ End of function Nation::ai_process_talk_msg ------//
+
+
+//----- Begin of function Nation::consider_talk_msg -----//
+//
+int Nation::consider_talk_msg(TalkMsg* talkMsg)
+{
+ //--------------------------------------------//
+ // Whether the nation has already sent out a
+ // message that is the same as the one it received.
+ // If so, accept the message right now.
+ //--------------------------------------------//
+
+ switch( talkMsg->talk_id )
+ {
+ case TALK_PROPOSE_TRADE_TREATY:
+ case TALK_PROPOSE_FRIENDLY_TREATY:
+ case TALK_PROPOSE_ALLIANCE_TREATY:
+ case TALK_REQUEST_TRADE_EMBARGO:
+ case TALK_REQUEST_CEASE_WAR:
+ case TALK_REQUEST_DECLARE_WAR:
+ if( has_sent_same_msg(talkMsg) )
+ return 1;
+ };
+
+ //-------------------------------//
+
+ switch( talkMsg->talk_id )
+ {
+ case TALK_PROPOSE_TRADE_TREATY:
+ return consider_trade_treaty(talkMsg->from_nation_recno) >= 0; // the returned value is the curRating - acceptRating, if >=0, means it accepts
+
+ case TALK_PROPOSE_FRIENDLY_TREATY:
+ return consider_friendly_treaty(talkMsg->from_nation_recno) >= 0;
+
+ case TALK_PROPOSE_ALLIANCE_TREATY:
+ return consider_alliance_treaty(talkMsg->from_nation_recno) >= 0;
+
+ case TALK_REQUEST_MILITARY_AID:
+ return consider_military_aid(talkMsg);
+
+ case TALK_REQUEST_TRADE_EMBARGO:
+ return consider_trade_embargo(talkMsg);
+
+ case TALK_REQUEST_CEASE_WAR:
+ return consider_cease_war(talkMsg->from_nation_recno) >= 0;
+
+ case TALK_REQUEST_DECLARE_WAR:
+ return consider_declare_war(talkMsg);
+
+ case TALK_REQUEST_BUY_FOOD:
+ return consider_sell_food(talkMsg);
+
+ case TALK_GIVE_TRIBUTE:
+ return consider_take_tribute(talkMsg);
+
+ case TALK_DEMAND_TRIBUTE:
+ return consider_give_tribute(talkMsg);
+
+ case TALK_GIVE_AID:
+ return consider_take_aid(talkMsg);
+
+ case TALK_DEMAND_AID:
+ return consider_give_aid(talkMsg);
+
+ case TALK_GIVE_TECH:
+ return consider_take_tech(talkMsg);
+
+ case TALK_DEMAND_TECH:
+ return consider_give_tech(talkMsg);
+
+ case TALK_REQUEST_SURRENDER:
+ return consider_accept_surrender_request(talkMsg);
+
+ default:
+ err_here();
+ return 0;
+ }
+}
+//------ End of function Nation::consider_talk_msg ------//
+
+
+//----- Begin of function Nation::notify_talk_msg -----//
+//
+// Notify the AI for a notification only message (reply not needed.)
+//
+// This function is called directly from TalkRes::send_talk_msg_now()
+// when the message is sent.
+//
+void Nation::notify_talk_msg(TalkMsg* talkMsg)
+{
+ int relationChange=0;
+ NationRelation* nationRelation = get_relation(talkMsg->from_nation_recno);
+
+ switch( talkMsg->talk_id )
+ {
+ case TALK_END_TRADE_TREATY: // it's a notification message only, no accept or reject
+ relationChange = -5;
+ nationRelation->last_talk_reject_date_array[TALK_PROPOSE_TRADE_TREATY-1] = info.game_date;
+ break;
+
+ case TALK_END_FRIENDLY_TREATY: // it's a notification message only, no accept or reject
+ case TALK_END_ALLIANCE_TREATY:
+ relationChange = -5;
+ nationRelation->last_talk_reject_date_array[TALK_PROPOSE_FRIENDLY_TREATY-1] = info.game_date;
+ nationRelation->last_talk_reject_date_array[TALK_PROPOSE_ALLIANCE_TREATY-1] = info.game_date;
+ break;
+
+ case TALK_DECLARE_WAR: // it already drops to zero when the status is set to hostile
+ break;
+
+ case TALK_GIVE_TRIBUTE:
+ case TALK_GIVE_AID:
+
+ //--------------------------------------------------------------//
+ // The less cash the nation, the more it will appreciate the
+ // tribute.
+ //
+ // $1000 for 100 ai relation increase if the nation's cash is 1000.
+ //--------------------------------------------------------------//
+
+ relationChange = 100 * talkMsg->talk_para1 / max(1000, (int) cash);
+ break;
+
+ case TALK_GIVE_TECH:
+
+ //--------------------------------------------------------------//
+ // The lower tech the nation has, the more it will appreciate the
+ // tech giveaway.
+ //
+ // Giving a level 2 weapon which the nation is unknown of
+ // increase the ai relation by 60 if its pref_use_weapon is 100.
+ // (by 30 if its pref_use_weapon is 0).
+ //--------------------------------------------------------------//
+ {
+ int ownLevel = tech_res[talkMsg->talk_para1]->get_nation_tech_level(nation_recno);
+
+ if( talkMsg->talk_para2 > ownLevel )
+ relationChange = 30 * (talkMsg->talk_para2-ownLevel)
+ * (100+pref_use_weapon) / 200;
+ break;
+ }
+
+ case TALK_SURRENDER:
+ break;
+
+ default:
+ err_here();
+ }
+
+ //------- chance relationship now -------//
+
+ if( relationChange < 0 )
+ relationChange -= relationChange * pref_forgiveness / 100;
+
+ if( relationChange != 0 )
+ change_ai_relation_level( talkMsg->from_nation_recno, relationChange );
+}
+//------ End of function Nation::notify_talk_msg ------//
+
+
+//----- Begin of function Nation::consider_trade_treaty -----//
+//
+// Consider agreeing to open up trade with the given nation.
+//
+int Nation::consider_trade_treaty(int withNationRecno)
+{
+ NationRelation* nationRelation = get_relation(withNationRecno);
+
+ //---- don't accept new trade treaty soon when the trade treaty was terminated not too long ago ----//
+
+ if( info.game_date < nationRelation->last_talk_reject_date_array[TALK_END_TRADE_TREATY-1] + 365 - pref_forgiveness )
+ return 0;
+
+ //-- if we look forward to have a trade treaty with this nation ourselves --//
+
+ if( nationRelation->ai_demand_trade_treaty )
+ return 1;
+
+ return ai_trade_with_rating(withNationRecno) > 0;
+}
+//------ End of function Nation::consider_trade_treaty ------//
+
+
+//----- Begin of function Nation::ai_trade_with_rating -----//
+//
+// Return a rating from 0 to 100 indicating how important
+// will be for us to trade with the given nation.
+//
+int Nation::ai_trade_with_rating(int withNationRecno)
+{
+ Nation* nationPtr = nation_array[withNationRecno];
+ int tradeRating=0;
+
+ for( int i=0 ; i<MAX_RAW ; i++ )
+ {
+ //--------------------------------------------------------------//
+ //
+ // If we have the raw material and it doesn't have, then we
+ // can export to it. And it is more favorite if the nation's
+ // population is high, so we can export more.
+ //
+ //--------------------------------------------------------------//
+
+ if( raw_count_array[i] && !nationPtr->raw_count_array[i] )
+ tradeRating += min(30, nationPtr->total_population/3);
+
+ //--------------------------------------------------------------//
+ //
+ // If the nation has the supply a raw material that we don't
+ // have, then we can import it.
+ //
+ //--------------------------------------------------------------//
+
+ else if( nationPtr->raw_count_array[i] && !raw_count_array[i] )
+ tradeRating += 30;
+ }
+
+ return tradeRating;
+}
+//------ End of function Nation::ai_trade_with_rating ------//
+
+
+//----- Begin of function Nation::consider_friendly_treaty -----//
+//
+int Nation::consider_friendly_treaty(int withNationRecno)
+{
+ NationRelation* nationRelation = get_relation(withNationRecno);
+
+ if( nationRelation->status >= NATION_FRIENDLY ) // already has a friendly relationship
+ return -1; // -1 means don't reply
+
+ if( nationRelation->ai_relation_level < 20 )
+ return -1;
+
+ //------- some consideration first -------//
+
+ if( !should_consider_friendly(withNationRecno) )
+ return -1;
+
+ //------ total import and export amounts --------//
+
+ int curRating = consider_alliance_rating(withNationRecno);
+
+ int acceptRating = 60 - pref_allying_tendency/8 - pref_peacefulness/4; // range of acceptRating: 23 to 60
+
+ return curRating - acceptRating;
+}
+//------ End of function Nation::consider_friendly_treaty ------//
+
+
+//----- Begin of function Nation::consider_alliance_treaty -----//
+//
+int Nation::consider_alliance_treaty(int withNationRecno)
+{
+ NationRelation* nationRelation = get_relation(withNationRecno);
+
+ if( nationRelation->status >= NATION_ALLIANCE ) // already has a friendly relationship
+ return -1; // -1 means don't reply
+
+ if( nationRelation->ai_relation_level < 40 )
+ return -1;
+
+ //------- some consideration first -------//
+
+ if( !should_consider_friendly(withNationRecno) )
+ return -1;
+
+ //------ total import and export amounts --------//
+
+ int curRating = consider_alliance_rating(withNationRecno);
+
+ int acceptRating = 80 - pref_allying_tendency/4 - pref_peacefulness/8; // range of acceptRating: 43 to 80
+
+ return curRating - acceptRating;
+}
+//------ End of function Nation::consider_alliance_treaty ------//
+
+
+//----- Begin of function Nation::consider_cease_war -----//
+//
+// This function is shared by think_request_cease_war().
+//
+int Nation::consider_cease_war(int withNationRecno)
+{
+ NationRelation* nationRelation = get_relation(withNationRecno);
+
+ if( nationRelation->status != NATION_HOSTILE )
+ return -1; // -1 means don't reply
+
+ //---- if we are attacking the nation, don't cease fire ----//
+
+ if( ai_attack_target_nation_recno == withNationRecno )
+ return -1;
+
+ //---- if we are planning to capture the enemy's town ---//
+
+ if( ai_capture_enemy_town_recno &&
+ !town_array.is_deleted(ai_capture_enemy_town_recno) &&
+ town_array[ai_capture_enemy_town_recno]->nation_recno == withNationRecno )
+ {
+ return -1;
+ }
+
+ //--- don't cease fire too soon after a war is declared ---//
+
+ if( info.game_date < nationRelation->last_change_status_date + 60 + (100-pref_peacefulness) ) // more peaceful nation may cease fire sooner (but the minimum is 60 days).
+ return -1;
+
+ //------ if we're run short of money for war -----//
+
+ Nation* withNation = nation_array[withNationRecno];
+
+ if( !ai_should_spend_war(withNation->military_rank_rating(), 1) ) // if we shouldn't spend any more on war, then return 1
+ return 1;
+
+ //------------------------------------------------//
+
+ int curRating = consider_alliance_rating(withNationRecno);
+
+ //------------------------------------//
+ //
+ // Tend to be easilier to accept cease-fire if this nation's
+ // military strength is weak.
+ //
+ // If the nation's peacefulness concern is high, it will
+ // also be more likely to accept cease-fire.
+ //
+ //-------------------------------------//
+
+ //--- if the enemy is more power than us, tend more to request cease-fire ---//
+
+ curRating += total_enemy_military() - military_rank_rating();
+
+ curRating += ai_trade_with_rating(withNationRecno) * (100+pref_trading_tendency) / 300; // when we have excessive supply, we may want to cease-fire with our enemy
+
+ curRating -= (military_rank_rating()-50)/2; // if our military ranking is high, we may like to continue the war, otherwise the nation should try to cease-fire
+
+ curRating -= nationRelation->started_war_on_us_count*10; // the number of times this nation has started a war with us, the higher the number, the more unlikely we will accept cease-fire
+
+ int acceptRating = pref_peacefulness/4;
+
+ return curRating - acceptRating;
+}
+//------ End of function Nation::consider_cease_war ------//
+
+
+//----- Begin of function Nation::consider_sell_food -----//
+//
+// talkMsg->talk_para1 - qty of food wanted to buy.
+// talkMsg->talk_para2 - buying price offered for 10 food.
+//
+int Nation::consider_sell_food(TalkMsg* talkMsg)
+{
+ int relationStatus = get_relation_status(talkMsg->from_nation_recno);
+
+ if( relationStatus == NATION_HOSTILE )
+ return 0;
+
+ //--- if after selling the food, the remaining is not enough for its own consumption for ? years ---//
+
+ float newFood = food-talkMsg->talk_para1;
+ float yearConsumption = (float) yearly_food_consumption();
+ int offeredAmount = talkMsg->talk_para2;
+ int relationLevel = get_relation(talkMsg->from_nation_recno)->ai_relation_level;
+
+ if( newFood < 1000 + 1000 * pref_food_reserve / 100 )
+ return 0;
+
+ if( relationLevel >= 50 )
+ offeredAmount += 5; // increase the chance of selling food
+
+ else if( relationLevel < 30 ) // decrease the chance of selling food
+ offeredAmount -=5 ;
+
+ //---- if we run short of cash, we tend to accept the offer ---//
+
+ float fixedExpense = fixed_expense_365days();
+
+ if( cash < fixedExpense )
+ offeredAmount += (int) (20 * (fixedExpense-cash) / fixedExpense);
+
+ //---------------------------------//
+
+ float reserveYears = (float) (100+pref_food_reserve) / 100; // 1 to 2 years
+
+ if( yearly_food_change() > 0 &&
+ newFood > yearConsumption * reserveYears )
+ {
+ if( offeredAmount >= 10 ) // offered >= $10
+ {
+ return 1;
+ }
+ else // < $10, only if we have plenty of reserve
+ {
+ if( newFood > yearConsumption * reserveYears * 2 )
+ return 1;
+ }
+ }
+ else
+ {
+ if( offeredAmount >= 20 )
+ {
+ if( yearly_food_change() > 0 &&
+ newFood > yearConsumption * reserveYears / 2 )
+ {
+ return 1;
+ }
+ }
+
+ if( offeredAmount >= 30 )
+ {
+ return yearly_food_change() > 0 ||
+ newFood > yearConsumption * reserveYears;
+ }
+ }
+
+ return 0;
+}
+//------ End of function Nation::consider_sell_food ------//
+
+
+//----- Begin of function Nation::should_consider_friendly -----//
+//
+int Nation::should_consider_friendly(int withNationRecno)
+{
+ Nation* withNation = nation_array[withNationRecno];
+
+ //------- if this is a larger nation -------//
+
+ if( overall_rank_rating() / 100 > 50 )
+ {
+ //--- big nations don't ally with their biggest opponents ---//
+
+ int maxOverallRating=0;
+ int biggestOpponentNationRecno=0;
+
+ for( int i=nation_array.size() ; i>0 ; i-- )
+ {
+ if( nation_array.is_deleted(i) || i==nation_recno )
+ continue;
+
+ int overallRating = nation_array[i]->overall_rating;
+
+ if( overallRating > maxOverallRating )
+ {
+ maxOverallRating = overallRating;
+ biggestOpponentNationRecno = i;
+ }
+ }
+
+ if( biggestOpponentNationRecno == withNationRecno )
+ return 0;
+ }
+
+ //--- don't ally with nations with too low reputation ---//
+
+ return withNation->reputation >= min(20, reputation) - 20;
+}
+//------ End of function Nation::should_consider_friendly -----//
+
+
+//----- Begin of function Nation::consider_alliance_rating -----//
+//
+// Return a rating from 0 to 100 for whether this nation should ally
+// with the given nation.
+//
+int Nation::consider_alliance_rating(int nationRecno)
+{
+ Nation* nationPtr = nation_array[nationRecno];
+
+ //---- the current relation affect the alliance tendency ---//
+
+ NationRelation* nationRelation = get_relation(nationRecno);
+
+ int allianceRating = nationRelation->ai_relation_level-20;
+
+ //--- if the nation has a bad record of starting wars with us before, decrease the rating ---//
+
+ allianceRating -= nationRelation->started_war_on_us_count * 20;
+
+ //------ add the trade rating -------//
+
+ int tradeRating = trade_rating(nationRecno) + // existing trade amount
+ ai_trade_with_rating(nationRecno)/2; // possible trade
+
+ allianceRating += tradeRating;
+
+ //---- if the nation's power is larger than us, it's a plus ----//
+
+ int powerRating = nationPtr->military_rank_rating() - military_rank_rating(); // if the nation's power is larger than ours, it's good to form treaty with them
+
+ if( powerRating > 0 )
+ allianceRating += powerRating;
+
+ return allianceRating;
+}
+//------ End of function Nation::consider_alliance_rating -----//
+
+
+//----- Begin of function Nation::consider_take_tribute -----//
+//
+// talkMsg->talk_para1 - amount of the tribute.
+//
+int Nation::consider_take_tribute(TalkMsg* talkMsg)
+{
+ int cashSignificance = 100 * talkMsg->talk_para1 / max(1000, (int) cash);
+
+ //--- It does not necessarily want the tribute ---//
+
+ int aiRelationLevel = get_relation(talkMsg->from_nation_recno)->ai_relation_level;
+
+ if( true_profit_365days() > 0 &&
+ cashSignificance < (100-aiRelationLevel)/5 )
+ {
+ return 0;
+ }
+
+ //----------- take the tribute ------------//
+
+ int relationChange = cashSignificance * (100+pref_cash_reserve) / 200;
+
+ change_ai_relation_level( talkMsg->from_nation_recno, relationChange );
+
+ return 1;
+}
+//------ End of function Nation::consider_take_tribute ------//
+
+
+//----- Begin of function Nation::consider_take_aid -----//
+//
+// talkMsg->talk_para1 - amount of the tribute.
+//
+int Nation::consider_take_aid(TalkMsg* talkMsg)
+{
+ int cashSignificance = 100 * talkMsg->talk_para1 / max(1000, (int) cash);
+
+ //--- It does not necessarily want the tribute ---//
+
+ int aiRelationLevel = get_relation(talkMsg->from_nation_recno)->ai_relation_level;
+
+ if( true_profit_365days() > 0 &&
+ cashSignificance < (100-aiRelationLevel)/5 )
+ {
+ return 0;
+ }
+
+ //----------- take the tribute ------------//
+
+ int relationChange = cashSignificance * (100+pref_cash_reserve) / 200;
+
+ change_ai_relation_level( talkMsg->from_nation_recno, relationChange );
+
+ return 1;
+}
+//------ End of function Nation::consider_take_aid ------//
+
+
+//-------- Begin of static function has_sent_same_msg --------//
+//
+// Whether the nation has already sent out a message that is
+// the same as the one it received.
+//
+static int has_sent_same_msg(TalkMsg* talkMsgPtr)
+{
+ TalkMsg talkMsg;
+
+ memcpy( &talkMsg, talkMsgPtr, sizeof(TalkMsg) );
+
+ talkMsg.from_nation_recno = talkMsg.to_nation_recno;
+ talkMsg.to_nation_recno = talkMsg.from_nation_recno;
+
+ return talk_res.is_talk_msg_exist(&talkMsg, 1); // 1-check talk_para1 & talk_para2
+}
+//------ End of static function has_sent_same_msg ------//
+
+
+//----- Begin of function Nation::consider_take_tech -----//
+//
+// talkMsg->talk_para1 - id. of the technology.
+// talkMsg->talk_para2 - level of the technology.
+//
+int Nation::consider_take_tech(TalkMsg* talkMsg)
+{
+ int ourTechLevel = tech_res[talkMsg->talk_para1]->get_nation_tech_level(nation_recno);
+
+ if( ourTechLevel >= talkMsg->talk_para2 )
+ return 0;
+
+ int relationChange = (talkMsg->talk_para2-ourTechLevel) * (15+pref_use_weapon/10);
+
+ change_ai_relation_level( talkMsg->from_nation_recno, relationChange );
+
+ return 1;
+}
+//------ End of function Nation::consider_take_tech ------//
+
+
+//----- Begin of function Nation::surplus_supply_rating -----//
+//
+// Return a rating from 0 to 100 indicating how much surplus
+// of supply this nation has in markets.
+//
+int Nation::surplus_supply_rating()
+{
+ FirmMarket* firmMarket;
+ int stockQty, totalStockQty=0, totalStockSlot=0;
+
+ for( int i=ai_market_count-1; i>=0 ; i-- )
+ {
+ firmMarket = (FirmMarket*) firm_array[ ai_market_array[i] ];
+
+ err_when( firmMarket->firm_id != FIRM_MARKET );
+
+ MarketGoods* marketGoods = firmMarket->market_goods_array;
+
+ for( int j=0 ; j<MAX_MARKET_GOODS ; j++, marketGoods++ )
+ {
+ if( marketGoods->raw_id || marketGoods->product_raw_id )
+ {
+ stockQty = (int) marketGoods->stock_qty;
+
+ totalStockQty += stockQty;
+ totalStockSlot++;
+ }
+ }
+ }
+
+ if( totalStockSlot==0 )
+ return 0;
+
+ int avgStockQty = totalStockQty / totalStockSlot;
+
+ return 100 * avgStockQty / MAX_MARKET_STOCK;
+}
+//------ End of function Nation::surplus_supply_rating ------//
+
+
+//----- Begin of function Nation::consider_give_aid -----//
+//
+// talkMsg->talk_para1 - amount of the tribute.
+//
+int Nation::consider_give_aid(TalkMsg* talkMsg)
+{
+ //-------- don't give tribute too frequently -------//
+
+ NationRelation* nationRelation = get_relation(talkMsg->from_nation_recno);
+
+ if( info.game_date <
+ nationRelation->last_talk_reject_date_array[TALK_GIVE_AID-1]
+ + 365 - pref_allying_tendency )
+ {
+ return 0;
+ }
+
+ //--------------------------------------------------//
+
+ int importanceRating = (int) nationRelation->good_relation_duration_rating;
+
+ if( nationRelation->status >= NATION_FRIENDLY &&
+ ai_should_spend( importanceRating, talkMsg->talk_para1 ) ) // 0-importance is 0
+ {
+ if( info.game_date > nationRelation->last_change_status_date
+ + 720 - pref_allying_tendency ) // we have allied with this nation for quite some while
+ {
+ nationRelation->last_talk_reject_date_array[TALK_GIVE_AID-1] = info.game_date;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+//------ End of function Nation::consider_give_aid ------//
+
+
+//----- Begin of function Nation::consider_give_tribute -----//
+//
+// talkMsg->talk_para1 - amount of the tribute.
+//
+int Nation::consider_give_tribute(TalkMsg* talkMsg)
+{
+ //-------- don't give tribute too frequently -------//
+
+ NationRelation* nationRelation = get_relation(talkMsg->from_nation_recno);
+
+ if( info.game_date <
+ nationRelation->last_talk_reject_date_array[TALK_GIVE_TRIBUTE-1] + 365 - pref_allying_tendency )
+ {
+ return 0;
+ }
+
+ //---------------------------------------------//
+
+ int relationStatus = get_relation_status(talkMsg->from_nation_recno);
+ Nation* fromNation = nation_array[talkMsg->from_nation_recno];
+
+ if( true_profit_365days() < 0 ) // don't give tribute if we are losing money
+ return 0;
+
+ int reserveYears = 1 + 3 * pref_cash_reserve / 100; // 1 to 4 years
+
+ if( cash-talkMsg->talk_para1 < fixed_expense_365days() * reserveYears )
+ return 0;
+
+ int militaryDiff = fromNation->military_rank_rating() - military_rank_rating();
+
+ if( militaryDiff > 10+pref_military_courage/2 )
+ {
+ nationRelation->last_talk_reject_date_array[TALK_GIVE_TRIBUTE-1] = info.game_date;
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::consider_give_tribute ------//
+
+
+//----- Begin of function Nation::consider_give_tech -----//
+//
+// Consider giving the latest level of the technology to the nation.
+//
+// talkMsg->talk_para1 - id. of the technology.
+//
+int Nation::consider_give_tech(TalkMsg* talkMsg)
+{
+ //-------- don't give tribute too frequently -------//
+
+ NationRelation* nationRelation = get_relation(talkMsg->from_nation_recno);
+
+ if( info.game_date <
+ nationRelation->last_talk_reject_date_array[TALK_GIVE_TECH-1] + 365 - pref_allying_tendency )
+ {
+ return 0;
+ }
+
+ //----------------------------------------------------//
+
+ int importanceRating = (int) nationRelation->good_relation_duration_rating;
+
+ if( nationRelation->status == NATION_ALLIANCE &&
+ importanceRating + pref_allying_tendency/10 > 30 )
+ {
+ nationRelation->last_talk_reject_date_array[TALK_GIVE_TECH-1] = info.game_date;
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::consider_give_tech ------//
+
+
+//----- Begin of function Nation::consider_declare_war -----//
+//
+// Consider the request of declaring war on the target nation.
+//
+// talk_para1 - the recno nation to declare war with.
+//
+int Nation::consider_declare_war(TalkMsg* talkMsg)
+{
+ //--- if it even won't consider trade embargo, there is no reason that it will consider declaring war ---//
+
+ if( !consider_trade_embargo(talkMsg) )
+ return 0;
+
+ //---------------------------------------//
+
+ int fromRelationRating = ai_overall_relation_rating(talkMsg->from_nation_recno);
+ int againstRelationRating = ai_overall_relation_rating(talkMsg->talk_para1);
+
+ Nation* againstNation = nation_array[talkMsg->talk_para1];
+
+ NationRelation* fromRelation = get_relation(talkMsg->from_nation_recno);
+ NationRelation* againstRelation = get_relation(talkMsg->talk_para1);
+
+ //--- if we don't have a good enough relation with the requesting nation, turn down the request ---//
+
+ if( fromRelation->good_relation_duration_rating < 10 )
+ return 0;
+
+ //--- if we are more friendly with the against nation than the requesting nation, turn down the request ---//
+
+ if( againstRelation->good_relation_duration_rating >
+ fromRelation->good_relation_duration_rating )
+ {
+ return 0;
+ }
+
+ //--- if the nation is having a financial difficulty, it won't agree ---//
+
+ if( cash < 2000 * pref_cash_reserve / 100 )
+ return 0;
+
+ //--------------------------------------------//
+
+ int acceptRating = 100 + againstNation->total_enemy_military() -
+ military_rank_rating();
+
+ //--- it won't declare war with a friendly or allied nation easily ---//
+
+ if( againstRelation->status >= NATION_FRIENDLY ) // no need to handle NATION_ALLIANCE separately as ai_overall_relation_relation() has already taken it into account
+ acceptRating += 100;
+
+ return fromRelationRating - againstRelationRating > acceptRating;
+}
+//------ End of function Nation::consider_declare_war ------//
+
+
+//----- Begin of function Nation::consider_trade_embargo -----//
+//
+int Nation::consider_trade_embargo(TalkMsg* talkMsg)
+{
+ int fromRelationRating = ai_overall_relation_rating(talkMsg->from_nation_recno);
+ int againstRelationRating = ai_overall_relation_rating(talkMsg->talk_para1);
+
+ NationRelation* fromRelation = get_relation(talkMsg->from_nation_recno);
+ NationRelation* againstRelation = get_relation(talkMsg->talk_para1);
+
+ //--- if we don't have a good enough relation with the requesting nation, turn down the request ---//
+
+ if( fromRelation->good_relation_duration_rating < 5 )
+ return 0;
+
+ //--- if we are more friendly with the against nation than the requesting nation, turn down the request ---//
+
+ if( againstRelation->good_relation_duration_rating >
+ fromRelation->good_relation_duration_rating )
+ {
+ return 0;
+ }
+
+ //--- if we have a large trade with the against nation or have a larger trade with the against nation than the requesting nation ---//
+
+ int fromTrade = trade_rating(talkMsg->from_nation_recno);
+ int againstTrade = trade_rating(talkMsg->talk_para1);
+
+ if( againstTrade > 40 ||
+ ( againstTrade > 10 && againstTrade - fromTrade > 15 ) )
+ {
+ return 0;
+ }
+
+ //--- if the nation is having a financial difficulty, it won't agree ---//
+
+ if( cash < 2000 * pref_cash_reserve / 100 )
+ return 0;
+
+ //--------------------------------------------//
+
+ int acceptRating = 75;
+
+ //--- it won't declare war with a friendly or allied nation easily ---//
+
+ if( againstRelation->status >= NATION_FRIENDLY ) // no need to handle NATION_ALLIANCE separately as ai_overall_relation_relation() has already taken it into account
+ acceptRating += 100;
+
+ return fromRelationRating - againstRelationRating > acceptRating;
+}
+//------ End of function Nation::consider_trade_embargo ------//
+
+
+//----- Begin of function Nation::consider_military_aid -----//
+//
+int Nation::consider_military_aid(TalkMsg* talkMsg)
+{
+ Nation* fromNation = nation_array[talkMsg->from_nation_recno];
+ NationRelation* fromRelation = get_relation(talkMsg->from_nation_recno);
+
+ //----- don't aid too frequently ------//
+
+ if( info.game_date < fromRelation->last_military_aid_date + 200 - pref_allying_tendency )
+ return 0;
+
+ //------- only when the AI relation >= 60 --------//
+
+ if( fromRelation->ai_relation_level < 60 )
+ return 0;
+
+ //--- if the requesting nation is not at war now ----//
+
+ if( !fromNation->is_at_war() )
+ return 0;
+
+ //---- can't aid if we are at war ourselves -----//
+
+ if( is_at_war() )
+ return 0;
+
+ //--- if the nation is having a financial difficulty, it won't agree ---//
+
+ if( cash < 2000 * pref_cash_reserve / 100 )
+ return 0;
+
+ //----- can't aid if we are too weak ourselves ---//
+
+ if( ai_general_count*10 + total_human_count < 100-pref_military_courage/2 )
+ return 0;
+
+ //----- see what units are attacking the nation -----//
+
+ if( unit_array.is_deleted(fromNation->last_attacker_unit_recno) )
+ return 0;
+
+ Unit* unitPtr = unit_array[ fromNation->last_attacker_unit_recno ];
+
+ if( unitPtr->nation_recno == nation_recno ) // if it's our own units
+ return 0;
+
+ if( unitPtr->nation_recno == 0 )
+ return 0;
+
+ if( !unitPtr->is_visible() )
+ return 0;
+
+ //------ only attack if it's a common enemy to us and our ally -----//
+
+ if( get_relation(unitPtr->nation_recno)->status != NATION_HOSTILE )
+ return 0;
+
+ //------- calculate the combat level of the target units there ------//
+
+ int hasWar;
+
+ int targetCombatLevel = mobile_defense_combat_level( unitPtr->next_x_loc(), unitPtr->next_y_loc(),
+ unitPtr->nation_recno, 0, hasWar );
+
+ if( ai_attack_target(unitPtr->next_x_loc(), unitPtr->next_y_loc(), targetCombatLevel, 0, 1 ) ) //0-not defense mode, 1-just move to flag
+ {
+ fromRelation->last_military_aid_date = info.game_date;
+ return 1;
+ }
+
+ return 0;
+}
+//------ End of function Nation::consider_military_aid ------//
+
+
+//----- Begin of function Nation::consider_accept_surrender_request -----//
+//
+// Consider accepting the cash offer and sell the throne to another kingdom.
+//
+// talkMsg->talk_para1 - the amount offered.
+//
+int Nation::consider_accept_surrender_request(TalkMsg* talkMsg)
+{
+ Nation* nationPtr = nation_array[talkMsg->from_nation_recno];
+ int offeredAmt = talkMsg->talk_para1 * 10; // *10 to restore its original value which has been divided by 10 to cope with <short> upper limit
+
+ //---- don't surrender to the player if the player is already the most powerful nation ---//
+
+ if( !nationPtr->is_ai() && config.ai_aggressiveness >= OPTION_HIGH )
+ {
+ if( nation_array.max_overall_nation_recno == nationPtr->nation_recno )
+ return 0;
+ }
+
+ // If our economy is good, then it is harder to convince us
+ // to surrender. But when we are running out of cash,
+ // we ignore all normal thinking in the following block.
+
+ if( !(cash < 100 && profit_365days() < 0) )
+ {
+ //----- never surrender to a weaker nation ------//
+
+ if( nationPtr->overall_rank_rating() < overall_rank_rating() )
+ return 0;
+
+ //------ don't surrender if we are still strong -----//
+
+ if( overall_rank_rating() > 30 + pref_peacefulness/4 ) // 30 to 55
+ return 0;
+
+ //---- don't surrender if our cash is more than the amount they offered ----//
+
+ if( offeredAmt < cash * (75+pref_cash_reserve/2) / 100 ) // 75% to 125%
+ return 0;
+
+ //-- if there are only two nations left, don't surrender if we still have some power --//
+
+ if( nation_array.nation_count == 2 )
+ {
+ if( overall_rank_rating() > 20 - 10 * pref_military_courage / 100 )
+ return 0;
+ }
+ }
+
+ //-------------------------------------//
+
+ int surrenderToRating = ai_surrender_to_rating(talkMsg->from_nation_recno);
+
+ surrenderToRating += 100 * offeredAmt / 13000;
+
+ int acceptRating = overall_rank_rating()*13 + 100;
+
+ //------ AI aggressiveness effects -------//
+
+ switch( config.ai_aggressiveness )
+ {
+ case OPTION_HIGH:
+ if( nationPtr->is_ai() ) // tend to accept AI kingdom offer easier
+ acceptRating -= 75;
+ else
+ acceptRating += 75;
+ break;
+
+ case OPTION_VERY_HIGH:
+ if( nationPtr->is_ai() ) // tend to accept AI kingdom offer easier
+ acceptRating -= 150;
+ else
+ acceptRating += 150;
+ break;
+ }
+
+ return surrenderToRating > acceptRating;
+}
+//------ End of function Nation::consider_accept_surrender_request ------//
+
+
+//----- Begin of function Nation::ai_overall_relation_rating -----//
+//
+// Return the overall relation rating of this nation with the
+// specific nation.
+//
+int Nation::ai_overall_relation_rating(int withNationRecno)
+{
+ NationRelation* nationRelation = get_relation(withNationRecno);
+ Nation* nationPtr = nation_array[withNationRecno];
+
+ int overallRating = nationRelation->ai_relation_level +
+ (int) nationRelation->good_relation_duration_rating +
+ (int) nationPtr->reputation +
+ nationPtr->military_rank_rating() +
+ trade_rating(withNationRecno) +
+ ai_trade_with_rating(withNationRecno)/2 +
+ nationPtr->total_alliance_military();
+
+ return overallRating;
+}
+//------ End of function Nation::ai_overall_relation_rating ------//
diff --git a/OAI_TOWN.cpp b/OAI_TOWN.cpp
new file mode 100644
index 0000000..e91f2df
--- /dev/null
+++ b/OAI_TOWN.cpp
@@ -0,0 +1,146 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_TOWN.CPP
+//Description: AI - processing town
+
+#include <ALL.h>
+#include <OUNIT.h>
+#include <OF_INN.h>
+#include <OTOWN.h>
+#include <OREGIONS.h>
+#include <ONATION.h>
+
+
+//--------- Begin of function Nation::think_town --------//
+//
+void Nation::think_town()
+{
+ optimize_town_race();
+}
+//---------- End of function Nation::think_town --------//
+
+
+//--------- Begin of function Nation::optimize_town_race --------//
+//
+// Optimize the distribution of different races in different towns.
+//
+void Nation::optimize_town_race()
+{
+ RegionStat* regionStat = region_array.region_stat_array;
+
+ for( int i=0 ; i<region_array.region_stat_count ; i++, regionStat++ )
+ {
+ if( regionStat->town_nation_count_array[nation_recno-1] > 0 )
+ optimize_town_race_region( regionStat->region_id );
+ }
+}
+//---------- End of function Nation::optimize_town_race --------//
+
+
+//--------- Begin of function Nation::optimize_town_race_region --------//
+//
+// Optimize the distribution of different races in different towns in
+// a single region.
+//
+void Nation::optimize_town_race_region(int regionId)
+{
+ //---- reckon the minority jobless pop of each race ----//
+
+ int racePopArray[MAX_RACE];
+
+ memset( racePopArray, 0, sizeof(racePopArray) );
+
+ int i, j, majorityRace;
+ Town* townPtr;
+
+ for( i=0 ; i<ai_town_count ; i++ )
+ {
+ townPtr = town_array[ ai_town_array[i] ];
+
+ if( townPtr->region_id != regionId )
+ continue;
+
+ majorityRace = townPtr->majority_race();
+
+ for( j=0 ; j<MAX_RACE ; j++ )
+ {
+ if( j+1 != majorityRace )
+ racePopArray[j] += townPtr->jobless_race_pop_array[j];
+ }
+ }
+
+ //--- locate for towns with minority being majority and those minority race can move to ---//
+
+ Town* destTown;
+
+ for( int raceId=0 ; raceId<MAX_RACE ; raceId++ )
+ {
+ if( racePopArray[raceId-1] == 0 ) // we don't have any minority of this race
+ continue;
+
+ destTown = NULL;
+
+ for( i=0 ; i<ai_town_count ; i++ )
+ {
+ townPtr = town_array[ ai_town_array[i] ];
+
+ if( townPtr->region_id != regionId )
+ continue;
+
+ if( !townPtr->is_base_town )
+ continue;
+
+ if( townPtr->majority_race() == raceId &&
+ townPtr->population < MAX_TOWN_POPULATION )
+ {
+ destTown = townPtr;
+ break;
+ }
+ }
+
+ if( !destTown )
+ continue;
+
+ //---- if there is a suitable town for minority to move to ---//
+
+ for( i=0 ; i<ai_town_count ; i++ )
+ {
+ townPtr = town_array[ ai_town_array[i] ];
+
+ if( townPtr->region_id != regionId )
+ continue;
+
+ //---- move minority units from towns -----//
+
+ int joblessCount = townPtr->jobless_race_pop_array[raceId-1];
+
+ if( joblessCount > 0 &&
+ townPtr->majority_race() != raceId )
+ {
+ int migrateCount = min(8, joblessCount); // migrate a maximum of 8 units at a time
+
+ add_action( destTown->loc_x1, destTown->loc_y1,
+ townPtr->loc_x1, townPtr->loc_y1, ACTION_AI_SETTLE_TO_OTHER_TOWN, 0, migrateCount);
+ }
+ }
+ }
+}
+//---------- End of function Nation::optimize_town_race_region --------//
diff --git a/OAI_TRAD.cpp b/OAI_TRAD.cpp
new file mode 100644
index 0000000..83797b7
--- /dev/null
+++ b/OAI_TRAD.cpp
@@ -0,0 +1,36 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_TRAD.CPP
+//Description: AI - process trading
+
+#include <ALL.h>
+#include <OU_CARA.h>
+#include <OF_MARK.h>
+#include <ONATION.h>
+
+//--------- Begin of function Nation::think_trading --------//
+
+void Nation::think_trading()
+{
+}
+//---------- End of function Nation::think_trading --------//
+
+
diff --git a/OAI_UNIT.cpp b/OAI_UNIT.cpp
new file mode 100644
index 0000000..4692c1e
--- /dev/null
+++ b/OAI_UNIT.cpp
@@ -0,0 +1,684 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAI_UNIT.CPP
+//Description: AI - unit related functions
+
+#include <ALL.h>
+#include <OUNIT.h>
+#include <OF_INN.h>
+#include <OTOWN.h>
+#include <ONATION.h>
+
+
+//-------- Begin of function Nation::get_skilled_unit -------//
+//
+// <int> skillId - the skill the selected unit should have
+// <int> raceId - the race the selected unit should have
+// (0 for any races)
+// <ActionNode*> actionNode - the ActionNode of the action that needs this skilled unit.
+//
+// return: <Unit*> skilledUnit - pointer to the skilled unit.
+//
+Unit* Nation::get_skilled_unit(int skillId, int raceId, ActionNode* actionNode)
+{
+ //--------- get a skilled unit --------//
+
+ Unit* skilledUnit;
+
+ if(actionNode->unit_recno) // a unit has started training previously
+ {
+ skilledUnit = unit_array[actionNode->unit_recno];
+ }
+ else
+ {
+ char resultFlag;
+ int xLoc, yLoc;
+
+ //---- for harbor, we have to get the land region id. instead of the sea region id. ----//
+
+ if( actionNode->action_mode==ACTION_AI_BUILD_FIRM &&
+ actionNode->action_para==FIRM_HARBOR )
+ {
+ int rc=0;
+
+ for( yLoc=actionNode->action_y_loc ; yLoc<actionNode->action_y_loc+3 ; yLoc++ )
+ {
+ for( xLoc=actionNode->action_x_loc ; xLoc<actionNode->action_x_loc+3 ; xLoc++ )
+ {
+ if( region_array[ world.get_region_id(xLoc,yLoc) ]->region_type == REGION_LAND )
+ {
+ rc=1;
+ break;
+ }
+ }
+
+ if( rc )
+ break;
+ }
+ }
+ else
+ {
+ xLoc = actionNode->action_x_loc;
+ yLoc = actionNode->action_y_loc;
+ }
+
+ //-----------------------------------------//
+
+ skilledUnit = find_skilled_unit(skillId, raceId, xLoc, yLoc, resultFlag, actionNode->action_id);
+
+ if( !skilledUnit ) // skilled unit not found
+ return NULL;
+ }
+
+ //------ if the unit is still in training -----//
+
+ if( !skilledUnit->is_visible() )
+ {
+ actionNode->next_retry_date = info.game_date + TOTAL_TRAIN_DAYS + 1;
+ return NULL; // continue processing this action after this date, this is used when training a unit for construction
+ }
+
+ err_when( !skilledUnit->race_id );
+
+ return skilledUnit;
+}
+//-------- End of function Nation::get_skilled_unit -------//
+
+
+//--------- Begin of function Nation::find_skilled_unit --------//
+//
+// <int> skillId - the skill the selected unit should have
+// <int> raceId - the race the selected unit should have
+// (0 for any races)
+// <short> destX, destY - location the unit move to
+// <char&> resultFlag - describle how to find the skilled unit
+// 0 - for unable to train unit,
+// 1 - for existing skilled unit
+// 2 - for unit hired from inn
+// 3 - for training unit in town (training is required)
+//
+// [int] actionId - the action id. of the action which
+// the unit should do after it has finished training.
+//
+// return the unit pointer pointed to the skilled unit
+//
+Unit* Nation::find_skilled_unit(int skillId, int raceId, short destX, short destY, char& resultFlag, int actionId)
+{
+ //----- try to find an existing unit with the required skill -----//
+
+ Unit *skilledUnit = NULL;
+ Unit *unitPtr;
+ Firm *firmPtr;
+ short curDist, minDist=0x1000;
+ int destRegionId = world.get_region_id(destX, destY);
+
+ for(int i=unit_array.size(); i>0; i--)
+ {
+ if(unit_array.is_deleted(i))
+ continue;
+
+ unitPtr = unit_array[i];
+
+ if( unitPtr->nation_recno!=nation_recno || !unitPtr->race_id )
+ continue;
+
+ if( raceId && unitPtr->race_id != raceId )
+ continue;
+
+ //---- if this unit is on a mission ----//
+
+ if( unitPtr->home_camp_firm_recno )
+ continue;
+
+ if( unitPtr->region_id() != destRegionId )
+ continue;
+
+ //----- if this is a mobile unit ------//
+
+ if( unitPtr->is_visible() )
+ {
+ if( !unitPtr->is_ai_all_stop() )
+ continue;
+
+ if( unitPtr->skill.skill_id==skillId &&
+ unitPtr->cur_action!=SPRITE_ATTACK && !unitPtr->ai_action_id )
+ {
+ curDist = m.points_distance(unitPtr->next_x_loc(), unitPtr->next_y_loc(), destX, destY);
+
+ if(curDist < minDist)
+ {
+ skilledUnit = unitPtr;
+ minDist = curDist;
+ }
+ }
+ }
+
+ //------- if this is an overseer ------//
+
+ else if( skillId==SKILL_LEADING && unitPtr->unit_mode==UNIT_MODE_OVERSEE )
+ {
+ firmPtr = firm_array[unitPtr->unit_mode_para];
+
+ if( firmPtr->region_id != destRegionId )
+ continue;
+
+ if( firmPtr->firm_id == FIRM_CAMP )
+ {
+ //--- if this military camp is going to be closed, use this overseer ---//
+
+ if( firmPtr->should_close_flag )
+ {
+ firmPtr->mobilize_overseer();
+ skilledUnit = unitPtr; // pick this overseer
+ break;
+ }
+ }
+ }
+ else if( skillId==SKILL_CONSTRUCTION && unitPtr->unit_mode==UNIT_MODE_CONSTRUCT ) // the unit is a residental builder for repairing the firm
+ {
+ firmPtr = firm_array[unitPtr->unit_mode_para];
+
+ if( !firmPtr->under_construction ) // only if the unit is repairing instead of constructing the firm
+ {
+ if( firmPtr->set_builder(0) ) // return 1 if the builder is mobilized successfully, 0 if the builder was killed because of out of space on the map
+ {
+ skilledUnit = unitPtr;
+ break;
+ }
+ }
+ }
+ }
+
+ //---------------------------------------------------//
+
+ if(skilledUnit)
+ {
+ resultFlag = 1;
+ }
+ else
+ {
+ //--- if no existing skilled unit found, try to hire one from inns ---//
+
+ int unitRecno = hire_unit(skillId, raceId, destX, destY); // this function will try going with hiring units that are better than training your own ones
+
+ if( unitRecno )
+ {
+ skilledUnit = unit_array[unitRecno];
+ resultFlag = 2;
+ }
+ else //--- if still cannot get a skilled unit, train one ---//
+ {
+ int trainTownRecno;
+
+ if( train_unit(skillId, raceId, destX, destY, trainTownRecno, actionId) )
+ resultFlag = 3;
+ else
+ resultFlag = 0;
+ }
+ }
+
+ err_when(skilledUnit && !skilledUnit->is_visible());
+ err_when(skilledUnit && skilledUnit->rank_id==RANK_KING && (skillId!=SKILL_CONSTRUCTION && skillId!=SKILL_LEADING));
+ err_when(skilledUnit && (skilledUnit->cur_action==SPRITE_DIE || skilledUnit->action_mode==ACTION_DIE));
+
+ return skilledUnit;
+}
+//---------- End of function Nation::find_skilled_unit --------//
+
+
+//--------- Begin of function Nation::hire_unit --------//
+//
+// <int> importantRating - importance of hiring the unit.
+//
+int Nation::ai_should_hire_unit(int importanceRating)
+{
+ if( !ai_inn_count ) // don't hire any body in the cash is too low
+ return 0;
+
+ return ai_should_spend(importanceRating + pref_hire_unit/5 - 10 ); // -10 to +10 depending on pref_hire_unit
+}
+//---------- End of function Nation::hire_unit --------//
+
+
+//--------- Begin of function Nation::hire_unit --------//
+//
+// <int> skillId - the skill the unit should have
+// <int> raceId - the race the selected unit should have
+// (0 for any races)
+// <short> destX - the x location the unit will move to
+// <short> destY - the y location the unit will move to
+//
+// hire unit with specified skill from an inn
+// return the unit pointer pointed to the skilled unit
+//
+// return: <int> recno of the unit recruited.
+//
+int Nation::hire_unit(int skillId, int raceId, short destX, short destY)
+{
+ if( !ai_should_hire_unit(20) ) // 20 - importance rating
+ return 0;
+
+ //-------------------------------------------//
+
+ FirmInn *firmInnPtr;
+ Firm *firmPtr;
+ InnUnit *innUnit;
+ Skill *innUnitSkill;
+ int i, j, innUnitCount, curRating, curFirmDist;
+ int bestRating=0, bestInnRecno=0, bestInnUnitId=0;
+ int destRegionId = world.get_region_id(destX, destY);
+
+ for(i=0; i<ai_inn_count; i++)
+ {
+ firmPtr = firm_array[ai_inn_array[i]];
+
+ if( firmPtr->region_id != destRegionId )
+ continue;
+
+ firmInnPtr = firmPtr->cast_to_FirmInn();
+
+ innUnitCount=firmInnPtr->inn_unit_count;
+
+ if( !innUnitCount )
+ continue;
+
+ innUnit = firmInnPtr->inn_unit_array + innUnitCount - 1;
+
+ curFirmDist = m.points_distance(firmPtr->center_x, firmPtr->center_y, destX, destY);
+
+ //------- check units in the inn ---------//
+
+ for(j=innUnitCount; j>0; j--, innUnit--)
+ {
+ innUnitSkill = &(innUnit->skill);
+
+ if( innUnitSkill->skill_id==skillId &&
+ (!raceId || unit_res[innUnit->unit_id]->race_id == raceId) &&
+ cash >= innUnit->hire_cost )
+ {
+ //----------------------------------------------//
+ // evalute a unit on:
+ // -its race, whether it's the same as the nation's race
+ // -the inn's distance from the destination
+ // -the skill level of the unit.
+ //----------------------------------------------//
+
+ curRating = innUnitSkill->skill_level
+ - (100-100*curFirmDist/MAX_WORLD_X_LOC);
+
+ if( unit_res[innUnit->unit_id]->race_id == race_id )
+ curRating += 50;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+
+ bestInnRecno = firmInnPtr->firm_recno;
+ bestInnUnitId = j;
+ }
+ }
+ }
+ }
+
+ //----------------------------------------------------//
+
+ if( bestInnUnitId )
+ {
+ firmPtr = firm_array[bestInnRecno];
+ firmInnPtr = firmPtr->cast_to_FirmInn();
+
+ return firmInnPtr->hire(bestInnUnitId);
+ }
+
+ return 0;
+}
+//---------- End of function Nation::hire_unit --------//
+
+
+//--------- Begin of function Nation::train_unit --------//
+//
+// <int> skillId - the skill the unit should have
+// <int> raceId - the race the selected unit should have
+// (0 for any races)
+// <short> destX - the x location the unit will move to
+// <short> destY - the y location the unit will move to
+//
+// <int&> trainTownRecno - the recno of the town where this unit is trained.
+//
+// [int] actionId - the action id. of the action which
+// the unit should do after it has finished training.
+//
+// return: <int> recno of the unit trained.
+//
+int Nation::train_unit(int skillId, int raceId, short destX, short destY, int& trainTownRecno, int actionId)
+{
+ //----- locate the best town for training the unit -----//
+
+ int i;
+ Town *townPtr;
+ int curDist, curRating, bestRating=0;
+ int destRegionId = world.get_loc(destX, destY)->region_id;
+
+ trainTownRecno = 0;
+
+ for(i=0; i<ai_town_count; i++)
+ {
+ townPtr = town_array[ai_town_array[i]];
+
+ if( !townPtr->jobless_population || townPtr->train_unit_recno || // no jobless population or currently a unit is being trained
+ !townPtr->has_linked_own_camp )
+ {
+ continue;
+ }
+
+ if( townPtr->region_id != destRegionId )
+ continue;
+
+ //--------------------------------------//
+
+ curDist = m.points_distance(townPtr->center_x, townPtr->center_y, destX, destY);
+
+ curRating = 100-100*curDist/MAX_WORLD_X_LOC;
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ trainTownRecno = townPtr->town_recno;
+ }
+ }
+
+ if( !trainTownRecno )
+ return 0;
+
+ //---------- train the unit ------------//
+
+ townPtr = town_array[trainTownRecno];
+
+ if( !raceId )
+ raceId = townPtr->pick_random_race(1, 1); // 1-pick has job units also, 1-pick spy units
+
+ if( !raceId )
+ return 0;
+
+ int unitRecno = townPtr->recruit(skillId, raceId, COMMAND_AI);
+
+ if( !unitRecno )
+ return 0;
+
+ townPtr->train_unit_action_id = actionId; // set train_unit_action_id so the unit can immediately execute the action when he has finished training.
+ return unitRecno;
+}
+//---------- End of function Nation::train_unit --------//
+
+
+//--------- Begin of function Nation::recruit_jobless_worker --------//
+//
+// <Firm*> destFirmPtr - the firm which the workers are recruited for.
+// [int] preferedRaceId - the prefered race id.
+//
+// return: <int> recno of the unit recruited.
+//
+int Nation::recruit_jobless_worker(Firm* destFirmPtr, int preferedRaceId)
+{
+ #define MIN_AI_TOWN_POP 8
+
+ int needSpecificRace, raceId; // the race of the needed unit
+
+ if( preferedRaceId )
+ {
+ raceId = preferedRaceId;
+ needSpecificRace = 1;
+ }
+ else
+ {
+ if( destFirmPtr->firm_id == FIRM_BASE ) // for seat of power, the race must be specific
+ {
+ raceId = firm_res.get_build(destFirmPtr->firm_build_id)->race_id;
+ needSpecificRace = 1;
+ }
+ else
+ {
+ raceId = destFirmPtr->majority_race();
+ needSpecificRace = 0;
+ }
+ }
+
+ if( !raceId )
+ return 0;
+
+ //----- locate the best town for recruiting the unit -----//
+
+ Town *townPtr;
+ int curDist, curRating, bestRating=0, bestTownRecno=0;
+
+ for( int i=0; i<ai_town_count; i++ )
+ {
+ townPtr = town_array[ai_town_array[i]];
+
+ err_when( townPtr->nation_recno != nation_recno );
+
+ if( !townPtr->jobless_population ) // no jobless population or currently a unit is being recruited
+ continue;
+
+ if( !townPtr->should_ai_migrate() ) // if the town is going to migrate, disregard the minimum population consideration
+ {
+ if( townPtr->population < MIN_AI_TOWN_POP ) // don't recruit workers if the population is low
+ continue;
+ }
+
+ if( !townPtr->has_linked_own_camp && townPtr->has_linked_enemy_camp ) // cannot recruit from this town if there are enemy camps but no own camps
+ continue;
+
+ if( townPtr->region_id != destFirmPtr->region_id )
+ continue;
+
+ //--- get the distance beteween town & the destination firm ---//
+
+ curDist = m.points_distance(townPtr->center_x, townPtr->center_y, destFirmPtr->center_x, destFirmPtr->center_y);
+
+ curRating = 100-100*curDist/MAX_WORLD_X_LOC;
+
+ //--- recruit units from non-base town first ------//
+
+ if( !townPtr->is_base_town )
+ curRating += 100;
+
+ //---- if the town has the race that the firm needs most ----//
+
+ if( townPtr->can_recruit(raceId) )
+ {
+ curRating += 50 + (int) townPtr->race_loyalty_array[raceId-1];
+ }
+ else
+ {
+ if( needSpecificRace ) // if the firm must need this race, don't consider the town if it doesn't have the race.
+ continue;
+ }
+
+ if( curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestTownRecno = townPtr->town_recno;
+ }
+ }
+
+ if( !bestTownRecno )
+ return 0;
+
+ //---------- recruit the unit ------------//
+
+ townPtr = town_array[bestTownRecno];
+
+ if( townPtr->recruitable_race_pop(raceId, 1) == 0 )
+ {
+ err_when( needSpecificRace );
+ raceId = townPtr->pick_random_race(0, 1); // 0-pick jobless only, 1-pick spy units
+
+ if( !raceId )
+ return 0;
+ }
+
+ //--- if the chosen race is not recruitable, pick any recruitable race ---//
+
+ if( !townPtr->can_recruit(raceId) )
+ {
+ //---- if the loyalty is too low to recruit, grant the town people first ---//
+
+ if( cash > 0 && townPtr->accumulated_reward_penalty < 10 )
+ townPtr->reward(COMMAND_AI);
+
+ //---- if the loyalty is still too low, return now ----//
+
+ if( !townPtr->can_recruit(raceId) )
+ return 0;
+ }
+
+ return townPtr->recruit(-1, raceId, COMMAND_AI);
+}
+//---------- End of function Nation::recruit_jobless_worker --------//
+
+
+//--------- Begin of function Nation::recruit_on_job_worker --------//
+//
+// Get skilled workers from existing firms who have labor more than
+// it really needs.
+//
+// <Firm*> destFirmPtr - the firm which the workers are recruited for.
+// [int] preferedRaceId - the prefered race id.
+//
+// return: <int> recno of the unit recruited.
+//
+int Nation::recruit_on_job_worker(Firm* destFirmPtr, int preferedRaceId)
+{
+ err_when( !firm_res[destFirmPtr->firm_id]->need_worker );
+
+ err_when( destFirmPtr->firm_id == FIRM_BASE ); // seat of power shouldn't call this function at all, as it doesn't handle the racial issue.
+
+ if( !preferedRaceId )
+ {
+ preferedRaceId = destFirmPtr->majority_race();
+
+ if( !preferedRaceId )
+ return 0;
+ }
+
+ //--- scan existing firms to see if any of them have excess workers ---//
+
+ int aiFirmCount; // reference vars for returning result
+ short* aiFirmArray = update_ai_firm_array(destFirmPtr->firm_id, 0, 0, aiFirmCount);
+ Firm* firmPtr, *bestFirmPtr=NULL;
+ int bestRating=0, curRating, curDistance;
+ int hasHuman;
+ Worker* workerPtr;
+
+ int i;
+ for( i=0 ; i<aiFirmCount ; i++ )
+ {
+ firmPtr = firm_array[aiFirmArray[i]];
+
+ err_when( firmPtr->firm_id != destFirmPtr->firm_id );
+
+ if( firmPtr->firm_recno == destFirmPtr->firm_recno )
+ continue;
+
+ if( firmPtr->region_id != destFirmPtr->region_id )
+ continue;
+
+ if( !firmPtr->ai_has_excess_worker() )
+ continue;
+
+ //-----------------------------------//
+
+ curDistance = m.points_distance( firmPtr->center_x, firmPtr->center_y,
+ destFirmPtr->center_x, destFirmPtr->center_y );
+
+ curRating = 100 - 100 * curDistance / MAX_WORLD_X_LOC;
+
+ workerPtr = firmPtr->worker_array;
+ hasHuman = 0;
+
+ for( int j=0 ; j<firmPtr->worker_count ; j++, workerPtr++ )
+ {
+ if( workerPtr->race_id )
+ hasHuman = 1;
+
+ if( workerPtr->race_id == preferedRaceId )
+ {
+ //---- can't recruit this unit if he lives in a foreign town ----//
+
+ if( workerPtr->town_recno &&
+ town_array[workerPtr->town_recno]->nation_recno != nation_recno )
+ {
+ continue;
+ }
+
+ //--------------------------//
+
+ curRating += 100;
+ break;
+ }
+ }
+
+ if( hasHuman && curRating > bestRating )
+ {
+ bestRating = curRating;
+ bestFirmPtr = firmPtr;
+ }
+ }
+
+ if( !bestFirmPtr )
+ return 0;
+
+ //------ mobilize a worker form the selected firm ----//
+
+ int workerId=0;
+
+ workerPtr = bestFirmPtr->worker_array;
+
+ for( i=1 ; i<=bestFirmPtr->worker_count ; i++, workerPtr++ )
+ {
+ //---- can't recruit this unit if he lives in a foreign town ----//
+
+ if( workerPtr->town_recno &&
+ town_array[workerPtr->town_recno]->nation_recno != nation_recno )
+ {
+ continue;
+ }
+
+ //--------------------------------//
+
+ if( workerPtr->race_id ) // if this is a human unit, take it first
+ workerId = i;
+
+ if( workerPtr->race_id == preferedRaceId ) // if we have a better one, take the better one
+ {
+ workerId = i;
+ break;
+ }
+ }
+
+ if( !workerId ) // this can happen if all the workers are foreign workers.
+ return 0;
+
+ return bestFirmPtr->mobilize_worker( workerId, COMMAND_AI );
+}
+//---------- End of function Nation::recruit_on_job_worker --------//
+
diff --git a/OANLINE.cpp b/OANLINE.cpp
new file mode 100644
index 0000000..b33a8ee
--- /dev/null
+++ b/OANLINE.cpp
@@ -0,0 +1,524 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Filename : OANLINE.CPP
+// Description : animated line
+// Onwership : Gilbert
+
+#include <OANLINE.h>
+#include <OVGABUF.h>
+#include <ALL.h>
+#include <math.h>
+
+// ----------- Define color table
+unsigned char AnimLine::init_color_code[ANIMCOLOR_PERIOD][ANIMLINE_PERIOD] =
+{
+// { 0x90, 0x93, 0x98, 0x9c, 0x9f, 0x9c, 0x98, 0x93 },
+// { 0x90, 0x93, 0x97, 0x9a, 0x9d, 0x9a, 0x97, 0x93 },
+// { 0x90, 0x92, 0x95, 0x97, 0x9a, 0x97, 0x95, 0x92 },
+// { 0x90, 0x92, 0x95, 0x97, 0x9a, 0x97, 0x95, 0x92 },
+// { 0x90, 0x93, 0x97, 0x9a, 0x9d, 0x9a, 0x97, 0x93 },
+// { 0x90, 0x93, 0x98, 0x9c, 0x9f, 0x9c, 0x98, 0x93 }
+
+ { 0xa6, 0xa5, 0xa4, 0xa4, 0x9c, 0xa4, 0xa4, 0xa5 },
+ { 0xa7, 0xa6, 0xa5, 0xa4, 0x9c, 0xa4, 0xa5, 0xa6 },
+ { 0xc7, 0xa7, 0xa6, 0x9c, 0x9f, 0x9c, 0xa6, 0xa7 },
+ { 0x90, 0x93, 0x98, 0x9c, 0x9f, 0x9c, 0x98, 0x93 },
+ { 0xc7, 0xa7, 0xa6, 0x9c, 0x9f, 0x9c, 0xa6, 0xa7 },
+ { 0xa7, 0xa6, 0xa5, 0xa4, 0x9c, 0xa4, 0xa5, 0xa6 }
+};
+
+unsigned char AnimLine::series_color_code[ANIMCOLOR_SERIES][ANIMLINE_PERIOD] =
+{
+ { 0x90, 0x93, 0x98, 0x9c, 0x9f, 0x9c, 0x98, 0x93 },
+ // { 0xa6, 0xa5, 0xa4, 0x9c, 0x9f, 0x9c, 0xa4, 0xa5 },
+ { 0xA0, 0xA1, 0xA2, 0xA3, 0xC3, 0xA3, 0xA2, 0xA0 },
+ { 0xA0, 0xC3, 0xA0, 0xC3, 0xA0, 0xC3, 0xA0, 0xC3 },
+};
+
+// ----------- Begin of function AnimLine::init ----------//
+//
+// <short> x1, y1, x2, y2 defines corners of the display area
+//
+void AnimLine::init(short x1, short y1, short x2, short y2)
+{
+ // set the boundary
+ bound_x1 = x1;
+ bound_y1 = y1;
+ bound_x2 = x2;
+ bound_y2 = y2;
+ phase = 0;
+ color_phase = 0;
+}
+// ----------- End of function AnimLine::init ----------//
+
+
+// ----------- Begin of function AnimLine::inc_phase ----------//
+//
+// called after each display cycle
+//
+void AnimLine::inc_phase()
+{
+ phase = (phase == 0 ? ANIMLINE_PERIOD : phase) - 1;
+ if( ++color_phase >= ANIMCOLOR_PERIOD*ANIMCOLOR_INNER_PERIOD )
+ color_phase = 0;
+}
+// ----------- End of function AnimLine::inc_phase ----------//
+
+
+// ----------- Begin of function AnimLine::draw_line ----------//
+//
+// clip the line before calling basic_line, basic_hline or basic_vline to draw
+// animatedFlag = TRUE for animated line, FALSE for stable line
+//
+// <VgaBuf *>vgabuf destination vgabuf
+// <short> x1,y1,x2,y2 draw from (x1,y1) to (x2,y2) on the screen
+// [int] animatedFlag whether the colors of the line is cycle (default:1 = animated)
+// [int] effectFlag effect : -1=dimming B/W, 0=non-dimming B/W, 1=non-dimming series (default : 0)
+//
+void AnimLine::draw_line(VgaBuf *vgabuf, short x1, short y1, short x2, short y2, int animatedFlag, int effectFlag)
+{
+ if( (x1 < bound_x1 && x2 < bound_x1) || ( x1 > bound_x2 && x2 > bound_x2) ||
+ (y1 < bound_y1 && y2 < bound_y1) || ( y1 > bound_y2 && y2 > bound_y2) )
+ return;
+
+ unsigned char* colorCode;
+ if( effectFlag == -1)
+ colorCode = init_color_code[color_phase/ANIMCOLOR_INNER_PERIOD];
+ else if( effectFlag >= 0 && effectFlag < ANIMCOLOR_SERIES)
+ colorCode = series_color_code[effectFlag];
+ else
+ {
+ err_here(); // invalid effectFlag
+ colorCode = init_color_code[0];
+ }
+
+ if( x1 >= bound_x1 && x1 <= bound_x2 && x2 >= bound_x1 && x2 <= bound_x2 &&
+ y1 >= bound_y1 && y1 <= bound_y2 && y2 >= bound_y1 && y2 <= bound_y2)
+ {
+ // already inside the screen
+ if( y1 == y2)
+ {
+ basic_hline(vgabuf, x1, x2, y2, animatedFlag, colorCode);
+ }
+ else if( x1 == x2)
+ {
+ basic_vline(vgabuf, x1, y1, y2, animatedFlag, colorCode);
+ }
+ else
+ {
+ basic_line(vgabuf, x1, y1, x2, y2, animatedFlag, colorCode);
+ }
+ }
+ else
+ {
+ if( y1 == y2)
+ {
+ // horizontal line
+ if( y1 < bound_y1 || y1 > bound_y2)
+ return;
+ if( x1 < bound_x1)
+ x1 = bound_x1;
+ else if( x1 > bound_x2)
+ x1 = bound_x2;
+
+ if( x2 < bound_x1)
+ x2 = bound_x1;
+ else if (x2 > bound_x2)
+ x2 = bound_x2;
+
+ basic_hline(vgabuf, x1, x2, y1, animatedFlag, colorCode);
+ }
+ else if( x1 == x2)
+ {
+ // vertical line
+ if( x1 < bound_x1 || x1 > bound_x2)
+ return;
+ if( y1 < bound_y1)
+ y1 = bound_y1;
+ else if( y1 > bound_y2)
+ y1 = bound_y2;
+
+ if( y2 < bound_y1)
+ y2 = bound_y1;
+ else if( y2 > bound_y2)
+ y2 = bound_y2;
+
+ basic_vline(vgabuf, x1, y1, y2, animatedFlag, colorCode);
+ }
+ else
+ {
+ // neither horizontal or vertical
+ int x1a, y1a, x1b, y1b;
+ int x2a, y2a, x2b, y2b;
+
+ // find intersection points for the first point
+ x1a = x1b = x1;
+ y1a = y1b = y1;
+ if( y1 < bound_y1)
+ {
+ y1a = bound_y1;
+ x1a = top_intercept( x1, y1, x2, y2);
+ }
+ if( y1 > bound_y2)
+ {
+ y1a = bound_y2;
+ x1a = bottom_intercept(x1, y1 ,x2, y2);
+ }
+ if( x1 < bound_x1)
+ {
+ x1b = bound_x1;
+ y1b = left_intercept(x1, y1, x2, y2);
+ }
+ if( x1 > bound_x2)
+ {
+ x1b = bound_x2;
+ y1b = right_intercept( x1, y1, x2, y2);
+ }
+
+ // find intersection points for the second point
+ x2a = x2b = x2;
+ y2a = y2b = y2;
+ if( y2 < bound_y1)
+ {
+ y2a = bound_y1;
+ x2a = top_intercept( x1, y1, x2, y2);
+ }
+ if( y2 > bound_y2)
+ {
+ y2a = bound_y2;
+ x2a = bottom_intercept(x1, y1 ,x2, y2);
+ }
+ if( x2 < bound_x1)
+ {
+ x2b = bound_x1;
+ y2b = left_intercept(x1, y1, x2, y2);
+ }
+ if( x2 > bound_x2)
+ {
+ x2b = bound_x2;
+ y2b = right_intercept( x1, y1, x2, y2);
+ }
+
+ // replace x1, y1
+ if( x1 < bound_x1 || x1 > bound_x2 || y1 < bound_y1 || y1 > bound_y2)
+ {
+ if( x1a < bound_x1 || x1a > bound_x2 || y1a < bound_y1 || y1a > bound_y2)
+ {
+ if( x1b < bound_x1 || x1b > bound_x2 || y1b < bound_y1 || y1b > bound_y2)
+ {
+ return;
+ }
+ else
+ {
+ x1 = x1b;
+ y1 = y1b;
+ }
+ }
+ else
+ {
+ x1 = x1a;
+ y1 = y1a;
+ }
+ }
+
+ // replace x2, y2
+ if( x2 < bound_x1 || x2 > bound_x2 || y2 < bound_y1 || y2 > bound_y2)
+ {
+ if( x2a < bound_x1 || x2a > bound_x2 || y2a < bound_y1 || y2a > bound_y2)
+ {
+ if( x2b < bound_x1 || x2b > bound_x2 || y2b < bound_y1 || y2b > bound_y2)
+ {
+ return;
+ }
+ else
+ {
+ x2 = x2b;
+ y2 = y2b;
+ }
+ }
+ else
+ {
+ x2 = x2a;
+ y2 = y2a;
+ }
+ }
+
+ // a segment may be horizontal or vertical, check again
+// if( y1 == y2)
+// basic_hline(vgabuf, x1, x2, y1, animatedFlag);
+// else if( x1 == x2)
+// basic_vline(vgabuf, x1, y1, y2, animatedFlag);
+// else
+ basic_line(vgabuf, x1, y1, x2, y2, animatedFlag, colorCode);
+ }
+ }
+}
+// ----------- End of function AnimLine::draw_line ----------//
+
+// ----------- Begin of function AnimLine::thick_line ----------//
+//
+// draw a thicker line
+//
+// <VgaBuf *>vgabuf destination vgabuf
+// <short> x1,y1,x2,y2 draw from (x1,y1) to (x2,y2) on the screen
+// [int] animatedFlag whether the colors of the line is cycle (default:1 = animated)
+// [int] effectFlag effect : -1=dimming B/W, 0=non-dimming B/W, 1=non-dimming series (default : 0)
+//
+void AnimLine::thick_line(VgaBuf *vgabuf, short x1, short y1, short x2, short y2, int animatedFlag, int effectFlag)
+{
+ if( abs(x2-x1) > abs(y2-y1) )
+ {
+ // likely to be horizontal, draw top, bottom and centre
+ draw_line( vgabuf, x1, y1-1, x2, y2-1, animatedFlag, effectFlag);
+ draw_line( vgabuf, x1, y1+1, x2, y2+1, animatedFlag, effectFlag);
+ }
+ else
+ {
+ // likely to be vertical, draw left right and centre
+ draw_line( vgabuf, x1-1, y1, x2-1, y2, animatedFlag, effectFlag);
+ draw_line( vgabuf, x1+1, y1, x2+1, y2, animatedFlag, effectFlag);
+ }
+ draw_line( vgabuf, x1, y1, x2, y2, animatedFlag, effectFlag);
+}
+// ----------- End of function AnimLine::thick_line ----------//
+
+
+// ----------- Begin of function AnimLine::basic_line ----------//
+//
+// draw a clipped line
+//
+void AnimLine::basic_line(VgaBuf *vgabuf, short x1, short y1, short x2, short y2, int animatedFlag,
+ unsigned char *colorCode)
+{
+ err_when( x1 < bound_x1 || x1 > bound_x2 );
+ err_when( x2 < bound_x1 || x2 > bound_x2 );
+ err_when( y1 < bound_y1 || y1 > bound_y2 );
+ err_when( y2 < bound_y1 || y2 > bound_y2 );
+
+ int dx = x2 - x1;
+ int dy = y2 - y1;
+
+ if( dy == 0)
+ {
+ basic_hline(vgabuf, x1, x2, y1, animatedFlag, colorCode);
+ return;
+ }
+
+ if( dx == 0)
+ {
+ basic_vline(vgabuf, x1, y1, y2, animatedFlag, colorCode);
+ return;
+ }
+ int d;
+ int inc_x = dx > 0 ? 1 : -1;
+ int inc_y = dy > 0 ? 1 : -1;
+ int lPitch = dy > 0 ? vgabuf->buf_pitch() : -vgabuf->buf_pitch();
+ unsigned char *bufPtr = (unsigned char *) vgabuf->buf_ptr(x1,y1);
+ short linePhase;
+ err_when( dx == 0 || dy == 0);
+
+ if( abs(dy) <= abs(dx) )
+ {
+ // draw gentle line
+ // use x as independent variable
+ dx = abs(dx);
+ dy = abs(dy);
+
+ d = 2 * dy - dx;
+ int x = x1-inc_x;
+ linePhase = animatedFlag ? phase : 0;
+ do
+ {
+ x += inc_x;
+ *bufPtr = colorCode[linePhase];
+ if(++linePhase >= ANIMLINE_PERIOD )
+ linePhase = 0;
+ bufPtr += inc_x;
+ if( d >= 0)
+ {
+ // y increase by 1
+ bufPtr += lPitch;
+ d += 2 * (dy - dx);
+ }
+ else
+ {
+ // y remain unchange;
+ d += 2 * dy;
+ }
+ } while ( x != x2);
+ }
+ else
+ {
+ // draw steep line
+ // use y as independent variable
+ dx = abs(dx);
+ dy = abs(dy);
+
+ d = 2 * dx - dy;
+ int y = y1 - inc_y;
+ linePhase = animatedFlag ? phase : 0;
+ do
+ {
+ y += inc_y;
+ *bufPtr = colorCode[linePhase];
+ if(++linePhase >= ANIMLINE_PERIOD )
+ linePhase = 0;
+ bufPtr += lPitch;
+ if( d >= 0)
+ {
+ // x increase by 1
+ bufPtr += inc_x;
+ d += 2 * (dx - dy);
+ }
+ else
+ {
+ // x remain unchange;
+ d += 2 * dx;
+ }
+ } while ( y != y2);
+ }
+}
+// ----------- End of function AnimLine::basic_line ----------//
+
+// ----------- Begin of function AnimLine::basic_hline ----------//
+//
+// draw a clipped horizontal line
+//
+void AnimLine::basic_hline(VgaBuf *vgabuf, short x1, short x2, short y1, int animatedFlag,
+ unsigned char *colorCode)
+{
+ err_when( x1 < bound_x1 || x1 > bound_x2 );
+ err_when( x2 < bound_x1 || x2 > bound_x2 );
+ err_when( y1 < bound_y1 || y1 > bound_y2 );
+
+ short linePhase = animatedFlag ? phase : 0;
+ unsigned char *bufPtr = (unsigned char *)vgabuf->buf_ptr(x1, y1);
+
+ if( x1 <= x2)
+ {
+ // from left to right
+ for(short x = x1; x <= x2; ++x, ++bufPtr)
+ {
+ *bufPtr = colorCode[linePhase];
+ if(++linePhase >= ANIMLINE_PERIOD )
+ linePhase = 0;
+ }
+ }
+ else
+ {
+ // from right to left
+ for( short x = x1; x >= x2; --x, --bufPtr)
+ {
+ *bufPtr = colorCode[linePhase];
+ if(++linePhase >= ANIMLINE_PERIOD )
+ linePhase = 0;
+ }
+ }
+}
+// ----------- End of function AnimLine::basic_hline ----------//
+
+// ----------- Begin of function AnimLine::basic_vline ----------//
+//
+// draw a clipped vertical line
+//
+void AnimLine::basic_vline(VgaBuf *vgabuf, short x1, short y1, short y2, int animatedFlag,
+ unsigned char *colorCode)
+{
+ err_when( x1 < bound_x1 || x1 > bound_x2 );
+ err_when( y1 < bound_y1 || y1 > bound_y2 );
+ err_when( y2 < bound_y1 || y2 > bound_y2 );
+
+ short linePhase = animatedFlag ? phase : 0;
+ unsigned char *bufPtr = (unsigned char *) vgabuf->buf_ptr(x1, y1);
+ int lPitch = vgabuf->buf_pitch();
+
+ if( y1 <= y2)
+ {
+ // from top to bottom
+ for(short y = y1; y <= y2; ++y, bufPtr += lPitch)
+ {
+ *bufPtr = colorCode[linePhase];
+ if(++linePhase >= ANIMLINE_PERIOD )
+ linePhase = 0;
+ }
+ }
+ else
+ {
+ // from bottom to top
+ for( short y = y1; y >= y2; --y, bufPtr -= lPitch)
+ {
+ *bufPtr = colorCode[linePhase];
+ if(++linePhase >= ANIMLINE_PERIOD )
+ linePhase = 0;
+ }
+ }
+}
+// ----------- End of function AnimLine::basic_vline ----------//
+
+
+// ----------- Begin of function AnimLine::get_series_color_array ----------//
+unsigned char *AnimLine::get_series_color_array(int effectFlag)
+{
+ unsigned char *colorCode;
+ if( effectFlag == -1)
+ colorCode = init_color_code[color_phase/ANIMCOLOR_INNER_PERIOD];
+ else if( effectFlag >= 0 && effectFlag < ANIMCOLOR_SERIES)
+ colorCode = series_color_code[effectFlag];
+ else
+ {
+ err_here(); // invalid effectFlag
+ colorCode = init_color_code[0];
+ }
+
+ return colorCode;
+}
+// ----------- End of function AnimLine::get_series_color_array ----------//
+
+// ----------- Begin of function AnimLine::top_intercept ----------//
+short AnimLine::top_intercept( short x1, short y1, short x2, short y2)
+{
+ return (bound_y1-y1) * (x2-x1) / (y2-y1) + x1;
+}
+// ----------- End of function AnimLine::top_intercept ----------//
+
+
+// ----------- Begin of function AnimLine::bottom_intercept ----------//
+short AnimLine::bottom_intercept( short x1, short y1, short x2, short y2)
+{
+ return (bound_y2-y1) * (x2-x1) / (y2-y1) + x1;
+}
+// ----------- End of function AnimLine::bottom_intercept ----------//
+
+
+// ----------- Begin of function AnimLine::left_intercept ----------//
+short AnimLine::left_intercept( short x1, short y1, short x2, short y2)
+{
+ return (bound_x1-x1) * (y2-y1) / (x2-x1) + y1;
+}
+// ----------- End of function AnimLine::left_intercept ----------//
+
+
+// ----------- Begin of function AnimLine::right_intercept ----------//
+short AnimLine::right_intercept( short x1, short y1, short x2, short y2)
+{
+ return (bound_x2-x1) * (y2-y1) / (x2-x1) + y1;
+}
+// ----------- End of function AnimLine::right_intercept ----------//
diff --git a/OAUDIO.cpp b/OAUDIO.cpp
new file mode 100644
index 0000000..1fe2132
--- /dev/null
+++ b/OAUDIO.cpp
@@ -0,0 +1,2091 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OAUDIO.CPP
+//Description : Object Midi Audio and Digitized Sound
+//Ownership : Gilbert
+
+#include <windows.h>
+#include <windowsx.h>
+#include <commdlg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <direct.h>
+#include <mmsystem.h>
+#include <string.h>
+#include <limits.h>
+
+#include <OSYS.h>
+#include <OBOX.h>
+#include <OAUDIO.h>
+#include <OVGALOCK.h>
+
+//---------------- Define constant ------------------//
+//
+// DirectSoundBuffer size = LWAV_STREAM_BUFSIZ * LWAV_BANKS
+// if it is going to play a high transfer rate wave
+// (e.g. 16-bit 44.1kHz Stereo), increase LWAV_STREAM_BUFSIZ
+//
+//---------------------------------------------------//
+
+#define LWAV_STREAM_BUFSIZ 0x1000
+#define LWAV_BANKS 4
+#define LOOPWAV_STREAM_BUFSIZ 0x1000
+#define LOOPWAV_BANKS 4
+
+#ifndef DSBCAPS_CTRLDEFAULT
+ #define DSBCAPS_CTRLDEFAULT (DSBCAPS_CTRLFREQUENCY|DSBCAPS_CTRLPAN|DSBCAPS_CTRLVOLUME)
+#endif
+
+
+//---------- Define static variables ---------//
+
+static MCI_PLAY_PARMS mci_play;
+static MCI_OPEN_PARMS mci_open;
+static MCI_SET_PARMS mci_set;
+
+//--------- Begin of function wavefile_offset -------//
+//
+// find the "data" tag in a wave file
+//
+static char * wavefile_data(char *wavfile_buf)
+{
+ //----- position at WAVEfmt tag size ------//
+
+ char *p = wavfile_buf+0x10;
+ DWORD tagSize=*(DWORD *)p;
+
+ //-------- go to next tag field -----------//
+
+ for( p += sizeof(DWORD)+tagSize; strncmp(p, "data", 4) != 0;
+ p += sizeof(DWORD)+tagSize)
+ {
+ p += 4; // pointing at size of tag field
+ tagSize = *(DWORD *)p; // get the size of this tage field
+
+ if(p - wavfile_buf > 128)
+ return NULL;
+ }
+
+ //----- p pointing at the start of "data" tag ------//
+
+ return p;
+}
+//--------- End of function wavefile_offset------------//
+
+
+//--------- Begin of function Audio::Audio ----------//
+
+Audio::Audio()
+{
+ init_flag = 0;
+}
+//--------- Begin of function Audio::Audio ----------//
+
+
+//--------- Begin of function Audio::~Audio ----------//
+
+Audio::~Audio()
+{
+ deinit();
+}
+//--------- Begin of function Audio::~Audio ----------//
+
+
+//--------- Begin of function Audio::init ----------//
+//
+// Initialize the mid driver
+//
+// return : <int> 1 - initialized successfully
+// 0 - init fail
+//
+int Audio::init()
+{
+ //-------- init vars -----------//
+
+ run_yield = 0;
+ mid_init_flag = 0; // the init flag is on when the driver is initialized successfully
+ wav_init_flag = 0;
+ cd_init_flag = 0;
+
+ mid_flag = 1;
+ wav_flag = 1;
+ cd_flag = 1;
+
+ mid_buf = NULL;
+ wav_buf = NULL;
+
+ mid_buf_size = 0;
+ wav_buf_size = 0;
+
+ int i;
+ for(i = 0; i < MAX_WAV_CHANNEL; ++i)
+ {
+ lp_wav_ch_dsb[i] = NULL;
+ wav_serial_no[i] = 0;
+ }
+ max_lwav_serial_no = 0;
+
+ for(i=0; i < MAX_LONG_WAV_CH; ++i)
+ {
+ lp_lwav_ch_dsb[i] = NULL;
+ lwav_serial_no[i] = 0;
+ lwav_fileptr[i] = NULL;
+ }
+ max_lwav_serial_no = 0;
+
+ for(i=0; i < MAX_LOOP_WAV_CH; ++i)
+ {
+ lp_loop_ch_dsb[i] = NULL;
+ loopwav_fileptr[i] = NULL;
+ }
+
+ // wav_volume = 0;
+ wav_volume = 100; // 0(slient) - 100(loudest)
+
+ //--------- init devices ----------//
+
+ if( init_wav() )
+ {
+ wav_res.init( DIR_RES"A_WAVE2.RES", 0, 0 ); // 2nd 0-don't read all, 3rd 0-don't use vga buffer
+ wav_buf = mem_add(DEFAULT_WAV_BUF_SIZE);
+ wav_buf_size = DEFAULT_WAV_BUF_SIZE;
+ }
+/*
+ if( init_mid() )
+ {
+ mid_res.init( DIR_RES"A_MIDI.RES", 0, 0 ); // 2nd 0-don't read all, 3rd 0-don't use vga buffer
+ mid_buf = mem_add(DEFAULT_MID_BUF_SIZE);
+ mid_buf_size = DEFAULT_MID_BUF_SIZE;
+ }
+*/
+ init_cd();
+
+ //----------------------------------//
+
+ init_flag = wav_init_flag || cd_init_flag;
+
+ return 1;
+}
+//--------- End of function Audio::init ----------//
+
+
+//--------- Begin of function Audio::deinit ----------//
+
+void Audio::deinit()
+{
+ if( init_flag )
+ {
+ //------- deinit vars --------//
+
+ run_yield = 0;
+ init_flag = 0;
+
+ //------- deinit devices -------//
+
+ deinit_wav();
+ deinit_mid();
+ deinit_cd();
+ }
+}
+//--------- End of function Audio::deinit ----------//
+
+
+//--------- Begin of function Audio::init_wav ----------//
+//
+// Initialize digitized wav driver
+//
+// return : <int> 1 - initialized successfully
+// 0 - init fail
+//
+int Audio::init_wav()
+{
+ if( wav_init_flag )
+ return 1;
+
+ //-------- create DirectSound object -------//
+
+ HRESULT rc=DirectSoundCreate(NULL, &lp_direct_sound, NULL);
+
+ //------------------------------------------//
+
+ if( rc==DS_OK ) // Create succeeded
+ {
+ lp_direct_sound->SetCooperativeLevel(sys.main_hwnd, DSSCL_NORMAL);
+ wav_init_flag=1;
+ }
+
+ return wav_init_flag;
+}
+//--------- End of function Audio::init_wav ----------//
+
+
+//--------- Begin of function Audio::init_mid ----------//
+//
+// Initialize MIDI mid driver
+//
+// return : <int> 1 - initialized successfully
+// 0 - init fail
+//
+int Audio::init_mid()
+{
+ if( mid_init_flag )
+ return 1;
+
+
+ //.. insert code here ...//
+
+
+ mid_init_flag=1;
+
+ return 1;
+}
+//--------- End of function Audio::init_mid ----------//
+
+
+//--------- Begin of function Audio::init_cd ----------//
+//
+// Initialize the audio CD player
+//
+// return : <int> 1 - initialized successfully
+// 0 - init fail
+//
+int Audio::init_cd()
+{
+ mci_open.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_CD_AUDIO;
+
+ if( mciSendCommand( NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID,
+ (DWORD) (LPVOID) &mci_open) == 0 ||
+ mciSendCommand( NULL, MCI_OPEN, MCI_OPEN_TYPE |
+ MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE,
+ (DWORD) (LPVOID) &mci_open)== 0 )
+ {
+ mci_set.dwTimeFormat = MCI_FORMAT_TMSF;
+
+ mciSendCommand( mci_open.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT,
+ (DWORD) (LPVOID) &mci_set);
+
+ cd_init_flag = 1;
+ return 1;
+ }
+
+ cd_init_flag = 0;
+ return 0;
+}
+//--------- End of function Audio::init_cd ----------//
+
+
+//--------- Begin of function Audio::deinit_cd ----------//
+
+void Audio::deinit_cd()
+{
+ if( cd_init_flag )
+ {
+ stop_cd();
+
+ mciSendString ("close cdaudio", NULL, 0, NULL);
+
+ cd_init_flag = 0;
+ }
+}
+//--------- End of function Audio::deinit_cd ----------//
+
+
+//--------- Begin of function Audio::deinit_wav ----------//
+
+void Audio::deinit_wav()
+{
+ stop_wav();
+
+ if( wav_buf )
+ {
+ mem_del(wav_buf);
+ wav_buf = NULL;
+ }
+
+ if(wav_init_flag)
+ {
+ lp_direct_sound->Release();
+ wav_init_flag = 0;
+ }
+}
+//--------- End of function Audio::deinit_wav ----------//
+
+
+//--------- Begin of function Audio::deinit_mid ----------//
+
+void Audio::deinit_mid()
+{
+ if( !mid_init_flag )
+ return;
+
+ stop_mid();
+
+ //.. insert code here ...//
+ mem_del(mid_buf);
+ mid_buf = NULL;
+
+ mid_init_flag = 0;
+}
+//--------- End of function Audio::deinit_mid ----------//
+
+
+//------- Begin of function Audio::play_mid -------//
+//
+// Play a midi mid from the mid resource file
+//
+// <char*> midName = name of the mid in the resource file
+//
+// return : <int> 1 - mid loaded and is playing
+// 0 - mid not played
+//
+int Audio::play_mid(char* midName)
+{
+ if( !mid_init_flag || !mid_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ stop_mid(); // stop currently playing mid if any
+
+ //------ Load mid file -------//
+
+ int dataSize;
+
+ File* filePtr = mid_res.get_file(midName, dataSize);
+
+ if( !filePtr )
+ return 0;
+
+ if( dataSize > mid_buf_size )
+ {
+ mid_buf_size = dataSize;
+ mid_buf = mem_resize( mid_buf, mid_buf_size );
+ }
+
+ if( !filePtr->file_read( mid_buf, dataSize ) )
+ return 0;
+
+ //-------- Play mid file --------//
+
+ //.. insert code here ...//
+
+
+ return 1;
+}
+//------- End of function Audio::play_mid -------//
+
+
+//------- Begin of function Audio::stop_mid -------//
+//
+void Audio::stop_mid()
+{
+ if( !mid_init_flag || !mid_flag )
+ return;
+
+ //.. insert code here ...//
+
+ mciSendCommand(mci_open.wDeviceID, MCI_STOP, NULL, NULL);
+}
+//------- End of function Audio::stop_mid -------//
+
+
+//------- Begin of function Audio::play_wav -------//
+//
+// Play digitized wav from the wav resource file
+//
+// <char*> wavName = name of the wav in the resource file
+// long vol = volume (0 to 100)
+// long pan = pan (-10000 to 10000)
+//
+// return : <int> non-zero - wav loaded and is playing, return a serial no. to be referred in stop_wav and is_wav_playing
+// 0 - wav not played
+//
+int Audio::play_wav(char* wavName, DsVolume dsVolume)
+{
+/*
+ //---- redirect to play_long_wav -------//
+
+ String str;
+
+ str = DIR_SOUND;
+ str += wavName;
+ str += ".WAV";
+
+ if( m.is_file_exist(str) )
+ return play_long_wav(str);
+ else
+ return 0;
+
+ //------------------------------------//
+*/
+ if( !wav_init_flag || !wav_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ //-------- Load wav file header-------//
+ int dataSize;
+ DWORD wavDataOffset, wavDataLength;
+
+ File* filePtr = wav_res.get_file(wavName, dataSize);
+
+ if( !filePtr )
+ return 0;
+
+ // load small part of the wave file (first 128 bytes) enough to hold
+ // the hold header
+//#define LOAD_FULL_WAVE
+#ifdef LOAD_FULL_WAVE
+ if( dataSize > wav_buf_size )
+ {
+ wav_buf_size = dataSize;
+ wav_buf = mem_resize( wav_buf, wav_buf_size );
+ }
+ if( !filePtr->file_read( wav_buf, dataSize))
+#else
+ if( !filePtr->file_read( wav_buf, 128 ) )
+#endif
+ return 0;
+
+// short-cut to test play_resided_wave()
+#ifdef LOAD_FULL_WAVE
+ return play_resided_wav(wav_buf);
+#endif
+
+ // determine the wave data offset and length
+ char * dataTag = wavefile_data(wav_buf);
+ if (!dataTag)
+ {
+ err_now("Invalid wave file format");
+ return 0; // invalid RIFF WAVE format
+ }
+
+ wavDataOffset = (dataTag - wav_buf) + 4 + sizeof(DWORD);
+ wavDataLength = *(DWORD *)(dataTag+4);
+
+#ifndef LOAD_FULL_WAVE
+ // seek to the start of wave data
+ filePtr->file_seek(wavDataOffset-128,FILE_CURRENT);
+#endif
+
+ //------- Create DirectSoundBuffer to store a wave ------//
+ LPDIRECTSOUNDBUFFER lpDsb;
+ DSBUFFERDESC dsbDesc;
+ HRESULT hr;
+ DWORD dsbStatus;
+
+ // set up DSBUFFERDESC structure
+ memset(&dsbDesc, 0, sizeof(DSBUFFERDESC)); // zero it out
+ dsbDesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbDesc.dwFlags = DSBCAPS_CTRLDEFAULT; // Need defaul controls (pan, volume, frequency)
+ dsbDesc.dwBufferBytes = wavDataLength;
+ dsbDesc.lpwfxFormat = (LPWAVEFORMATEX) (wav_buf+0x14);
+ // ------- assign buffer to a channel number ----------//
+ lpDsb = NULL;
+ int chanNum;
+ for( chanNum = 0; chanNum < MAX_WAV_CHANNEL; ++chanNum)
+ if(lp_wav_ch_dsb[chanNum] == NULL || (lp_wav_ch_dsb[chanNum]->GetStatus(&dsbStatus),
+ !(dsbStatus & DSBSTATUS_PLAYING)))
+ {
+ if(lp_wav_ch_dsb[chanNum])
+ {
+ lp_wav_ch_dsb[chanNum]->Release();
+ lp_wav_ch_dsb[chanNum] = NULL;
+ }
+ // found an idle channel, create DirectSoundBuffer
+ hr = lp_direct_sound->CreateSoundBuffer(&dsbDesc, &lpDsb, NULL);
+ if (DS_OK != hr)
+ {
+ // failed!
+ err_now("Cannot create DirectSoundBuffer");
+ return 0;
+ }
+ lp_wav_ch_dsb[chanNum] = lpDsb;
+ break;
+ }
+ if( chanNum >= MAX_WAV_CHANNEL)
+ {
+ return 0;
+ }
+ // Note : if not found, just play the sound, don't play the sound
+ // increase MAX_WAV_CHANNEL
+
+ //------- copy sound data to DirectSoundBuffer--------//
+ // unlock vga_front
+ VgaFrontLock vgaLock;
+
+ // lock the buffer first
+ LPVOID lpvPtr1;
+ DWORD dwBytes1;
+ LPVOID lpvPtr2;
+ DWORD dwBytes2;
+
+ hr = lpDsb->Lock(0, wavDataLength, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
+ if(DS_OK != hr)
+ {
+ // fail to lock
+ err_now("Cannot lock DirectSoundBuffer");
+ return 0;
+ }
+
+ // write to pointers
+#ifdef LOAD_FULL_WAVE
+ memcpy(lpvPtr1, wav_buf+wavDataOffset, dwBytes1);
+#else
+ filePtr->file_read(lpvPtr1, dwBytes1);
+#endif
+ if(lpvPtr2)
+ {
+#ifdef LOAD_FULL_WAVE
+ memcpy(lpvPtr2, wav_buf+wavDataOffset+dwBytes1, dwBytes2);
+#else
+ filePtr->file_read(lpvPtr2, dwBytes2);
+#endif
+ }
+ // unlock data back
+ hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
+ if(DS_OK != hr)
+ {
+ // fail to unlock
+ err_now("Cannot unlock DirectSoundBuffer");
+ return 0;
+ }
+
+ //------- Set volume and pan -----------//
+ lpDsb->SetVolume(dsVolume.ds_vol);
+ lpDsb->SetPan(dsVolume.ds_pan);
+
+ //------- Play wav file --------//
+ if(lpDsb->Play(0, 0, 0) != DS_OK)
+ {
+ // fail to play
+ err_now("Cannot play DirectSoundBuffer");
+ return 0;
+ }
+
+ return wav_serial_no[chanNum] = assign_serial(max_wav_serial_no);
+}
+//------- End of function Audio::play_wav -------//
+
+
+//------- Begin of function Audio::play_wav -------//
+//
+// Play digitized wav from the wav resource file
+//
+// short resIdx = index of wave file in A_WAVE2.RES
+// long vol = volume (0 to 100)
+// long pan = pan (-10000 to 10000)
+//
+// return : <int> 1 - wav loaded and is playing
+// 0 - wav not played
+//
+int Audio::play_wav(short resIdx, DsVolume dsVolume)
+{
+ if( !wav_init_flag || !wav_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ //-------- Load wav file header-------//
+ int dataSize;
+ DWORD wavDataOffset, wavDataLength;
+
+ File* filePtr = wav_res.get_file(resIdx, dataSize);
+
+ if( !filePtr )
+ return 0;
+
+ // load small part of the wave file (first 128 bytes) enough to hold
+ // the hold header
+#ifdef LOAD_FULL_WAVE
+ if( dataSize > wav_buf_size )
+ {
+ wav_buf_size = dataSize;
+ wav_buf = mem_resize( wav_buf, wav_buf_size );
+ }
+ if( !filePtr->file_read( wav_buf, dataSize))
+#else
+ if( !filePtr->file_read( wav_buf, 128 ) )
+#endif
+ return 0;
+
+// short-cut to test play_resided_wave()
+#ifdef LOAD_FULL_WAVE
+ return play_resided_wav(wav_buf);
+#endif
+
+ // determine the wave data offset and length
+ char * dataTag = wavefile_data(wav_buf);
+ if (!dataTag)
+ {
+ err_now("Invalid wave file format");
+ return 0; // invalid RIFF WAVE format
+ }
+
+ wavDataOffset = (dataTag - wav_buf) + 4 + sizeof(DWORD);
+ wavDataLength = *(DWORD *)(dataTag+4);
+
+#ifndef LOAD_FULL_WAVE
+ // seek to the start of wave data
+ filePtr->file_seek(wavDataOffset-128,FILE_CURRENT);
+#endif
+
+ //------- Create DirectSoundBuffer to store a wave ------//
+ LPDIRECTSOUNDBUFFER lpDsb;
+ DSBUFFERDESC dsbDesc;
+ HRESULT hr;
+ DWORD dsbStatus;
+
+ // set up DSBUFFERDESC structure
+ memset(&dsbDesc, 0, sizeof(DSBUFFERDESC)); // zero it out
+ dsbDesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbDesc.dwFlags = DSBCAPS_CTRLDEFAULT; // Need defaul controls (pan, volume, frequency)
+ dsbDesc.dwBufferBytes = wavDataLength;
+ dsbDesc.lpwfxFormat = (LPWAVEFORMATEX) (wav_buf+0x14);
+ // ------- assign buffer to a channel number ----------//
+ lpDsb = NULL;
+ int chanNum;
+ for( chanNum = 0; chanNum < MAX_WAV_CHANNEL; ++chanNum)
+ if(lp_wav_ch_dsb[chanNum] == NULL || (lp_wav_ch_dsb[chanNum]->GetStatus(&dsbStatus),
+ !(dsbStatus & DSBSTATUS_PLAYING)))
+ {
+ if(lp_wav_ch_dsb[chanNum])
+ {
+ lp_wav_ch_dsb[chanNum]->Release();
+ lp_wav_ch_dsb[chanNum] = NULL;
+ }
+ // found an idle channel, create DirectSoundBuffer
+ hr = lp_direct_sound->CreateSoundBuffer(&dsbDesc, &lpDsb, NULL);
+ if (DS_OK != hr)
+ {
+ // failed!
+ err_now("Cannot create DirectSoundBuffer");
+ return 0;
+ }
+ lp_wav_ch_dsb[chanNum] = lpDsb;
+ break;
+ }
+ if( chanNum >= MAX_WAV_CHANNEL)
+ {
+ return 0;
+ }
+ // Note : if not found, just play the sound, don't play the sound
+ // increase MAX_WAV_CHANNEL
+
+ //------- copy sound data to DirectSoundBuffer--------//
+ // unlock vga_front
+ VgaFrontLock vgaLock;
+
+ // lock the buffer first
+ LPVOID lpvPtr1;
+ DWORD dwBytes1;
+ LPVOID lpvPtr2;
+ DWORD dwBytes2;
+
+ hr = lpDsb->Lock(0, wavDataLength, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
+ if(DS_OK != hr)
+ {
+ // fail to lock
+ err_now("Cannot lock DirectSoundBuffer");
+ return 0;
+ }
+
+ // write to pointers
+#ifdef LOAD_FULL_WAVE
+ memcpy(lpvPtr1, wav_buf+wavDataOffset, dwBytes1);
+#else
+ filePtr->file_read(lpvPtr1, dwBytes1);
+#endif
+ if(lpvPtr2)
+ {
+#ifdef LOAD_FULL_WAVE
+ memcpy(lpvPtr2, wav_buf+wavDataOffset+dwBytes1, dwBytes2);
+#else
+ filePtr->file_read(lpvPtr2, dwBytes2);
+#endif
+ }
+ // unlock data back
+ hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
+ if(DS_OK != hr)
+ {
+ // fail to unlock
+ err_now("Cannot unlock DirectSoundBuffer");
+ return 0;
+ }
+
+ //------- Set volume and pan -----------//
+ lpDsb->SetVolume(dsVolume.ds_vol);
+ lpDsb->SetPan(dsVolume.ds_pan);
+
+ //------- Play wav file --------//
+ if(lpDsb->Play(0, 0, 0) != DS_OK)
+ {
+ // fail to play
+ err_now("Cannot play DirectSoundBuffer");
+ return 0;
+ }
+
+ return wav_serial_no[chanNum] = assign_serial(max_wav_serial_no);
+}
+//------- End of function Audio::play_wav -------//
+
+
+//------- Begin of function Audio::play_resided_wav -------//
+//
+// Play digitized wav from the wav file in memory
+//
+// <char*> wavBuf = point to the wav in memory
+// long vol = volume (0 to 100)
+// long pan = pan (-10000 to 10000)
+//
+// return : <int> 1 - wav loaded and is playing
+// 0 - wav not played
+//
+int Audio::play_resided_wav(char* wavBuf, DsVolume dsVolume)
+{
+ if( !wav_init_flag || !wav_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ //-------- Load wav file header-------//
+ DWORD wavDataOffset, wavDataLength;
+
+ // determine the wave data offset and length
+ char * dataTag = wavefile_data(wavBuf);
+ if (!dataTag)
+ {
+ err_now("Invalid wave file format");
+ return 0; // invalid RIFF WAVE format
+ }
+
+ wavDataOffset = (dataTag - wavBuf) + 4 + sizeof(DWORD);
+ wavDataLength = *(DWORD *)(dataTag+4);
+
+ //------- Create DirectSoundBuffer to store a wave ------//
+ LPDIRECTSOUNDBUFFER lpDsb;
+ DSBUFFERDESC dsbDesc;
+ HRESULT hr;
+ DWORD dsbStatus;
+
+ // set up DSBUFFERDESC structure
+ memset(&dsbDesc, 0, sizeof(DSBUFFERDESC)); // zero it out
+ dsbDesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbDesc.dwFlags = DSBCAPS_CTRLDEFAULT; // Need defaul controls (pan, volume, frequency)
+ dsbDesc.dwBufferBytes = wavDataLength;
+ dsbDesc.lpwfxFormat = (LPWAVEFORMATEX) (wavBuf+0x14);
+ // ------- assign buffer to a channel number ----------//
+ lpDsb = NULL;
+ int chanNum;
+ for( chanNum = 0; chanNum < MAX_WAV_CHANNEL; ++chanNum)
+ if(lp_wav_ch_dsb[chanNum] == NULL || (lp_wav_ch_dsb[chanNum]->GetStatus(&dsbStatus),
+ !(dsbStatus & DSBSTATUS_PLAYING)))
+ {
+ if(lp_wav_ch_dsb[chanNum])
+ {
+ lp_wav_ch_dsb[chanNum]->Release();
+ lp_wav_ch_dsb[chanNum] = NULL;
+ }
+ // found an idle channel, create DirectSoundBuffer
+ hr = lp_direct_sound->CreateSoundBuffer(&dsbDesc, &lpDsb, NULL);
+ if (DS_OK != hr)
+ {
+ // failed!
+ err_now("Cannot create DirectSoundBuffer");
+ return 0;
+ }
+ lp_wav_ch_dsb[chanNum] = lpDsb;
+ break;
+ }
+ if( chanNum >= MAX_WAV_CHANNEL)
+ {
+ return 0;
+ }
+ // Note : if not found, just play the sound, don't play the sound
+ // increase MAX_WAV_CHANNEL
+
+ //------- copy sound data to DirectSoundBuffer--------//
+ // unlock vga_front
+ VgaFrontLock vgaLock;
+
+ // lock the buffer first
+ LPVOID lpvPtr1;
+ DWORD dwBytes1;
+ LPVOID lpvPtr2;
+ DWORD dwBytes2;
+
+ hr = lpDsb->Lock(0, wavDataLength, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
+ if(DS_OK != hr)
+ {
+ // fail to lock
+ err_now("Cannot lock DirectSoundBuffer");
+ return 0;
+ }
+
+ // write to pointers
+ memcpy(lpvPtr1, wavBuf+wavDataOffset, dwBytes1);
+ if(lpvPtr2)
+ {
+ memcpy(lpvPtr2, wavBuf+wavDataOffset+dwBytes1, dwBytes2);
+ }
+ // unlock data back
+ hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
+ if(DS_OK != hr)
+ {
+ // fail to unlock
+ err_now("Cannot unlock DirectSoundBuffer");
+ return 0;
+ }
+
+ //------- Set volume -----------//
+ lpDsb->SetVolume(dsVolume.ds_vol);
+ lpDsb->SetPan(dsVolume.ds_pan);
+
+ //------- Play wav file --------//
+ if(lpDsb->Play(0, 0, 0) != DS_OK)
+ {
+ // fail to play
+ err_now("Cannot play DirectSoundBuffer");
+ return 0;
+ }
+
+ return wav_serial_no[chanNum] = assign_serial(max_wav_serial_no);
+}
+//------- End of function Audio::play_resided_wav -------//
+
+// ###### begin Gilbert 6/12 ########//
+//------- Begin of function Audio::get_free_wav_ch --------//
+int Audio::get_free_wav_ch()
+{
+ int count = 0;
+ DWORD dsbStatus;
+ for( int chanNum = 0; chanNum < MAX_WAV_CHANNEL; ++chanNum)
+ {
+ if( lp_wav_ch_dsb[chanNum] != NULL && (lp_wav_ch_dsb[chanNum]->GetStatus(&dsbStatus),
+ !(dsbStatus & DSBSTATUS_PLAYING)) )
+ {
+ lp_wav_ch_dsb[chanNum]->Release();
+ lp_wav_ch_dsb[chanNum] = NULL;
+ }
+
+ if( !lp_wav_ch_dsb[chanNum] )
+ count++;
+ }
+
+ return count;
+}
+//------- End of function Audio::get_free_wav_ch --------//
+// ###### end Gilbert 6/12 ########//
+
+//------- Begin of function Audio::stop_wav ------------//
+//
+// stop a short sound effect started by play_wav or play_resided_wav
+//
+// <int> the serial no returned by play_wav or play_resided_wav
+//
+// return 1 - channel is found and stopped / channel not found
+// return 0 - cannot stop the channel
+//
+int Audio::stop_wav(int serial)
+{
+ for( int chanNum = 0; chanNum < MAX_WAV_CHANNEL; ++chanNum)
+ {
+ if(lp_wav_ch_dsb[chanNum] != NULL && wav_serial_no[chanNum] == serial)
+ {
+ lp_wav_ch_dsb[chanNum]->Stop();
+ lp_wav_ch_dsb[chanNum]->Release();
+ lp_wav_ch_dsb[chanNum] = NULL;
+ wav_serial_no[chanNum] = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+//------- End of function Audio::stop_wav ------------//
+
+//------- Begin of function Audio::is_wav_playing ------------//
+//
+// return wheather a short sound effect is stopped
+//
+// <int> the serial no returned by play_wav or play_resided_wav
+//
+int Audio::is_wav_playing(int serial)
+{
+ DWORD dsbStatus;
+ for( int chanNum = 0; chanNum < MAX_WAV_CHANNEL; ++chanNum)
+ {
+ if(lp_wav_ch_dsb[chanNum] != NULL && wav_serial_no[chanNum] == serial
+ && lp_wav_ch_dsb[chanNum]->GetStatus(&dsbStatus) )
+ {
+ return dsbStatus & DSBSTATUS_PLAYING;
+ }
+ }
+ return 0;
+}
+//------- End of function Audio::is_wav_playing ------------//
+
+//------- Begin of function Audio::play_long_wav --------//
+//
+// Play digitized wav from the wav file
+// suitable for very large wave file
+//
+// <char*> wavName = name of the wave file
+//
+// return : <int> 1 - wav loaded and is playing
+// 0 - wav not played
+// note : it uses streaming DirectSoundBuffer
+// Audio::yield() keeps on feeding data to it
+
+// Create a DirectSoundBuffer of size lwav_buf_size[c]*LWAV_BANKS
+// divide into LWAV_BANKS parts. Each time Audio::yield() is called,
+// load wave file into one part. lwav_bank[c] record which part to be
+// filled next for channel c.
+
+int Audio::play_long_wav(char *wavName, DsVolume dsVolume)
+{
+ if( !wav_init_flag || !wav_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ //-------- Load wav file header-------//
+ DWORD wavDataOffset,wavDataLength;
+
+ if( LWAV_STREAM_BUFSIZ*LWAV_BANKS > wav_buf_size )
+ {
+ wav_buf_size = LWAV_STREAM_BUFSIZ*LWAV_BANKS;
+ wav_buf = mem_resize( wav_buf, wav_buf_size );
+ }
+
+ // File* filePtr = (File *)mem_add(sizeof(File)); // new File;
+ File* filePtr = new File; // new File;
+ if(!filePtr->file_open(wavName,0,0))
+ {
+ char errmsg[60];
+ sprintf(errmsg, "Cannot open %s", wavName);
+ box.msg(errmsg);
+ delete filePtr;
+ return 0;
+ }
+
+ if( !filePtr )
+ {
+ delete filePtr;
+ return 0;
+ }
+
+ // load small part of the wave file (first 128 bytes) enough to hold
+ // the hold header
+ if( !filePtr->file_read( wav_buf, 128 ) )
+ {
+ delete filePtr;
+ return 0;
+ }
+
+ // determine the wave data offset
+ char * dataTag = wavefile_data(wav_buf);
+ if (!dataTag)
+ {
+ err_now("Invalid wave file format");
+ delete filePtr;
+ return 0; // invalid RIFF WAVE format
+ }
+ wavDataOffset = (dataTag - wav_buf) + 4 + sizeof(DWORD);
+ wavDataLength = *(DWORD *)(dataTag+4);
+
+ // seek to the start of wave data
+ long temp1 = filePtr->file_seek(wavDataOffset,FILE_BEGIN);
+
+ WORD OptBufferSize=LWAV_STREAM_BUFSIZ,
+ MinRemainder =(WORD)(wavDataLength % (OptBufferSize * LWAV_BANKS));
+ //------- find out the best buffer size -------//
+ // store it in OptBufferSize
+ // criteria : below or equal to LWAV_STREAM_BUFSIZ and
+ // minimize wavDataLength % (OptBufferSize * LWAV_BANKS)
+ // i.e. minimize the truncation to the wave file
+ for(WORD TryBufSiz=LWAV_STREAM_BUFSIZ-0x200; TryBufSiz <= LWAV_STREAM_BUFSIZ;
+ TryBufSiz+=0x20)
+ {
+ WORD TryRemainder = (WORD)(wavDataLength % (TryBufSiz * LWAV_BANKS));
+ if(TryRemainder < MinRemainder)
+ {
+ MinRemainder = TryRemainder;
+ OptBufferSize = TryBufSiz;
+ }
+ }
+
+ //------- Create DirectSoundBuffer to store a wave ------//
+ LPDIRECTSOUNDBUFFER lpDsb;
+ DSBUFFERDESC dsbDesc;
+ HRESULT hr;
+ DWORD dsbStatus;
+
+ // set up DSBUFFERDESC structure
+ memset(&dsbDesc, 0, sizeof(DSBUFFERDESC)); // zero it out
+ dsbDesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbDesc.dwFlags = DSBCAPS_CTRLDEFAULT; // Need defaul controls (pan, volume, frequency)
+ dsbDesc.dwBufferBytes = OptBufferSize * LWAV_BANKS;
+ dsbDesc.lpwfxFormat = (LPWAVEFORMATEX) (wav_buf+0x14);
+ // ------- assign buffer to a channel number ----------//
+ lpDsb = NULL;
+ int chanNum;
+ for( chanNum = 0; chanNum < MAX_LONG_WAV_CH; ++chanNum)
+ if(lp_lwav_ch_dsb[chanNum] == NULL || (lp_lwav_ch_dsb[chanNum]->GetStatus(&dsbStatus),
+ !(dsbStatus & DSBSTATUS_PLAYING)))
+ {
+ if(lp_lwav_ch_dsb[chanNum])
+ {
+ lp_lwav_ch_dsb[chanNum]->Release();
+ lp_lwav_ch_dsb[chanNum] = NULL;
+ // mem_del(lwav_fileptr[chanNum]); // delete lwav_fileptr[chanNum];
+ delete lwav_fileptr[chanNum];
+ lwav_fileptr[chanNum] = NULL;
+ }
+ // found an idle channel, create DirectSoundBuffer
+ hr = lp_direct_sound->CreateSoundBuffer(&dsbDesc, &lpDsb, NULL);
+ if (DS_OK != hr)
+ {
+ // failed!
+ err_now("Cannot create Stream DirectSoundBuffer");
+ delete filePtr;
+ return 0;
+ }
+ lp_lwav_ch_dsb[chanNum] = lpDsb;
+ lwav_fileptr[chanNum] = filePtr; // no need to delete filePtr any more
+ lwav_bank[chanNum] = 0;
+ lwav_bufsiz[chanNum] = OptBufferSize;
+ break;
+ }
+ if( chanNum >= MAX_LONG_WAV_CH)
+ {
+ delete filePtr;
+ return 0;
+ }
+ // Note : if not found, just play the sound, don't play the sound
+ // increase MAX_LONG_WAV_CH
+
+ //------- copy sound data to DirectSoundBuffer--------//
+ // unlock vga_front
+ VgaFrontLock vgaLock;
+
+ // lock the buffer first
+ LPVOID lpvPtr1;
+ DWORD dwBytes1;
+ LPVOID lpvPtr2;
+ DWORD dwBytes2;
+
+ // load wave data into buffer
+ // load data before lock DirectSoundBuffer in case the wave file
+ // is very short
+ DWORD startPos = filePtr->file_pos();
+ if( !filePtr->file_read(wav_buf, OptBufferSize*LWAV_BANKS))
+ {
+ // file error
+ err_now("Missing wave file");
+ return 0;
+ }
+ DWORD readStreamSize = filePtr->file_pos() - startPos;
+ DWORD playFlag = DSBPLAY_LOOPING;
+ hr = lpDsb->Lock(0, OptBufferSize*LWAV_BANKS, &lpvPtr1, &dwBytes1,
+ &lpvPtr2, &dwBytes2, 0);
+ if(DS_OK != hr)
+ {
+ // fail to lock
+ err_now("Cannot lock DirectSoundBuffer");
+ return 0;
+ }
+
+ // write to pointers
+ memcpy(lpvPtr1, wav_buf, min(dwBytes1, readStreamSize));
+ if( dwBytes1 > readStreamSize )
+ { // end of file, fill the remaining with zero
+ memset((char *)lpvPtr1+readStreamSize, 0, dwBytes1 - readStreamSize);
+ playFlag &= ~DSBPLAY_LOOPING;
+ }
+ else
+ {
+ readStreamSize -= dwBytes1;
+ if(lpvPtr2 && dwBytes2 > 0)
+ {
+ memcpy(lpvPtr2, wav_buf+dwBytes1, min(dwBytes2, readStreamSize));
+ if( dwBytes2 > readStreamSize )
+ { // end of file, fill the remaining with zero
+ memset((char *)lpvPtr2+readStreamSize, 0 , dwBytes2 - readStreamSize);
+ playFlag &= ~DSBPLAY_LOOPING;
+ }
+ }
+ }
+
+ // unlock data back
+ hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
+ if(DS_OK != hr)
+ {
+ // fail to unlock
+ err_now("Cannot unlock DirectSoundBuffer");
+ return 0;
+ }
+
+ //------- Set volume -----------//
+ lpDsb->SetVolume(dsVolume.ds_vol);
+ lpDsb->SetPan(dsVolume.ds_pan);
+
+ //------- Play wav file --------//
+ if(lpDsb->Play(0, 0, playFlag) != DS_OK)
+ {
+ // fail to play
+ err_now("Cannot play DirectSoundBuffer");
+ return 0;
+ }
+ run_yield = 1;
+ return lwav_serial_no[chanNum] = assign_serial(max_lwav_serial_no);
+}
+//------- End of function Audio::play_long_wav ----------//
+
+//------- Begin of function Audio::stop_long_wav ------------//
+//
+// stop a short sound effect started by play_long_wav
+//
+// <int> the serial no returned by play_long_wav
+//
+// return 1 - channel is found and stopped / channel not found
+// return 0 - cannot stop the channel
+//
+int Audio::stop_long_wav(int serial)
+{
+ for( int chanNum = 0; chanNum < MAX_LONG_WAV_CH; ++chanNum)
+ {
+ if(lp_lwav_ch_dsb[chanNum] != NULL && lwav_serial_no[chanNum] == serial)
+ {
+ lp_lwav_ch_dsb[chanNum]->Stop();
+ lp_lwav_ch_dsb[chanNum]->Release();
+ lp_lwav_ch_dsb[chanNum] = NULL;
+ delete lwav_fileptr[chanNum];
+ lwav_fileptr[chanNum] = NULL;
+ lwav_serial_no[chanNum] = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+//------- End of function Audio::stop_long_wav ------------//
+
+//------- Begin of function Audio::is_long_wav_playing ------------//
+//
+// return wheather a short sound effect is stopped
+//
+// <int> the serial no returned by play_wav or play_resided_wav
+//
+int Audio::is_long_wav_playing(int serial)
+{
+ DWORD dsbStatus;
+ for( int chanNum = 0; chanNum < MAX_LONG_WAV_CH; ++chanNum)
+ {
+ if(lp_lwav_ch_dsb[chanNum] != NULL && lwav_serial_no[chanNum] == serial
+ && lp_lwav_ch_dsb[chanNum]->GetStatus(&dsbStatus) == DS_OK )
+ {
+ return dsbStatus & DSBSTATUS_PLAYING;
+ }
+ }
+ return 0;
+}
+//------- End of function Audio::is_long_wav_playing ------------//
+
+//--------------- Begin of Audio::vol_multiply --------------//
+long Audio::vol_multiply(int relVolume)
+{
+ long dsVolume = (wav_volume * relVolume) - 10000;
+ if( dsVolume > 0 )
+ dsVolume = 0;
+ else if( dsVolume < -10000 )
+ dsVolume = -10000;
+ return dsVolume;
+}
+//--------------- End of Audio::vol_multiply --------------//
+
+
+//--------------- Begin of Audio::vol_divide --------------//
+int Audio::vol_divide(long dsVolume)
+{
+ if( wav_volume == 0)
+ return 0;
+
+ int relVolume = (dsVolume + 10000) / wav_volume;
+ if( relVolume < 0)
+ relVolume = 0;
+ else if( relVolume > 100 )
+ relVolume = 100;
+ return relVolume;
+}
+//--------------- End of Audio::vol_divide --------------//
+
+
+//------- Begin of function Audio::play_loop_wav -------//
+//
+// Play digitized wav from the wav resource file
+//
+// <char*> wavName = name of the wav in the resource file
+// int repeatOffset = offset of wave data to play on repeat
+// i.e. 0 to start of wave data
+//
+// return : <int> channel number (1 - MAX_LOOP_WAV_CH)
+// 0 not played
+//
+int Audio::play_loop_wav(char *wavName, int repeatOffset, DsVolume dsVolume)
+{
+ if( !wav_init_flag || !wav_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ //-------- Load wav file header-------//
+ DWORD wavDataOffset,wavDataLength;
+
+ // File* filePtr = (File *)mem_add(sizeof(File)); // new File;
+ File* filePtr = new File;
+
+
+ if(!filePtr->file_open(wavName,0,0))
+ {
+ char errmsg[60];
+ sprintf(errmsg, "Cannot open %s", wavName);
+ box.msg(errmsg);
+ delete filePtr;
+ return 0;
+ }
+
+ if( !filePtr )
+ return 0;
+
+ // load small part of the wave file (first 128 bytes) enough to hold
+ // the hold header
+ if( !filePtr->file_read( wav_buf, 128 ) )
+ {
+ delete filePtr;
+ return 0;
+ }
+
+ // determine the wave data offset
+ char * dataTag = wavefile_data(wav_buf);
+ if (!dataTag)
+ {
+ err_now("Invalid wave file format");
+ delete filePtr;
+ return 0; // invalid RIFF WAVE format
+ }
+ wavDataOffset = (dataTag - wav_buf) + 4 + sizeof(DWORD);
+ wavDataLength = *(DWORD *)(dataTag+4);
+
+ // seek to the start of wave data
+ long temp1 = filePtr->file_seek(wavDataOffset,FILE_BEGIN);
+
+ WORD OptBufferSize=LOOPWAV_STREAM_BUFSIZ;
+
+ //------- Create DirectSoundBuffer to store a wave ------//
+ LPDIRECTSOUNDBUFFER lpDsb;
+ DSBUFFERDESC dsbDesc;
+ HRESULT hr;
+ DWORD dsbStatus;
+
+ // set up DSBUFFERDESC structure
+ memset(&dsbDesc, 0, sizeof(DSBUFFERDESC)); // zero it out
+ dsbDesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbDesc.dwFlags = DSBCAPS_CTRLDEFAULT; // Need defaul controls (pan, volume, frequency)
+ dsbDesc.dwBufferBytes = OptBufferSize * LWAV_BANKS;
+ dsbDesc.lpwfxFormat = (LPWAVEFORMATEX) (wav_buf+0x14);
+ // ------- assign buffer to a channel number ----------//
+ lpDsb = NULL;
+ int chanNum;
+ for( chanNum = 0; chanNum < MAX_LOOP_WAV_CH; ++chanNum)
+ if(lp_loop_ch_dsb[chanNum] == NULL || (lp_loop_ch_dsb[chanNum]->GetStatus(&dsbStatus),
+ !(dsbStatus & DSBSTATUS_PLAYING)))
+ {
+ if(lp_loop_ch_dsb[chanNum])
+ {
+ lp_loop_ch_dsb[chanNum]->Release();
+ lp_loop_ch_dsb[chanNum] = NULL;
+ // mem_del(loopwav_fileptr[chanNum]); // delete lwav_fileptr[chanNum];
+ delete loopwav_fileptr[chanNum];
+ loopwav_fileptr[chanNum] = NULL;
+ }
+ // found an idle channel, create DirectSoundBuffer
+ hr = lp_direct_sound->CreateSoundBuffer(&dsbDesc, &lpDsb, NULL);
+ if (DS_OK != hr)
+ {
+ // failed!
+ err_now("Cannot create Stream DirectSoundBuffer");
+ return 0;
+ }
+ lp_loop_ch_dsb[chanNum] = lpDsb;
+ loopwav_fileptr[chanNum] = filePtr; // no need to delete filePtr any more
+ loopwav_bank[chanNum] = 0;
+ repeat_offset[chanNum] = wavDataOffset + repeatOffset;
+ loopwav_fade_rate[chanNum] = 0;
+ break;
+ }
+ if( chanNum >= MAX_LOOP_WAV_CH)
+ {
+ delete filePtr;
+ return 0;
+ }
+ // Note : if not found, just play the sound, don't play the sound
+
+ //------- copy sound data to DirectSoundBuffer--------//
+ // unlock vga_front
+ VgaFrontLock vgaLock;
+
+ // lock the buffer first
+ LPVOID lpvPtr1;
+ DWORD dwBytes1;
+ LPVOID lpvPtr2;
+ DWORD dwBytes2;
+
+ // load wave data into buffer
+ // load data before lock DirectSoundBuffer in case the wave file
+ // is very short
+ DWORD startPos = filePtr->file_pos();
+ if( !filePtr->file_read(wav_buf, OptBufferSize*LOOPWAV_BANKS))
+ {
+ // file error
+ err_now("Missing wave file");
+ return 0;
+ }
+ DWORD readStreamSize = filePtr->file_pos() - startPos;
+ DWORD playFlag = DSBPLAY_LOOPING;
+ hr = lpDsb->Lock(0, OptBufferSize*LOOPWAV_BANKS, &lpvPtr1, &dwBytes1,
+ &lpvPtr2, &dwBytes2, 0);
+ if(DS_OK != hr)
+ {
+ // fail to lock
+ err_now("Cannot lock DirectSoundBuffer");
+ return 0;
+ }
+
+ // write to pointers, assume wave file repeating size is
+ // larger than OptBufferSize * LOOPWAV_BANKS
+ memcpy(lpvPtr1, wav_buf, min(dwBytes1, readStreamSize));
+ if( dwBytes1 < readStreamSize )
+ {
+ readStreamSize -= dwBytes1;
+ if(lpvPtr2 && dwBytes2 > 0)
+ {
+ memcpy(lpvPtr2, wav_buf+dwBytes1, min(dwBytes2, readStreamSize));
+ }
+ }
+
+ // unlock data back
+ hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
+ if(DS_OK != hr)
+ {
+ // fail to unlock
+ err_now("Cannot unlock DirectSoundBuffer");
+ return 0;
+ }
+
+ //------- Set volume -----------//
+ lpDsb->SetVolume(dsVolume.ds_vol);
+ lpDsb->SetPan(dsVolume.ds_pan);
+
+ //------- Play wav file --------//
+ if(lpDsb->Play(0, 0, playFlag) != DS_OK)
+ {
+ // fail to play
+ err_now("Cannot play DirectSoundBuffer");
+ return 0;
+ }
+ run_yield = 1;
+ return chanNum+1;
+}
+//------- End of function Audio::play_loop_wav ---------//
+
+
+//------- Begin of function Audio::volume_loop_wav -------//
+void Audio::volume_loop_wav(int ch, DsVolume dsVolume)
+{
+ int chanNum = ch-1;
+ if( chanNum < 0 || chanNum >= MAX_LOOP_WAV_CH)
+ return;
+ if(lp_loop_ch_dsb[chanNum])
+ {
+ lp_loop_ch_dsb[chanNum]->SetVolume(dsVolume.ds_vol);
+ lp_loop_ch_dsb[chanNum]->SetPan(dsVolume.ds_pan);
+
+ // stop fading
+ loopwav_fade_rate[chanNum] = 0;
+ }
+}
+//------- End of function Audio::volume_loop_wav -------//
+
+
+//------- Begin of function Audio::fade_out_loop_wav -------//
+//
+// <int> fadeRate, time for volume 100 wave drop to slience
+//
+void Audio::fade_out_loop_wav(int ch, int fadeRate)
+{
+ int chanNum = ch-1;
+ if( chanNum < 0 || chanNum >= MAX_LOOP_WAV_CH)
+ return;
+ if(lp_loop_ch_dsb[chanNum])
+ {
+ loopwav_fade_rate[chanNum] = fadeRate;
+ loopwav_fade_time[chanNum] = m.get_time();
+ }
+}
+//------- End of function Audio::fade_out_loop_wav -------//
+
+
+//------- Begin of function Audio::get_loop_wav_volume -------//
+DsVolume Audio::get_loop_wav_volume(int ch)
+{
+ int chanNum = ch-1;
+ if( chanNum < 0 || chanNum >= MAX_LOOP_WAV_CH)
+ {
+ RelVolume rel = RelVolume(0,0);
+ return DsVolume(rel);
+ }
+
+ LONG volume;
+ LONG pan;
+ LPDIRECTSOUNDBUFFER lpDsb= lp_loop_ch_dsb[chanNum];
+ if( lpDsb && lpDsb->GetVolume(&volume) == DS_OK &&
+ lpDsb->GetPan(&pan) == DS_OK )
+ {
+ return DsVolume(volume, pan);
+ }
+ RelVolume rel = RelVolume(0,0);
+ return DsVolume(rel);
+}
+//------- End of function Audio::get_loop_wav_volume -------//
+
+
+//------- Begin of function Audio::is_loop_wav_fading -------//
+int Audio::is_loop_wav_fading(int ch)
+{
+ int chanNum = ch-1;
+ if( chanNum < 0 || chanNum >= MAX_LOOP_WAV_CH)
+ return 0;
+
+ return lp_loop_ch_dsb[chanNum] && loopwav_fade_rate[chanNum];
+}
+//------- End of function Audio::is_loop_wav_fading -------//
+
+
+//------- Begin of function Audio::yield ---------------//
+void Audio::yield()
+{
+#ifndef WIN32
+ // unlock vga_front
+ VgaFrontLock vgaLock;
+#endif
+
+ if( !run_yield)
+ return;
+
+ run_yield = 0; // suspend recursive Audio::yield();
+
+#ifdef WIN32
+ // unlock vga_front
+ VgaFrontLock vgaLock;
+#endif
+
+ // set break point beyond this point
+ int i;
+ for(i = 0; i < MAX_LONG_WAV_CH; ++i)
+ {
+ if( !lp_lwav_ch_dsb[i] )
+ continue;
+
+ // if a wav is not play, or buffer lost, stop it
+ LPDIRECTSOUNDBUFFER& lpDsb = lp_lwav_ch_dsb[i];
+ DWORD dsbStatus;
+ if( lpDsb->GetStatus(&dsbStatus) != DS_OK)
+ err_here();
+ if( !(dsbStatus & DSBSTATUS_PLAYING) ||
+ (dsbStatus & DSBSTATUS_BUFFERLOST) && lpDsb->Restore() != DS_OK )
+ {
+ lpDsb->Stop();
+ lpDsb->Release();
+ lpDsb = NULL;
+ // mem_del(lwav_fileptr[i]);
+ delete lwav_fileptr[i];
+ lwav_fileptr[i] = NULL;
+ continue;
+ }
+
+ char writeTooFast = 1;
+
+ // buffer lost, succeeded in restoring
+ if( dsbStatus & DSBSTATUS_BUFFERLOST )
+ {
+ writeTooFast = 0;
+ }
+ else
+ {
+ // perform flow control
+ DWORD tmpPlayCursor, tmpWriteCursor;
+ if( lpDsb->GetCurrentPosition(&tmpPlayCursor, &tmpWriteCursor) == DS_OK)
+ {
+ writeTooFast = ((short)(tmpPlayCursor / lwav_bufsiz[i]) == lwav_bank[i]);
+ }
+ }
+
+ if(!writeTooFast)
+ {
+ // lock a channel for lwav_bufsiz[i]
+ LPVOID lpvPtr1;
+ DWORD dwBytes1;
+ LPVOID lpvPtr2;
+ DWORD dwBytes2;
+ File *filePtr = lwav_fileptr[i];
+ HRESULT hr = lpDsb->Lock(lwav_bank[i]*lwav_bufsiz[i], lwav_bufsiz[i],
+ &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
+ lwav_bank[i] = (lwav_bank[i] + 1) % LWAV_BANKS; // next bank to fill
+ if(DS_OK != hr)
+ {
+ // fail to lock
+ err_now("Cannot lock DirectSoundBuffer");
+ run_yield = 1;
+ return;
+ }
+
+ long startPos;
+ long readStreamSize;
+ DWORD playFlag = DSBPLAY_LOOPING;
+
+ // write to pointers
+ startPos = filePtr->file_pos();
+
+// filePtr->file_read(wav_buf, dwBytes1);
+// readStreamSize = filePtr->file_pos() - startPos; // bytes read in
+// memcpy(lpvPtr1, wav_buf, readStreamSize);
+ filePtr->file_read(lpvPtr1, dwBytes1);
+ readStreamSize = filePtr->file_pos() - startPos; // bytes read in
+
+ if((long)dwBytes1 > readStreamSize)
+ { // end of file, fill the remaining with zero
+// memset((char *)lpvPtr1+readStreamSize, 0, dwBytes1 - readStreamSize);
+ playFlag &= ~DSBPLAY_LOOPING; // clear DSBPLAY_LOOPING
+ }
+ else
+ {
+ if( lpvPtr2 && dwBytes2 > 0)
+ {
+ startPos = filePtr->file_pos();
+ filePtr->file_read(lpvPtr2, dwBytes2);
+ readStreamSize = filePtr->file_pos() - startPos; // bytes read in
+ if((long)dwBytes2 > readStreamSize)
+ { // end of file
+// memset((char *)lpvPtr2+readStreamSize, 0, dwBytes2 - readStreamSize);
+ playFlag &= ~DSBPLAY_LOOPING; // clear DSBPLAY_LOOPING
+ }
+ }
+ }
+
+ // unlock data back
+ hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
+ if(DS_OK != hr)
+ {
+ // fail to unlock
+ err_now("Cannot unlock DirectSoundBuffer");
+ run_yield = 1;
+ return;
+ }
+ // load file into a channel, on last stream, don't loop back
+ //------- Play wav file --------//
+ if(lpDsb->Play(0, 0, playFlag) != DS_OK)
+ {
+ // fail to play
+ err_now("Cannot play DirectSoundBuffer");
+ run_yield = 1;
+ return;
+ }
+ }
+ }
+
+ for(i = 0; i < MAX_LOOP_WAV_CH; ++i)
+ {
+ if( !lp_loop_ch_dsb[i] )
+ continue;
+
+ // if a channel is not playing, or can't restore release it
+ LPDIRECTSOUNDBUFFER& lpDsb = lp_loop_ch_dsb[i];
+ DWORD dsbStatus;
+ if( lpDsb->GetStatus(&dsbStatus) != DS_OK )
+ err_here();
+ if( !(dsbStatus & DSBSTATUS_PLAYING) ||
+ (dsbStatus & DSBSTATUS_BUFFERLOST) && lpDsb->Restore() != DS_OK )
+ {
+ lpDsb->Stop();
+ lpDsb->Release();
+ lpDsb = NULL;
+ // mem_del(loopwav_fileptr[i]);
+ delete loopwav_fileptr[i];
+ loopwav_fileptr[i] = NULL;
+ continue;
+ }
+
+ char writeTooFast = 1;
+
+ // buffer lost, succeeded in restoring
+ if( dsbStatus & DSBSTATUS_BUFFERLOST )
+ {
+ writeTooFast = 0;
+ }
+ else
+ {
+ // perform flow control
+ DWORD tmpPlayCursor, tmpWriteCursor;
+ if( lpDsb->GetCurrentPosition(&tmpPlayCursor, &tmpWriteCursor) == DS_OK)
+ {
+ writeTooFast = ((short)(tmpPlayCursor / LOOPWAV_STREAM_BUFSIZ) == loopwav_bank[i]);
+ }
+
+ }
+
+ if(!writeTooFast)
+ {
+ // lock a channel for loopwav_bufsiz[i]
+ LPVOID lpvPtr1;
+ DWORD dwBytes1;
+ LPVOID lpvPtr2;
+ DWORD dwBytes2;
+ File *filePtr = loopwav_fileptr[i];
+ HRESULT hr = lpDsb->Lock(loopwav_bank[i]*LOOPWAV_STREAM_BUFSIZ, LOOPWAV_STREAM_BUFSIZ,
+ &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
+ loopwav_bank[i] = (loopwav_bank[i] + 1) % LOOPWAV_BANKS; // next bank to fill
+ if(DS_OK != hr)
+ {
+ // fail to lock
+ err_now("Cannot lock DirectSoundBuffer");
+ run_yield = 1;
+ return;
+ }
+
+ long startPos;
+ long readStreamSize;
+ DWORD playFlag = DSBPLAY_LOOPING;
+
+ // write to pointers
+ startPos = filePtr->file_pos();
+ filePtr->file_read(lpvPtr1, dwBytes1);
+ readStreamSize = filePtr->file_pos() - startPos; // bytes read in
+
+ if((long)dwBytes1 > readStreamSize)
+ { // end of file, seek to beginning and read again
+ filePtr->file_seek(repeat_offset[i]);
+ filePtr->file_read((char *)lpvPtr1+readStreamSize, dwBytes1-readStreamSize);
+ }
+
+ // unlock data back
+ hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
+ if(DS_OK != hr)
+ {
+ // fail to unlock
+ err_now("Cannot unlock DirectSoundBuffer");
+ run_yield = 1;
+ return;
+ }
+
+ // set volume if fading
+ if( loopwav_fade_rate[i] )
+ {
+ DWORD nextFadeTime = m.get_time();
+ LONG volume;
+ if( DS_OK == (hr = lpDsb->GetVolume(&volume)) )
+ {
+ // calculate new volume
+ volume -= (nextFadeTime - loopwav_fade_time[i]) * loopwav_fade_rate[i];
+ if( volume < DSBVOLUME_MIN )
+ volume = DSBVOLUME_MIN;
+ else if( volume > DSBVOLUME_MAX )
+ volume = DSBVOLUME_MAX;
+ if( DS_OK == (hr = lpDsb->SetVolume(volume)) )
+ {
+ loopwav_fade_time[i] = nextFadeTime;
+ }
+ }
+ }
+
+ // load file into a channel, on last stream, don't loop back
+ //------- Play wav file --------//
+ if(lpDsb->Play(0, 0, playFlag) != DS_OK)
+ {
+ // fail to play
+ err_now("Cannot play DirectSoundBuffer");
+ run_yield = 1;
+ return;
+ }
+ }
+ }
+
+ run_yield = 1; // resume Audio::yield();
+}
+
+//------- End of function Audio::yield ---------------//
+
+
+//------- Begin of function Audio::stop_wav -------//
+//
+void Audio::stop_wav()
+{
+ if( !wav_init_flag || !wav_flag )
+ return;
+
+ // ---------- stop all short wave ------------- //
+ int i;
+ for(i = 0; i < MAX_WAV_CHANNEL; ++i)
+ if(lp_wav_ch_dsb[i])
+ {
+ lp_wav_ch_dsb[i]->Stop();
+ lp_wav_ch_dsb[i]->Release();
+ lp_wav_ch_dsb[i] = NULL;
+ }
+
+ // ----------- stop all long wave ------------- //
+ stop_long_wav();
+
+ // ------------ stop all loop wave ------------//
+ for(i = 1; i <= MAX_LOOP_WAV_CH; ++i)
+ {
+ stop_loop_wav(i);
+ }
+
+}
+//------- End of function Audio::stop_wav -------//
+
+//------- Begin of function Audio::stop_long_wav -------//
+//
+void Audio::stop_long_wav()
+{
+ if( !wav_init_flag || !wav_flag )
+ return;
+
+ for(int i = 0; i < MAX_LONG_WAV_CH; ++i)
+ if(lp_lwav_ch_dsb[i])
+ {
+ lp_lwav_ch_dsb[i]->Stop();
+ lp_lwav_ch_dsb[i]->Release();
+ lp_lwav_ch_dsb[i] = NULL;
+ // mem_del(lwav_fileptr[i]);
+ delete lwav_fileptr[i];
+ lwav_fileptr[i] = NULL;
+ }
+}
+//------- End of function Audio::stop_long_wav -------//
+
+
+//------- Begin of function Audio::stop_loop_wav -------//
+void Audio::stop_loop_wav(int ch)
+{
+ int chanNum = ch-1;
+ if( chanNum < 0 || chanNum >= MAX_LOOP_WAV_CH)
+ return;
+ if(lp_loop_ch_dsb[chanNum])
+ {
+ lp_loop_ch_dsb[chanNum]->Stop();
+ lp_loop_ch_dsb[chanNum]->Release();
+ lp_loop_ch_dsb[chanNum] = NULL;
+ // mem_del(loopwav_fileptr[chanNum]); // delete lwav_fileptr[chanNum];
+ delete loopwav_fileptr[chanNum];
+ loopwav_fileptr[chanNum] = NULL;
+ }
+}
+//------- End of function Audio::stop_loop_wav ---------//
+
+
+//------- Begin of function Audio::play_cd -------//
+//
+// <int> trackId - the id. of the CD track to play.
+//
+int Audio::play_cd(int trackId, int volume)
+{
+ if( !cd_init_flag || !cd_flag )
+ return 0;
+
+ // ###### begin Gilbert 3/10 ########//
+ MCIERROR mciError;
+ DWORD maxTrack = 99;
+
+ // Get the number of tracks;
+ // limit to number that can be displayed (20).
+ MCI_STATUS_PARMS mciStatusParms;
+ mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
+ mciError = mciSendCommand(mci_open.wDeviceID, MCI_STATUS,
+ MCI_STATUS_ITEM, (DWORD)(LPVOID) &mciStatusParms);
+ if( !mciError )
+ maxTrack = mciStatusParms.dwReturn;
+
+ //--- Send an MCI_PLAY command to the CD Audio driver ---//
+
+ mci_open.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_CD_AUDIO;
+
+ mci_play.dwFrom = MCI_MAKE_TMSF(trackId , 0, 0, 0);
+ mci_play.dwTo = MCI_MAKE_TMSF(trackId+1, 0, 0, 0);
+
+ DWORD cmdFlag = MCI_FROM;
+ if( (DWORD) trackId < maxTrack )
+ cmdFlag |= MCI_TO; // if MCI_TO is missing, play until the end
+ mciError = mciSendCommand( mci_open.wDeviceID, MCI_PLAY, cmdFlag,
+ (DWORD) (LPVOID) &mci_play);
+ if( mciError )
+ return 0;
+
+ return 1;
+ // ###### end Gilbert 3/10 ########//
+}
+//------- End of function Audio::play_cd -------//
+
+
+//------- Begin of function Audio::stop_cd -------//
+//
+void Audio::stop_cd()
+{
+ if( !cd_init_flag || !cd_flag )
+ return;
+
+ DWORD dwResult;
+ dwResult = mciSendCommand(mci_open.wDeviceID, MCI_STOP,
+ MCI_WAIT, (DWORD)(LPVOID)NULL);
+}
+//------- End of function Audio::stop_cd -------//
+
+
+//------- Begin of function Audio::is_mid_playing -------//
+//
+int Audio::is_mid_playing()
+{
+ if( !mid_init_flag || !mid_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ //... insert code here ...//
+
+ return 0;
+}
+//------- End of function Audio::is_mid_playing -------//
+
+
+//------- Begin of function Audio::is_wav_playing -------//
+//
+int Audio::is_wav_playing()
+{
+ int playingChannelCount = 0;
+ DWORD dwStatus;
+ if( !wav_init_flag || !wav_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ //------ find any wav channel is playing -----//
+ // update lp_wav_ch_dsb[x] if necessary
+ for(int ch=0; ch < MAX_WAV_CHANNEL; ++ch)
+ if( lp_wav_ch_dsb[ch])
+ if( lp_wav_ch_dsb[ch]->GetStatus(&dwStatus) == DS_OK)
+ if (dwStatus & DSBSTATUS_PLAYING )
+ // a channel is playing
+ playingChannelCount++;
+ else
+ // is not playing, clear it
+ lp_wav_ch_dsb[ch] = NULL;
+ else
+ // GetStatus not ok, clear it
+ lp_wav_ch_dsb[ch] = NULL;
+ else
+ {
+ // nothing
+ }
+
+ return (playingChannelCount > 0);
+}
+//------- End of function Audio::is_wav_playing -------//
+
+
+//------- Begin of function Audio::is_cd_playing -------//
+//
+int Audio::is_cd_playing()
+{
+ if( !cd_init_flag || !cd_flag ) // a initialized and workable midi device can be disabled by user setting
+ return 0;
+
+ MCI_STATUS_PARMS status;
+ DWORD dwResult;
+
+ //
+ // get the current status
+ //
+
+ status.dwItem = MCI_STATUS_MODE;
+ dwResult = mciSendCommand(mci_open.wDeviceID, MCI_STATUS,
+ MCI_WAIT | MCI_STATUS_ITEM, (DWORD)(LPVOID)&status);
+ if (dwResult == 0)
+ return status.dwReturn == MCI_MODE_PLAY;
+ return 0;
+}
+//------- End of function Audio::is_cd_playing -------//
+
+
+//----------------- Begin of Audio::toggle_mid -----------------//
+//
+void Audio::toggle_mid(int midFlag)
+{
+ if( !midFlag )
+ stop_mid();
+
+ mid_flag = midFlag;
+}
+//------------------- End of Audio::toggle_mid ------------------//
+
+
+//----------------- Begin of Audio::toggle_wav -----------------//
+//
+void Audio::toggle_wav(int wavFlag)
+{
+ if( !wavFlag )
+ stop_wav();
+
+ wav_flag = wavFlag;
+}
+//------------------- End of Audio::toggle_wav ------------------//
+
+
+//----------------- Begin of Audio::toggle_cd -----------------//
+//
+void Audio::toggle_cd(int cdFlag)
+{
+ if( !cdFlag )
+ stop_cd();
+
+ cd_flag = cdFlag;
+}
+//------------------- End of Audio::toggle_cd ------------------//
+
+
+//-------------- Begin of Audio::set_mid_volume -------------//
+//
+// Set mid volume
+//
+// <int> midVolume = mid volume, 0-100
+//
+void Audio::set_mid_volume(int midVolume)
+{
+ if( !mid_init_flag )
+ return;
+
+ //.. insert code here ...//
+
+}
+//--------------- End of Audio::set_mid_volume --------------//
+
+
+//-------------- Begin of Audio::set_wav_volume -------------//
+//
+// Set wav volume
+//
+// <int> wavVolume = wav volume, 0-100
+//
+void Audio::set_wav_volume(int wavVolume)
+{
+ if( !wav_init_flag )
+ return;
+
+ LONG dsVolume;
+ long dsVolDiff = (wavVolume - wav_volume) * 100;
+
+ // change volume for all channels
+ int i;
+ for( i = 0; i < MAX_WAV_CHANNEL; ++i)
+ {
+ if( lp_wav_ch_dsb[i] &&
+ lp_wav_ch_dsb[i]->GetVolume(&dsVolume) == DS_OK)
+ {
+ dsVolume += dsVolDiff;
+ if( dsVolume > DSBVOLUME_MAX )
+ dsVolume = DSBVOLUME_MAX;
+ if( dsVolume < DSBVOLUME_MIN )
+ dsVolume = DSBVOLUME_MIN;
+ lp_wav_ch_dsb[i]->SetVolume(dsVolume);
+ }
+ }
+
+ for( i = 0; i < MAX_LONG_WAV_CH; ++i)
+ {
+ if( lp_lwav_ch_dsb[i] &&
+ lp_lwav_ch_dsb[i]->GetVolume(&dsVolume) == DS_OK)
+ {
+ dsVolume += dsVolDiff;
+ if( dsVolume > DSBVOLUME_MAX )
+ dsVolume = DSBVOLUME_MAX;
+ if( dsVolume < DSBVOLUME_MIN )
+ dsVolume = DSBVOLUME_MIN;
+ lp_lwav_ch_dsb[i]->SetVolume(dsVolume);
+ }
+ }
+
+ for( i = 0; i < MAX_LOOP_WAV_CH; ++i)
+ {
+ if( lp_loop_ch_dsb[i] &&
+ lp_loop_ch_dsb[i]->GetVolume(&dsVolume) == DS_OK)
+ {
+ dsVolume += dsVolDiff;
+ if( dsVolume > DSBVOLUME_MAX )
+ dsVolume = DSBVOLUME_MAX;
+ if( dsVolume < DSBVOLUME_MIN )
+ dsVolume = DSBVOLUME_MIN;
+ lp_loop_ch_dsb[i]->SetVolume(dsVolume);
+ }
+ }
+
+ wav_volume = wavVolume;
+}
+//--------------- End of Audio::set_wav_volume --------------//
+
+
+//-------------- Begin of Audio::set_cd_volume -------------//
+//
+// Set cd volume
+//
+// <int> cdVolume = cd volume, 0-100
+//
+void Audio::set_cd_volume(int cdVolume)
+{
+ if( !cd_init_flag )
+ return;
+
+ //.. insert code here ...//
+
+}
+//--------------- End of Audio::set_cd_volume --------------//
+
+
+//-------------- Begin of function Audio::assign_serial ----------//
+int Audio::assign_serial(int &s)
+{
+ if( s == INT_MAX)
+ return s = 1;
+ return ++s;
+}
+//-------------- End of function Audio::assign_serial ----------//
+
+
+// ------------ Begin of function Audio::volume_long_wav -------//
+void Audio::volume_long_wav(int serial, DsVolume dsVolume)
+{
+ if( is_long_wav_playing(serial) )
+ {
+ for( int chanNum = 0; chanNum < MAX_LONG_WAV_CH; ++chanNum)
+ {
+ if(lp_lwav_ch_dsb[chanNum] != NULL && lwav_serial_no[chanNum] == serial )
+ {
+ lp_lwav_ch_dsb[chanNum]->SetVolume(dsVolume.ds_vol);
+ // lp_lwav_ch_dsb[chanNum]->SetPan(dsVolume.ds_pan);
+ break;
+ }
+ }
+ }
+}
+// ------------ End of function Audio::volume_long_wav -------//
+
diff --git a/OBATTLE.cpp b/OBATTLE.cpp
new file mode 100644
index 0000000..02fd89e
--- /dev/null
+++ b/OBATTLE.cpp
@@ -0,0 +1,930 @@
+/*
+ * Seven Kingdoms: Ancient Adversaries
+ *
+ * Copyright 1997,1998 Enlight Software Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//Filename : OBATTLE.CPP
+//Description : Battle Object
+
+#include <OSYS.h>
+#include <OWORLD.h>
+#include <OPOWER.h>
+#include <OCONFIG.h>
+#include <OSPY.h>
+#include <OMOUSE.h>
+#include <OMUSIC.h>
+#include <OSITE.h>
+#include <ORACERES.h>
+#include <OGODRES.h>
+#include <OTECHRES.h>
+#include <OCONFIG.h>
+#include <OREMOTE.h>
+#include <OTOWN.h>
+#include <OFIRM.h>
+#include <ONEWS.h>
+#include <ONATION.h>
+#include <OMONSRES.h>
+#include <OWALLRES.h>
+#include <OINFO.h>
+#include <OUNITALL.h>
+#include <OGAME.h>
+#include <OBATTLE.h>
+#include <OMOUSECR.h>
+
+//---------- define static functions -------------//
+
+static int is_space(int xLoc1, int yLoc1, int xLoc2, int yLoc2, char mobileType);
+
+//-------- Begin of function Battle::init --------//
+//
+void Battle::init()
+{
+}
+//-------- End of function Battle::init --------//
+
+
+//-------- Begin of function Battle::deinit --------//
+//
+void Battle::deinit()
+{
+}
+//-------- End of function Battle::deinit --------//
+
+
+//-------- Begin of function Battle::run --------//
+//
+// <int> mpGame - whether this is a multiplayer game or not.
+//
+void Battle::run(NewNationPara *mpGame, int mpPlayerCount)
+{
+ int oldCursor = mouse_cursor.get_icon();
+ mouse_cursor.set_icon(CURSOR_WAITING);
+
+#ifdef DEBUG
+ debug_sim_game_type = (m.is_file_exist("sim.sys")) ? 2 : 0;
+ if(debug_sim_game_type)
+ {
+ run_sim();
+ return;
+ }
+#endif
+
+ // ####### begin Gilbert 24/10 #######//
+ //-- random seed is initalized at connecting multiplayer --//
+ //if( !mpGame )
+ // info.init_random_seed(0);
+ // ####### end Gilbert 24/10 #######//
+
+ //----------- save the current seed for generating map -----------//
+ #ifdef DEBUG2
+ File seedFile;
+ char *chPtr = m.format(m.get_random_seed());
+ seedFile.file_create("mapseed.rs");
+ seedFile.file_write(chPtr, strlen(chPtr));
+ seedFile.file_close();
+ #endif
+
+ world.generate_map();
+
+ //------- create player nation --------//
+
+ if( !mpGame )
+ {
+ // if config.race_id == 0, select a random race, but don't call m.random
+ int nationRecno = nation_array.new_nation( NATION_OWN,
+ config.race_id ? config.race_id : 1+m.get_time() % MAX_RACE,
+ config.player_nation_color );
+
+ nation_array.set_human_name( nationRecno, config.player_name );
+ }
+ else
+ {
+ for( int i = 0; i < mpPlayerCount; ++i )
+ {
+ int nationRecno = nation_array.new_nation(mpGame[i]);
+ if( nationRecno != mpGame[i].nation_recno )
+ err.run( "Unexpected nation recno created" );
+ nation_array.set_human_name( nationRecno, mpGame[i].player_name );
+ }
+ }
+
+ //--------- create ai nations --------//
+
+ if( mpGame )
+ {
+ int aiToCreate = config.ai_nation_count;
+ if( aiToCreate + mpPlayerCount > MAX_NATION )
+ aiToCreate = MAX_NATION - mpPlayerCount;
+ err_when( aiToCreate < 0 );
+ create_ai_nation(aiToCreate);
+ }
+ else
+ {
+ create_ai_nation(config.ai_nation_count);
+ }
+
+ //------ create pregame objects ------//
+
+ create_pregame_object();
+
+ //------- update nation statistic -------//
+
+ nation_array.update_statistic();
+
+ //--- highlight the player's town in the beginning of the game ---//
+
+ Town* townPtr;
+
+ for( int i=1 ; i<=town_array.size() ; i++ )
+ {
+ townPtr = town_array[i];
+
+ if( townPtr->nation_recno == nation_array.player_recno )
+ {
+ world.go_loc( townPtr->loc_x1, townPtr->loc_y1 );
+ break;
+ }
+ }
+
+ //---- reset config parameter ----//
+
+ sys.set_speed(12); // set the speed to normal.
+
+ //---- reset cheats ----//
+
+ config.fast_build = 0;
+ config.king_undie_flag = sys.testing_session && !mpGame;
+ config.blacken_map = 1;
+ config.disable_ai_flag = 0;
+
+ if( sys.testing_session )
+ config.show_unit_path = 3;
+
+ // ######## begin Gilbert 11/11 #######//
+ // enable tech and god, useful for multi-player
+#if(0)
+ for( i = 1; i < nation_array.size(); ++i )
+ {
+ if( !nation_array.is_deleted(i) && !nation_array[i]->is_ai() )
+ {
+ tech_res.inc_all_tech_level(i);
+ god_res.enable_know_all(i);
+ }
+ }
+#endif
+ // ######## end Gilbert 11/11 #######//
+
+ //------- enable/disable sound effects -------//
+
+#ifdef AMPLUS
+ int songId = (~nation_array)->race_id <= 7 ? (~nation_array)->race_id+1 : music.random_bgm_track();
+ music.play(songId, sys.cdrom_drive ? MUSIC_CD_THEN_WAV : 0 );
+#else
+ music.play((~nation_array)->race_id +1, sys.cdrom_drive ? MUSIC_CD_THEN_WAV : 0 );
+#endif
+
+ mouse_cursor.restore_icon(oldCursor);
+
+ //--- give the control to the system main loop, start the game now ---//
+
+ sys.run();
+}
+//--------- End of function Battle::run ---------//
+
+
+//-------- Begin of function Battle::run_sim --------//
+// run simulation
+//
+#ifdef DEBUG
+void Battle::run_sim()
+{
+ err_when(!debug_sim_game_type);
+
+ info.disp_panel();
+ //info.init_random_seed(0);
+ //info.init_random_seed(869451513); // for testing marine
+ info.init_random_seed(869639665); // for testing marine
+
+ //info.init_random_seed(866608391);
+ //info.init_random_seed(866621716);
+ //info.init_random_seed(867299236);
+
+ world.generate_map();
+
+ //--------- refresh world ---------//
+ world.refresh();
+ vga.blt_buf(0, 0, VGA_WIDTH-1, VGA_HEIGHT-1);
+ world.paint();
+
+ //------- create player nation --------//
+
+ // if config.race_id == 0, select a random race, but don't call m.random
+ nation_array.new_nation( NATION_OWN,
+ config.race_id ? config.race_id : 1+m.get_time() % MAX_RACE,
+ config.player_nation_color );
+
+ //--------- create ai nations --------//
+ create_ai_nation(config.ai_nation_count);
+
+ //------ create pregame objects ------//
+ create_pregame_object();
+
+ //--- highlight the player's town in the beginning of the game ---//
+ Town* townPtr;
+ for( int i=1 ; i<=town_array.size() ; i++ )
+ {
+ townPtr = town_array[i];
+
+ if( townPtr->nation_recno == nation_array.player_recno )
+ {
+ world.go_loc( townPtr->loc_x1, townPtr->loc_y1 );
+ break;
+ }
+ }
+
+ //-*************** create units, objects *****************-//
+ int maxNationCount = 2;
+ int unitId = UNIT_DRAGON;
+ int nationCount;
+ SpriteInfo *spriteInfo;
+ char teraMask;
+ int unitRecno, x, y, xLoc, yLoc;
+
+ //--------- create dragon ---------//
+ spriteInfo = sprite_res[unit_res[unitId]->sprite_id];
+ teraMask = UnitRes::mobile_type_to_mask(unit_res[unitId]->mobile_type);
+ for(nationCount=1; nationCount<=maxNationCount; nationCount++)
+ {
+ xLoc = 0;
+ yLoc = min(20*nationCount, 180);
+
+ for(int createCount=0; createCount<10; createCount++, xLoc+=4) // createCount<50
+ {
+ for(y=0; y<4; y+=2)
+ {
+ for(x=0; x<4; x+=2)
+ {
+ unitRecno = unit_array.add_unit(unitId, nationCount, RANK_SOLDIER, 100, xLoc+x, yLoc+y);
+ unit_array[unitRecno]->set_combat_level(100);
+ ((UnitGod*)unit_array[unitRecno])->god_id = 1;
+ }
+ }