summaryrefslogtreecommitdiff
path: root/src/uqm
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm')
-rw-r--r--src/uqm/Makeinfo24
-rw-r--r--src/uqm/battle.c512
-rw-r--r--src/uqm/battle.h66
-rw-r--r--src/uqm/battlecontrols.c100
-rw-r--r--src/uqm/battlecontrols.h99
-rw-r--r--src/uqm/border.c200
-rw-r--r--src/uqm/build.c547
-rw-r--r--src/uqm/build.h69
-rw-r--r--src/uqm/cleanup.c99
-rw-r--r--src/uqm/clock.c314
-rw-r--r--src/uqm/clock.h111
-rw-r--r--src/uqm/cnctdlg.c630
-rw-r--r--src/uqm/cnctdlg.h38
-rw-r--r--src/uqm/coderes.h43
-rw-r--r--src/uqm/collide.c183
-rw-r--r--src/uqm/collide.h70
-rw-r--r--src/uqm/colors.h440
-rw-r--r--src/uqm/comm.c1649
-rw-r--r--src/uqm/comm.h142
-rw-r--r--src/uqm/comm/Makeinfo4
-rw-r--r--src/uqm/comm/arilou/Makeinfo2
-rw-r--r--src/uqm/comm/arilou/arilouc.c855
-rw-r--r--src/uqm/comm/arilou/resinst.h9
-rw-r--r--src/uqm/comm/arilou/strings.h123
-rw-r--r--src/uqm/comm/blackur/Makeinfo2
-rw-r--r--src/uqm/comm/blackur/blackurc.c567
-rw-r--r--src/uqm/comm/blackur/resinst.h9
-rw-r--r--src/uqm/comm/blackur/strings.h103
-rw-r--r--src/uqm/comm/chmmr/Makeinfo2
-rw-r--r--src/uqm/comm/chmmr/chmmrc.c641
-rw-r--r--src/uqm/comm/chmmr/resinst.h9
-rw-r--r--src/uqm/comm/chmmr/strings.h105
-rw-r--r--src/uqm/comm/comandr/Makeinfo2
-rw-r--r--src/uqm/comm/comandr/comandr.c694
-rw-r--r--src/uqm/comm/comandr/resinst.h12
-rw-r--r--src/uqm/comm/comandr/strings.h127
-rw-r--r--src/uqm/comm/commall.h26
-rw-r--r--src/uqm/comm/druuge/Makeinfo2
-rw-r--r--src/uqm/comm/druuge/druugec.c926
-rw-r--r--src/uqm/comm/druuge/resinst.h9
-rw-r--r--src/uqm/comm/druuge/strings.h132
-rw-r--r--src/uqm/comm/ilwrath/Makeinfo2
-rw-r--r--src/uqm/comm/ilwrath/ilwrathc.c649
-rw-r--r--src/uqm/comm/ilwrath/resinst.h9
-rw-r--r--src/uqm/comm/ilwrath/strings.h135
-rw-r--r--src/uqm/comm/melnorm/Makeinfo2
-rw-r--r--src/uqm/comm/melnorm/melnorm.c1855
-rw-r--r--src/uqm/comm/melnorm/resinst.h9
-rw-r--r--src/uqm/comm/melnorm/strings.h309
-rw-r--r--src/uqm/comm/mycon/Makeinfo2
-rw-r--r--src/uqm/comm/mycon/myconc.c643
-rw-r--r--src/uqm/comm/mycon/resinst.h9
-rw-r--r--src/uqm/comm/mycon/strings.h136
-rw-r--r--src/uqm/comm/orz/Makeinfo2
-rw-r--r--src/uqm/comm/orz/orzc.c898
-rw-r--r--src/uqm/comm/orz/resinst.h9
-rw-r--r--src/uqm/comm/orz/strings.h143
-rw-r--r--src/uqm/comm/pkunk/Makeinfo2
-rw-r--r--src/uqm/comm/pkunk/pkunkc.c1148
-rw-r--r--src/uqm/comm/pkunk/resinst.h9
-rw-r--r--src/uqm/comm/pkunk/strings.h214
-rw-r--r--src/uqm/comm/rebel/Makeinfo2
-rw-r--r--src/uqm/comm/rebel/rebel.c449
-rw-r--r--src/uqm/comm/rebel/strings.h61
-rw-r--r--src/uqm/comm/shofixt/Makeinfo2
-rw-r--r--src/uqm/comm/shofixt/resinst.h9
-rw-r--r--src/uqm/comm/shofixt/shofixt.c652
-rw-r--r--src/uqm/comm/shofixt/strings.h122
-rw-r--r--src/uqm/comm/slyhome/Makeinfo2
-rw-r--r--src/uqm/comm/slyhome/resinst.h9
-rw-r--r--src/uqm/comm/slyhome/slyhome.c921
-rw-r--r--src/uqm/comm/slyhome/strings.h143
-rw-r--r--src/uqm/comm/slyland/Makeinfo2
-rw-r--r--src/uqm/comm/slyland/resinst.h9
-rw-r--r--src/uqm/comm/slyland/slyland.c518
-rw-r--r--src/uqm/comm/slyland/strings.h113
-rw-r--r--src/uqm/comm/spahome/Makeinfo2
-rw-r--r--src/uqm/comm/spahome/spahome.c1018
-rw-r--r--src/uqm/comm/spahome/strings.h174
-rw-r--r--src/uqm/comm/spathi/Makeinfo2
-rw-r--r--src/uqm/comm/spathi/resinst.h14
-rw-r--r--src/uqm/comm/spathi/spathic.c834
-rw-r--r--src/uqm/comm/spathi/strings.h160
-rw-r--r--src/uqm/comm/starbas/Makeinfo2
-rw-r--r--src/uqm/comm/starbas/starbas.c1961
-rw-r--r--src/uqm/comm/starbas/strings.h327
-rw-r--r--src/uqm/comm/supox/Makeinfo2
-rw-r--r--src/uqm/comm/supox/resinst.h9
-rw-r--r--src/uqm/comm/supox/strings.h124
-rw-r--r--src/uqm/comm/supox/supoxc.c708
-rw-r--r--src/uqm/comm/syreen/Makeinfo2
-rw-r--r--src/uqm/comm/syreen/resinst.h9
-rw-r--r--src/uqm/comm/syreen/strings.h158
-rw-r--r--src/uqm/comm/syreen/syreenc.c878
-rw-r--r--src/uqm/comm/talkpet/Makeinfo2
-rw-r--r--src/uqm/comm/talkpet/resinst.h9
-rw-r--r--src/uqm/comm/talkpet/strings.h140
-rw-r--r--src/uqm/comm/talkpet/talkpet.c841
-rw-r--r--src/uqm/comm/thradd/Makeinfo2
-rw-r--r--src/uqm/comm/thradd/resinst.h9
-rw-r--r--src/uqm/comm/thradd/strings.h181
-rw-r--r--src/uqm/comm/thradd/thraddc.c954
-rw-r--r--src/uqm/comm/umgah/Makeinfo2
-rw-r--r--src/uqm/comm/umgah/resinst.h9
-rw-r--r--src/uqm/comm/umgah/strings.h114
-rw-r--r--src/uqm/comm/umgah/umgahc.c729
-rw-r--r--src/uqm/comm/urquan/Makeinfo2
-rw-r--r--src/uqm/comm/urquan/resinst.h9
-rw-r--r--src/uqm/comm/urquan/strings.h101
-rw-r--r--src/uqm/comm/urquan/urquanc.c555
-rw-r--r--src/uqm/comm/utwig/Makeinfo2
-rw-r--r--src/uqm/comm/utwig/resinst.h10
-rw-r--r--src/uqm/comm/utwig/strings.h144
-rw-r--r--src/uqm/comm/utwig/utwigc.c996
-rw-r--r--src/uqm/comm/vux/Makeinfo2
-rw-r--r--src/uqm/comm/vux/resinst.h9
-rw-r--r--src/uqm/comm/vux/strings.h129
-rw-r--r--src/uqm/comm/vux/vuxc.c796
-rw-r--r--src/uqm/comm/yehat/Makeinfo2
-rw-r--r--src/uqm/comm/yehat/resinst.h11
-rw-r--r--src/uqm/comm/yehat/strings.h102
-rw-r--r--src/uqm/comm/yehat/yehatc.c685
-rw-r--r--src/uqm/comm/zoqfot/Makeinfo2
-rw-r--r--src/uqm/comm/zoqfot/resinst.h9
-rw-r--r--src/uqm/comm/zoqfot/strings.h365
-rw-r--r--src/uqm/comm/zoqfot/zoqfotc.c975
-rw-r--r--src/uqm/commanim.c623
-rw-r--r--src/uqm/commanim.h141
-rw-r--r--src/uqm/commglue.c421
-rw-r--r--src/uqm/commglue.h183
-rw-r--r--src/uqm/confirm.c250
-rw-r--r--src/uqm/cons_res.c112
-rw-r--r--src/uqm/cons_res.h38
-rw-r--r--src/uqm/controls.h172
-rw-r--r--src/uqm/corecode.h49
-rw-r--r--src/uqm/credits.c839
-rw-r--r--src/uqm/credits.h32
-rw-r--r--src/uqm/cyborg.c1339
-rw-r--r--src/uqm/demo.c141
-rw-r--r--src/uqm/demo.h55
-rw-r--r--src/uqm/displist.c274
-rw-r--r--src/uqm/displist.h131
-rw-r--r--src/uqm/dummy.c207
-rw-r--r--src/uqm/dummy.h52
-rw-r--r--src/uqm/element.h242
-rw-r--r--src/uqm/encount.c844
-rw-r--r--src/uqm/encount.h119
-rw-r--r--src/uqm/flash.c805
-rw-r--r--src/uqm/flash.h223
-rw-r--r--src/uqm/fmv.c134
-rw-r--r--src/uqm/fmv.h41
-rw-r--r--src/uqm/galaxy.c464
-rw-r--r--src/uqm/gameev.c729
-rw-r--r--src/uqm/gameev.h68
-rw-r--r--src/uqm/gameinp.c496
-rw-r--r--src/uqm/gameopt.c1347
-rw-r--r--src/uqm/gameopt.h36
-rw-r--r--src/uqm/gamestr.h93
-rw-r--r--src/uqm/gendef.c137
-rw-r--r--src/uqm/gendef.h71
-rw-r--r--src/uqm/getchar.c442
-rw-r--r--src/uqm/globdata.c511
-rw-r--r--src/uqm/globdata.h1059
-rw-r--r--src/uqm/gravity.c200
-rw-r--r--src/uqm/grpinfo.c865
-rw-r--r--src/uqm/grpinfo.h93
-rw-r--r--src/uqm/grpintrn.h56
-rw-r--r--src/uqm/hyper.c1747
-rw-r--r--src/uqm/hyper.h90
-rw-r--r--src/uqm/ifontres.h12
-rw-r--r--src/uqm/igfxres.h274
-rw-r--r--src/uqm/ikey_con.h2
-rw-r--r--src/uqm/imusicre.h20
-rw-r--r--src/uqm/init.c351
-rw-r--r--src/uqm/init.h46
-rw-r--r--src/uqm/intel.c76
-rw-r--r--src/uqm/intel.h85
-rw-r--r--src/uqm/intro.c875
-rw-r--r--src/uqm/ipdisp.c777
-rw-r--r--src/uqm/ipdisp.h37
-rw-r--r--src/uqm/isndres.h7
-rw-r--r--src/uqm/istrtab.h154
-rw-r--r--src/uqm/load.c774
-rw-r--r--src/uqm/load_legacy.c821
-rw-r--r--src/uqm/loadship.c200
-rw-r--r--src/uqm/master.c217
-rw-r--r--src/uqm/master.h69
-rw-r--r--src/uqm/menu.c603
-rw-r--r--src/uqm/menustat.h131
-rw-r--r--src/uqm/misc.c407
-rw-r--r--src/uqm/nameref.h33
-rw-r--r--src/uqm/oscill.c191
-rw-r--r--src/uqm/oscill.h43
-rw-r--r--src/uqm/outfit.c795
-rw-r--r--src/uqm/pickship.c501
-rw-r--r--src/uqm/pickship.h35
-rw-r--r--src/uqm/plandata.c1850
-rw-r--r--src/uqm/planets/Makeinfo7
-rw-r--r--src/uqm/planets/calc.c530
-rw-r--r--src/uqm/planets/cargo.c356
-rw-r--r--src/uqm/planets/devices.c690
-rw-r--r--src/uqm/planets/elemdata.h215
-rw-r--r--src/uqm/planets/generate.h110
-rw-r--r--src/uqm/planets/generate/Makeinfo6
-rw-r--r--src/uqm/planets/generate/genall.h27
-rw-r--r--src/uqm/planets/generate/genand.c164
-rw-r--r--src/uqm/planets/generate/genburv.c192
-rw-r--r--src/uqm/planets/generate/genchmmr.c154
-rw-r--r--src/uqm/planets/generate/gencol.c126
-rw-r--r--src/uqm/planets/generate/gendefault.c373
-rw-r--r--src/uqm/planets/generate/gendefault.h66
-rw-r--r--src/uqm/planets/generate/gendru.c169
-rw-r--r--src/uqm/planets/generate/genilw.c150
-rw-r--r--src/uqm/planets/generate/genmel.c114
-rw-r--r--src/uqm/planets/generate/genmyc.c286
-rw-r--r--src/uqm/planets/generate/genorz.c222
-rw-r--r--src/uqm/planets/generate/genpet.c257
-rw-r--r--src/uqm/planets/generate/genpku.c159
-rw-r--r--src/uqm/planets/generate/genrain.c102
-rw-r--r--src/uqm/planets/generate/gensam.c324
-rw-r--r--src/uqm/planets/generate/genshof.c178
-rw-r--r--src/uqm/planets/generate/gensly.c70
-rw-r--r--src/uqm/planets/generate/gensol.c671
-rw-r--r--src/uqm/planets/generate/genspa.c283
-rw-r--r--src/uqm/planets/generate/gensup.c159
-rw-r--r--src/uqm/planets/generate/gensyr.c102
-rw-r--r--src/uqm/planets/generate/genthrad.c217
-rw-r--r--src/uqm/planets/generate/gentrap.c80
-rw-r--r--src/uqm/planets/generate/genutw.c269
-rw-r--r--src/uqm/planets/generate/genvault.c130
-rw-r--r--src/uqm/planets/generate/genvux.c329
-rw-r--r--src/uqm/planets/generate/genwreck.c111
-rw-r--r--src/uqm/planets/generate/genyeh.c140
-rw-r--r--src/uqm/planets/generate/genzfpscout.c96
-rw-r--r--src/uqm/planets/generate/genzoq.c170
-rw-r--r--src/uqm/planets/gentopo.c206
-rw-r--r--src/uqm/planets/lander.c2101
-rw-r--r--src/uqm/planets/lander.h88
-rw-r--r--src/uqm/planets/lifeform.h75
-rw-r--r--src/uqm/planets/orbits.c629
-rw-r--r--src/uqm/planets/oval.c329
-rw-r--r--src/uqm/planets/pl_stuff.c318
-rw-r--r--src/uqm/planets/plandata.h318
-rw-r--r--src/uqm/planets/planets.c483
-rw-r--r--src/uqm/planets/planets.h322
-rw-r--r--src/uqm/planets/plangen.c1954
-rw-r--r--src/uqm/planets/pstarmap.c1631
-rw-r--r--src/uqm/planets/report.c271
-rw-r--r--src/uqm/planets/roster.c428
-rw-r--r--src/uqm/planets/scan.c1385
-rw-r--r--src/uqm/planets/scan.h72
-rw-r--r--src/uqm/planets/solarsys.c2021
-rw-r--r--src/uqm/planets/solarsys.h34
-rw-r--r--src/uqm/planets/sundata.h73
-rw-r--r--src/uqm/planets/surface.c251
-rw-r--r--src/uqm/process.c1108
-rw-r--r--src/uqm/process.h37
-rw-r--r--src/uqm/races.h675
-rw-r--r--src/uqm/resinst.h24
-rw-r--r--src/uqm/restart.c413
-rw-r--r--src/uqm/restart.h33
-rw-r--r--src/uqm/save.c813
-rw-r--r--src/uqm/save.h78
-rw-r--r--src/uqm/settings.c97
-rw-r--r--src/uqm/settings.h41
-rw-r--r--src/uqm/setup.c332
-rw-r--r--src/uqm/setup.h89
-rw-r--r--src/uqm/setupmenu.c1613
-rw-r--r--src/uqm/setupmenu.h100
-rw-r--r--src/uqm/ship.c574
-rw-r--r--src/uqm/ship.h43
-rw-r--r--src/uqm/shipcont.h44
-rw-r--r--src/uqm/ships/Makeinfo5
-rw-r--r--src/uqm/ships/androsyn/Makeinfo2
-rw-r--r--src/uqm/ships/androsyn/androsyn.c528
-rw-r--r--src/uqm/ships/androsyn/androsyn.h31
-rw-r--r--src/uqm/ships/androsyn/icode.h5
-rw-r--r--src/uqm/ships/androsyn/resinst.h19
-rw-r--r--src/uqm/ships/arilou/Makeinfo2
-rw-r--r--src/uqm/ships/arilou/arilou.c303
-rw-r--r--src/uqm/ships/arilou/arilou.h31
-rw-r--r--src/uqm/ships/arilou/icode.h5
-rw-r--r--src/uqm/ships/arilou/resinst.h16
-rw-r--r--src/uqm/ships/blackurq/Makeinfo2
-rw-r--r--src/uqm/ships/blackurq/blackurq.c567
-rw-r--r--src/uqm/ships/blackurq/blackurq.h31
-rw-r--r--src/uqm/ships/blackurq/icode.h5
-rw-r--r--src/uqm/ships/blackurq/resinst.h19
-rw-r--r--src/uqm/ships/chenjesu/Makeinfo2
-rw-r--r--src/uqm/ships/chenjesu/chenjesu.c588
-rw-r--r--src/uqm/ships/chenjesu/chenjesu.h31
-rw-r--r--src/uqm/ships/chenjesu/icode.h5
-rw-r--r--src/uqm/ships/chenjesu/resinst.h19
-rw-r--r--src/uqm/ships/chmmr/Makeinfo2
-rw-r--r--src/uqm/ships/chmmr/chmmr.c790
-rw-r--r--src/uqm/ships/chmmr/chmmr.h31
-rw-r--r--src/uqm/ships/chmmr/icode.h5
-rw-r--r--src/uqm/ships/chmmr/resinst.h19
-rw-r--r--src/uqm/ships/druuge/Makeinfo2
-rw-r--r--src/uqm/ships/druuge/druuge.c324
-rw-r--r--src/uqm/ships/druuge/druuge.h31
-rw-r--r--src/uqm/ships/druuge/icode.h5
-rw-r--r--src/uqm/ships/druuge/resinst.h16
-rw-r--r--src/uqm/ships/human/Makeinfo2
-rw-r--r--src/uqm/ships/human/human.c360
-rw-r--r--src/uqm/ships/human/human.h31
-rw-r--r--src/uqm/ships/human/icode.h5
-rw-r--r--src/uqm/ships/human/resinst.h16
-rw-r--r--src/uqm/ships/ilwrath/Makeinfo2
-rw-r--r--src/uqm/ships/ilwrath/icode.h5
-rw-r--r--src/uqm/ships/ilwrath/ilwrath.c409
-rw-r--r--src/uqm/ships/ilwrath/ilwrath.h31
-rw-r--r--src/uqm/ships/ilwrath/resinst.h16
-rw-r--r--src/uqm/ships/lastbat/Makeinfo2
-rw-r--r--src/uqm/ships/lastbat/icode.h5
-rw-r--r--src/uqm/ships/lastbat/lastbat.c926
-rw-r--r--src/uqm/ships/lastbat/lastbat.h31
-rw-r--r--src/uqm/ships/lastbat/resinst.h16
-rw-r--r--src/uqm/ships/melnorme/Makeinfo2
-rw-r--r--src/uqm/ships/melnorme/icode.h5
-rw-r--r--src/uqm/ships/melnorme/melnorme.c658
-rw-r--r--src/uqm/ships/melnorme/melnorme.h31
-rw-r--r--src/uqm/ships/melnorme/resinst.h19
-rw-r--r--src/uqm/ships/mmrnmhrm/Makeinfo2
-rw-r--r--src/uqm/ships/mmrnmhrm/icode.h5
-rw-r--r--src/uqm/ships/mmrnmhrm/mmrnmhrm.c527
-rw-r--r--src/uqm/ships/mmrnmhrm/mmrnmhrm.h31
-rw-r--r--src/uqm/ships/mmrnmhrm/resinst.h19
-rw-r--r--src/uqm/ships/mycon/Makeinfo2
-rw-r--r--src/uqm/ships/mycon/icode.h5
-rw-r--r--src/uqm/ships/mycon/mycon.c376
-rw-r--r--src/uqm/ships/mycon/mycon.h31
-rw-r--r--src/uqm/ships/mycon/resinst.h16
-rw-r--r--src/uqm/ships/orz/Makeinfo2
-rw-r--r--src/uqm/ships/orz/icode.h5
-rw-r--r--src/uqm/ships/orz/orz.c1083
-rw-r--r--src/uqm/ships/orz/orz.h35
-rw-r--r--src/uqm/ships/orz/resinst.h19
-rw-r--r--src/uqm/ships/pkunk/Makeinfo2
-rw-r--r--src/uqm/ships/pkunk/icode.h5
-rw-r--r--src/uqm/ships/pkunk/pkunk.c640
-rw-r--r--src/uqm/ships/pkunk/pkunk.h31
-rw-r--r--src/uqm/ships/pkunk/resinst.h16
-rw-r--r--src/uqm/ships/probe/Makeinfo2
-rw-r--r--src/uqm/ships/probe/icode.h5
-rw-r--r--src/uqm/ships/probe/probe.c118
-rw-r--r--src/uqm/ships/probe/probe.h31
-rw-r--r--src/uqm/ships/probe/resinst.h5
-rw-r--r--src/uqm/ships/ship.h37
-rw-r--r--src/uqm/ships/shofixti/Makeinfo2
-rw-r--r--src/uqm/ships/shofixti/icode.h5
-rw-r--r--src/uqm/ships/shofixti/resinst.h23
-rw-r--r--src/uqm/ships/shofixti/shofixti.c521
-rw-r--r--src/uqm/ships/shofixti/shofixti.h31
-rw-r--r--src/uqm/ships/sis_ship/Makeinfo2
-rw-r--r--src/uqm/ships/sis_ship/icode.h5
-rw-r--r--src/uqm/ships/sis_ship/resinst.h15
-rw-r--r--src/uqm/ships/sis_ship/sis_ship.c1002
-rw-r--r--src/uqm/ships/sis_ship/sis_ship.h31
-rw-r--r--src/uqm/ships/slylandr/Makeinfo2
-rw-r--r--src/uqm/ships/slylandr/icode.h5
-rw-r--r--src/uqm/ships/slylandr/resinst.h13
-rw-r--r--src/uqm/ships/slylandr/slylandr.c438
-rw-r--r--src/uqm/ships/slylandr/slylandr.h31
-rw-r--r--src/uqm/ships/spathi/Makeinfo2
-rw-r--r--src/uqm/ships/spathi/icode.h5
-rw-r--r--src/uqm/ships/spathi/resinst.h19
-rw-r--r--src/uqm/ships/spathi/spathi.c301
-rw-r--r--src/uqm/ships/spathi/spathi.h31
-rw-r--r--src/uqm/ships/supox/Makeinfo2
-rw-r--r--src/uqm/ships/supox/icode.h5
-rw-r--r--src/uqm/ships/supox/resinst.h16
-rw-r--r--src/uqm/ships/supox/supox.c288
-rw-r--r--src/uqm/ships/supox/supox.h31
-rw-r--r--src/uqm/ships/syreen/Makeinfo2
-rw-r--r--src/uqm/ships/syreen/icode.h5
-rw-r--r--src/uqm/ships/syreen/resinst.h16
-rw-r--r--src/uqm/ships/syreen/syreen.c284
-rw-r--r--src/uqm/ships/syreen/syreen.h31
-rw-r--r--src/uqm/ships/thradd/Makeinfo2
-rw-r--r--src/uqm/ships/thradd/icode.h5
-rw-r--r--src/uqm/ships/thradd/resinst.h19
-rw-r--r--src/uqm/ships/thradd/thradd.c400
-rw-r--r--src/uqm/ships/thradd/thradd.h31
-rw-r--r--src/uqm/ships/umgah/Makeinfo2
-rw-r--r--src/uqm/ships/umgah/icode.h5
-rw-r--r--src/uqm/ships/umgah/resinst.h17
-rw-r--r--src/uqm/ships/umgah/umgah.c434
-rw-r--r--src/uqm/ships/umgah/umgah.h31
-rw-r--r--src/uqm/ships/urquan/Makeinfo2
-rw-r--r--src/uqm/ships/urquan/icode.h5
-rw-r--r--src/uqm/ships/urquan/resinst.h19
-rw-r--r--src/uqm/ships/urquan/urquan.c554
-rw-r--r--src/uqm/ships/urquan/urquan.h31
-rw-r--r--src/uqm/ships/utwig/Makeinfo2
-rw-r--r--src/uqm/ships/utwig/icode.h5
-rw-r--r--src/uqm/ships/utwig/resinst.h16
-rw-r--r--src/uqm/ships/utwig/utwig.c380
-rw-r--r--src/uqm/ships/utwig/utwig.h31
-rw-r--r--src/uqm/ships/vux/Makeinfo2
-rw-r--r--src/uqm/ships/vux/icode.h5
-rw-r--r--src/uqm/ships/vux/resinst.h17
-rw-r--r--src/uqm/ships/vux/vux.c398
-rw-r--r--src/uqm/ships/vux/vux.h31
-rw-r--r--src/uqm/ships/yehat/Makeinfo2
-rw-r--r--src/uqm/ships/yehat/icode.h5
-rw-r--r--src/uqm/ships/yehat/resinst.h19
-rw-r--r--src/uqm/ships/yehat/yehat.c369
-rw-r--r--src/uqm/ships/yehat/yehat.h31
-rw-r--r--src/uqm/ships/zoqfot/Makeinfo2
-rw-r--r--src/uqm/ships/zoqfot/icode.h5
-rw-r--r--src/uqm/ships/zoqfot/resinst.h19
-rw-r--r--src/uqm/ships/zoqfot/zoqfot.c377
-rw-r--r--src/uqm/ships/zoqfot/zoqfot.h31
-rw-r--r--src/uqm/shipstat.c437
-rw-r--r--src/uqm/shipyard.c1495
-rw-r--r--src/uqm/sis.c1741
-rw-r--r--src/uqm/sis.h241
-rw-r--r--src/uqm/sounds.c199
-rw-r--r--src/uqm/sounds.h85
-rw-r--r--src/uqm/starbase.c602
-rw-r--r--src/uqm/starbase.h55
-rw-r--r--src/uqm/starcon.c323
-rw-r--r--src/uqm/starcon.h35
-rw-r--r--src/uqm/starmap.c125
-rw-r--r--src/uqm/starmap.h42
-rw-r--r--src/uqm/state.c354
-rw-r--r--src/uqm/state.h166
-rw-r--r--src/uqm/status.c582
-rw-r--r--src/uqm/status.h75
-rw-r--r--src/uqm/supermelee/Makeinfo5
-rw-r--r--src/uqm/supermelee/buildpick.c221
-rw-r--r--src/uqm/supermelee/buildpick.h25
-rw-r--r--src/uqm/supermelee/loadmele.c826
-rw-r--r--src/uqm/supermelee/loadmele.h67
-rw-r--r--src/uqm/supermelee/melee.c2640
-rw-r--r--src/uqm/supermelee/melee.h144
-rw-r--r--src/uqm/supermelee/meleesetup.c440
-rw-r--r--src/uqm/supermelee/meleesetup.h143
-rw-r--r--src/uqm/supermelee/meleeship.h55
-rw-r--r--src/uqm/supermelee/netplay/FILES50
-rw-r--r--src/uqm/supermelee/netplay/Makeinfo4
-rw-r--r--src/uqm/supermelee/netplay/checkbuf.c145
-rw-r--r--src/uqm/supermelee/netplay/checkbuf.h77
-rw-r--r--src/uqm/supermelee/netplay/checksum.c302
-rw-r--r--src/uqm/supermelee/netplay/checksum.h99
-rw-r--r--src/uqm/supermelee/netplay/crc.c142
-rw-r--r--src/uqm/supermelee/netplay/crc.h60
-rw-r--r--src/uqm/supermelee/netplay/nc_connect.ci300
-rw-r--r--src/uqm/supermelee/netplay/netconnection.c378
-rw-r--r--src/uqm/supermelee/netplay/netconnection.h260
-rw-r--r--src/uqm/supermelee/netplay/netinput.c157
-rw-r--r--src/uqm/supermelee/netplay/netinput.h57
-rw-r--r--src/uqm/supermelee/netplay/netmelee.c740
-rw-r--r--src/uqm/supermelee/netplay/netmelee.h90
-rw-r--r--src/uqm/supermelee/netplay/netmisc.c134
-rw-r--r--src/uqm/supermelee/netplay/netmisc.h77
-rw-r--r--src/uqm/supermelee/netplay/netoptions.c39
-rw-r--r--src/uqm/supermelee/netplay/netoptions.h56
-rw-r--r--src/uqm/supermelee/netplay/netplay.h77
-rw-r--r--src/uqm/supermelee/netplay/netrcv.c193
-rw-r--r--src/uqm/supermelee/netplay/netrcv.h34
-rw-r--r--src/uqm/supermelee/netplay/netsend.c95
-rw-r--r--src/uqm/supermelee/netplay/netsend.h35
-rw-r--r--src/uqm/supermelee/netplay/netstate.c48
-rw-r--r--src/uqm/supermelee/netplay/netstate.h83
-rw-r--r--src/uqm/supermelee/netplay/notify.c118
-rw-r--r--src/uqm/supermelee/netplay/notify.h62
-rw-r--r--src/uqm/supermelee/netplay/notifyall.c146
-rw-r--r--src/uqm/supermelee/netplay/notifyall.h49
-rw-r--r--src/uqm/supermelee/netplay/packet.c263
-rw-r--r--src/uqm/supermelee/netplay/packet.h299
-rw-r--r--src/uqm/supermelee/netplay/packethandlers.c649
-rw-r--r--src/uqm/supermelee/netplay/packethandlers.h56
-rw-r--r--src/uqm/supermelee/netplay/packetq.c149
-rw-r--r--src/uqm/supermelee/netplay/packetq.h59
-rw-r--r--src/uqm/supermelee/netplay/packetsenders.c197
-rw-r--r--src/uqm/supermelee/netplay/packetsenders.h67
-rw-r--r--src/uqm/supermelee/netplay/proto/Makeinfo2
-rw-r--r--src/uqm/supermelee/netplay/proto/npconfirm.c81
-rw-r--r--src/uqm/supermelee/netplay/proto/npconfirm.h36
-rw-r--r--src/uqm/supermelee/netplay/proto/ready.c106
-rw-r--r--src/uqm/supermelee/netplay/proto/ready.h38
-rw-r--r--src/uqm/supermelee/netplay/proto/reset.c166
-rw-r--r--src/uqm/supermelee/netplay/proto/reset.h41
-rw-r--r--src/uqm/supermelee/pickmele.c948
-rw-r--r--src/uqm/supermelee/pickmele.h102
-rw-r--r--src/uqm/tactrans.c1032
-rw-r--r--src/uqm/tactrans.h59
-rw-r--r--src/uqm/trans.c154
-rw-r--r--src/uqm/units.h227
-rw-r--r--src/uqm/uqmdebug.c1926
-rw-r--r--src/uqm/uqmdebug.h200
-rw-r--r--src/uqm/util.c312
-rw-r--r--src/uqm/util.h39
-rw-r--r--src/uqm/velocity.c153
-rw-r--r--src/uqm/velocity.h76
-rw-r--r--src/uqm/weapon.c414
-rw-r--r--src/uqm/weapon.h68
499 files changed, 124122 insertions, 0 deletions
diff --git a/src/uqm/Makeinfo b/src/uqm/Makeinfo
new file mode 100644
index 0000000..4b224fa
--- /dev/null
+++ b/src/uqm/Makeinfo
@@ -0,0 +1,24 @@
+uqm_SUBDIRS="comm planets ships supermelee"
+uqm_CFILES="battle.c battlecontrols.c border.c build.c cleanup.c clock.c
+ cnctdlg.c collide.c comm.c commanim.c commglue.c confirm.c credits.c
+ cyborg.c demo.c displist.c dummy.c encount.c flash.c fmv.c galaxy.c
+ gameev.c gameinp.c gameopt.c gendef.c getchar.c globdata.c gravity.c
+ cons_res.c grpinfo.c hyper.c init.c intel.c intro.c ipdisp.c load.c
+ load_legacy.c
+ loadship.c master.c menu.c misc.c oscill.c outfit.c pickship.c
+ plandata.c process.c restart.c save.c settings.c setup.c setupmenu.c
+ ship.c shipstat.c shipyard.c sis.c sounds.c starbase.c starcon.c
+ starmap.c state.c status.c tactrans.c trans.c uqmdebug.c util.c
+ velocity.c weapon.c"
+uqm_HFILES="battlecontrols.h battle.h build.h clock.h cnctdlg.h coderes.h
+ collide.h colors.h commanim.h commglue.h comm.h cons_res.h controls.h
+ corecode.h credits.h demo.h displist.h dummy.h element.h encount.h
+ flash.h fmv.h gameev.h gameopt.h gamestr.h gendef.h globdata.h
+ grpinfo.h hyper.h ifontres.h igfxres.h ikey_con.h imusicre.h init.h
+ intel.h ipdisp.h isndres.h istrtab.h master.h menustat.h
+ nameref.h oscill.h pickship.h process.h races.h resinst.h respkg.h
+ restart.h save.h settings.h setup.h setupmenu.h shipcont.h ship.h
+ sis.h sounds.h starbase.h starcon.h state.h status.h tactrans.h
+ starmap.h
+ units.h uqmdebug.h util.h velocity.h weapon.h"
+
diff --git a/src/uqm/battle.c b/src/uqm/battle.c
new file mode 100644
index 0000000..f33e66d
--- /dev/null
+++ b/src/uqm/battle.c
@@ -0,0 +1,512 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "battle.h"
+
+#include "battlecontrols.h"
+#include "controls.h"
+#include "init.h"
+#include "element.h"
+#include "ship.h"
+#include "process.h"
+#include "tactrans.h"
+ // for flee_preprocess()
+#include "intel.h"
+#ifdef NETPLAY
+# include "supermelee/netplay/netmelee.h"
+# ifdef NETPLAY_CHECKSUM
+# include "supermelee/netplay/checksum.h"
+# endif
+# include "supermelee/netplay/notifyall.h"
+#endif
+#include "supermelee/pickmele.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "setup.h"
+#include "settings.h"
+#include "sounds.h"
+#include "libs/async.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/log.h"
+#include "libs/mathlib.h"
+
+
+BYTE battle_counter[NUM_SIDES];
+ // The number of ships still available for battle to each side.
+ // A ship that has warped out is no longer available.
+BOOLEAN instantVictory;
+size_t battleInputOrder[NUM_SIDES];
+ // Indices of the sides in the order their input is processed.
+ // Network sides are last so that the sides will never be waiting
+ // on eachother, and games with a 0 frame delay are theoretically
+ // possible.
+#ifdef NETPLAY
+BattleFrameCounter battleFrameCount;
+ // Used for synchronisation purposes during netplay.
+#endif
+
+static BOOLEAN
+RunAwayAllowed (void)
+{
+ return (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER
+ || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ && GET_GAME_STATE (STARBASE_AVAILABLE)
+ && !GET_GAME_STATE (BOMB_CARRIER);
+}
+
+static void
+DoRunAway (STARSHIP *StarShipPtr)
+{
+ ELEMENT *ElementPtr;
+
+ LockElement (StarShipPtr->hShip, &ElementPtr);
+ if (GetPrimType (&DisplayArray[ElementPtr->PrimIndex]) == STAMP_PRIM
+ && ElementPtr->life_span == NORMAL_LIFE
+ && !(ElementPtr->state_flags & FINITE_LIFE)
+ && ElementPtr->mass_points != MAX_SHIP_MASS * 10
+ && !(ElementPtr->state_flags & APPEARING))
+ {
+ battle_counter[0]--;
+
+ ElementPtr->turn_wait = 3;
+ ElementPtr->thrust_wait = 4;
+ ElementPtr->colorCycleIndex = 0;
+ ElementPtr->preprocess_func = flee_preprocess;
+ ElementPtr->mass_points = MAX_SHIP_MASS * 10;
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+
+ SetPrimColor (&DisplayArray[ElementPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x0B, 0x00, 0x00), 0x2E));
+ // XXX: I think this is supposed to be the same as the
+ // first entry of the color cycle table in flee_preeprocess,
+ // but it is slightly different (0x0A as red value). - SvdB.
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMPFILL_PRIM);
+
+ StarShipPtr->ship_input_state = 0;
+ }
+ UnlockElement (StarShipPtr->hShip);
+}
+
+static void
+setupBattleInputOrder(void)
+{
+ size_t i;
+
+#ifndef NETPLAY
+ for (i = 0; i < NUM_SIDES; i++)
+ battleInputOrder[i] = i;
+#else
+ int j;
+
+ i = 0;
+ // First put the locally controlled players in the array.
+ for (j = 0; j < NUM_SIDES; j++) {
+ if (!(PlayerControl[j] & NETWORK_CONTROL)) {
+ battleInputOrder[i] = j;
+ i++;
+ }
+ }
+
+ // Next put the network controlled players in the array.
+ for (j = 0; j < NUM_SIDES; j++) {
+ if (PlayerControl[j] & NETWORK_CONTROL) {
+ battleInputOrder[i] = j;
+ i++;
+ }
+ }
+#endif
+}
+
+BATTLE_INPUT_STATE
+frameInputHuman (HumanInputContext *context, STARSHIP *StarShipPtr)
+{
+ (void) StarShipPtr;
+ return CurrentInputToBattleInput (context->playerNr);
+}
+
+static void
+ProcessInput (void)
+{
+ BOOLEAN CanRunAway;
+ size_t sideI;
+
+#ifdef NETPLAY
+ netInput ();
+#endif
+
+ CanRunAway = RunAwayAllowed ();
+
+ for (sideI = 0; sideI < NUM_SIDES; sideI++)
+ {
+ HSTARSHIP hBattleShip, hNextShip;
+ size_t cur_player = battleInputOrder[sideI];
+
+ for (hBattleShip = GetHeadLink (&race_q[cur_player]);
+ hBattleShip != 0; hBattleShip = hNextShip)
+ {
+ BATTLE_INPUT_STATE InputState;
+ STARSHIP *StarShipPtr;
+
+ StarShipPtr = LockStarShip (&race_q[cur_player], hBattleShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+
+ if (StarShipPtr->hShip)
+ {
+ // TODO: review and see if we have to do this every frame, or
+ // if we can do this once somewhere
+ StarShipPtr->control = PlayerControl[cur_player];
+
+ InputState = PlayerInput[cur_player]->handlers->frameInput (
+ PlayerInput[cur_player], StarShipPtr);
+
+#if CREATE_JOURNAL
+ JournalInput (InputState);
+#endif /* CREATE_JOURNAL */
+#ifdef NETPLAY
+ if (!(PlayerControl[cur_player] & NETWORK_CONTROL))
+ {
+ BattleInputBuffer *bib = getBattleInputBuffer(cur_player);
+ Netplay_NotifyAll_battleInput (InputState);
+ flushPacketQueues ();
+
+ BattleInputBuffer_push (bib, InputState);
+ // Add this input to the end of the buffer.
+ BattleInputBuffer_pop (bib, &InputState);
+ // Get the input from the front of the buffer.
+ }
+#endif
+
+ StarShipPtr->ship_input_state = 0;
+ if (StarShipPtr->RaceDescPtr->ship_info.crew_level)
+ {
+ if (InputState & BATTLE_LEFT)
+ StarShipPtr->ship_input_state |= LEFT;
+ else if (InputState & BATTLE_RIGHT)
+ StarShipPtr->ship_input_state |= RIGHT;
+ if (InputState & BATTLE_THRUST)
+ StarShipPtr->ship_input_state |= THRUST;
+ if (InputState & BATTLE_WEAPON)
+ StarShipPtr->ship_input_state |= WEAPON;
+ if (InputState & BATTLE_SPECIAL)
+ StarShipPtr->ship_input_state |= SPECIAL;
+
+ if (CanRunAway && cur_player == 0 &&
+ (InputState & BATTLE_ESCAPE))
+ DoRunAway (StarShipPtr);
+ }
+ }
+
+ UnlockStarShip (&race_q[cur_player], hBattleShip);
+ }
+ }
+
+#ifdef NETPLAY
+ flushPacketQueues ();
+#endif
+
+ if (GLOBAL (CurrentActivity) & (CHECK_LOAD | CHECK_ABORT))
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+}
+
+#if DEMO_MODE || CREATE_JOURNAL
+DWORD BattleSeed;
+#endif /* DEMO_MODE */
+
+static MUSIC_REF BattleRef;
+
+void
+BattleSong (BOOLEAN DoPlay)
+{
+ if (BattleRef == 0)
+ {
+ if (inHyperSpace ())
+ BattleRef = LoadMusic (HYPERSPACE_MUSIC);
+ else if (inQuasiSpace ())
+ BattleRef = LoadMusic (QUASISPACE_MUSIC);
+ else
+ BattleRef = LoadMusic (BATTLE_MUSIC);
+ }
+
+ if (DoPlay)
+ PlayMusic (BattleRef, TRUE, 1);
+}
+
+void
+FreeBattleSong (void)
+{
+ DestroyMusic (BattleRef);
+ BattleRef = 0;
+}
+
+static BOOLEAN
+DoBattle (BATTLE_STATE *bs)
+{
+ extern UWORD nth_frame;
+ RECT r;
+ BYTE battle_speed;
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+
+#if defined (NETPLAY) && defined (NETPLAY_CHECKSUM)
+ if (getNumNetConnections() > 0 &&
+ battleFrameCount % NETPLAY_CHECKSUM_INTERVAL == 0)
+ {
+ crc_State state;
+ Checksum checksum;
+
+ crc_init(&state);
+ crc_processState (&state);
+ checksum = (Checksum) crc_finish (&state);
+
+ Netplay_NotifyAll_checksum ((uint32) battleFrameCount,
+ (uint32) checksum);
+ flushPacketQueues ();
+ addLocalChecksum (battleFrameCount, checksum);
+ }
+#endif
+ ProcessInput ();
+ // Also calls NetInput()
+#if defined (NETPLAY) && defined (NETPLAY_CHECKSUM)
+ if (getNumNetConnections() > 0)
+ {
+ size_t delay = getBattleInputDelay();
+
+ if (battleFrameCount >= delay
+ && (battleFrameCount - delay) % NETPLAY_CHECKSUM_INTERVAL == 0)
+ {
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ if (!verifyChecksums (battleFrameCount - delay)) {
+ GLOBAL(CurrentActivity) |= CHECK_ABORT;
+ resetConnections (ResetReason_syncLoss);
+ }
+ }
+ }
+ }
+#endif
+
+ if (bs->first_time)
+ {
+ r.corner.x = SIS_ORG_X;
+ r.corner.y = SIS_ORG_Y;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = SIS_SCREEN_HEIGHT;
+ SetTransitionSource (&r);
+ }
+ BatchGraphics ();
+
+ // Call the callback function, if set
+ if (bs->frame_cb)
+ bs->frame_cb ();
+
+ RedrawQueue (TRUE);
+
+ if (bs->first_time)
+ {
+ bs->first_time = FALSE;
+ ScreenTransition (3, &r);
+ }
+ UnbatchGraphics ();
+ if ((!(GLOBAL (CurrentActivity) & IN_BATTLE)) ||
+ (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ return FALSE;
+ }
+
+ battle_speed = HIBYTE (nth_frame);
+ if (battle_speed == (BYTE)~0)
+ { // maximum speed, nothing rendered at all
+ Async_process ();
+ TaskSwitch ();
+ }
+ else
+ {
+ SleepThreadUntil (bs->NextTime
+ + BATTLE_FRAME_RATE / (battle_speed + 1));
+ bs->NextTime = GetTimeCounter ();
+ }
+
+ if ((GLOBAL (CurrentActivity) & IN_BATTLE) == 0)
+ return FALSE;
+
+#ifdef NETPLAY
+ battleFrameCount++;
+#endif
+ return TRUE;
+}
+
+#ifdef NETPLAY
+COUNT
+GetPlayerOrder (COUNT i)
+{
+ // Iff 'myTurn' is set on a connection, the local party will be
+ // processed first.
+ // If neither is network controlled, the top player (1) is handled
+ // first.
+ if (((PlayerControl[0] & NETWORK_CONTROL) &&
+ !NetConnection_getDiscriminant (netConnections[0])) ||
+ ((PlayerControl[1] & NETWORK_CONTROL) &&
+ NetConnection_getDiscriminant (netConnections[1])))
+ return i;
+ else
+ return 1 - i;
+}
+#endif
+
+// Let each player pick his ship.
+static BOOLEAN
+selectAllShips (SIZE num_ships)
+{
+ if (num_ships == 1) {
+ // HyperSpace in full game.
+ return GetNextStarShip (NULL, 0);
+ }
+
+#ifdef NETPLAY
+ if ((PlayerControl[0] & NETWORK_CONTROL) &&
+ (PlayerControl[1] & NETWORK_CONTROL))
+ {
+ log_add (log_Error, "Only one side at a time can be network "
+ "controlled.\n");
+ return FALSE;
+ }
+#endif
+
+ return GetInitialStarShips ();
+}
+
+BOOLEAN
+Battle (BattleFrameCallback *callback)
+{
+ SIZE num_ships;
+
+
+#if !(DEMO_MODE || CREATE_JOURNAL)
+ if (LOBYTE (GLOBAL (CurrentActivity)) != SUPER_MELEE) {
+ // In Supermelee, the RNG is already initialised.
+ TFB_SeedRandom (GetTimeCounter ());
+ }
+#else /* DEMO_MODE */
+ if (BattleSeed == 0)
+ BattleSeed = TFB_Random ();
+ TFB_SeedRandom (BattleSeed);
+ BattleSeed = TFB_Random (); /* get next battle seed */
+#endif /* DEMO_MODE */
+
+ BattleSong (FALSE);
+
+ num_ships = InitShips ();
+
+ if (instantVictory)
+ {
+ num_ships = 0;
+ battle_counter[0] = 1;
+ battle_counter[1] = 0;
+ instantVictory = FALSE;
+ }
+
+ if (num_ships)
+ {
+ BATTLE_STATE bs;
+
+ GLOBAL (CurrentActivity) |= IN_BATTLE;
+ battle_counter[0] = CountLinks (&race_q[0]);
+ battle_counter[1] = CountLinks (&race_q[1]);
+
+ if (optMeleeScale != TFB_SCALE_STEP)
+ SetGraphicScaleMode (optMeleeScale);
+
+ setupBattleInputOrder ();
+#ifdef NETPLAY
+ initBattleInputBuffers ();
+#ifdef NETPLAY_CHECKSUM
+ initChecksumBuffers ();
+#endif /* NETPLAY_CHECKSUM */
+ battleFrameCount = 0;
+ ResetWinnerStarShip ();
+ setBattleStateConnections (&bs);
+#endif /* NETPLAY */
+
+ if (!selectAllShips (num_ships)) {
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ goto AbortBattle;
+ }
+
+ BattleSong (TRUE);
+ bs.NextTime = 0;
+#ifdef NETPLAY
+ initBattleStateDataConnections ();
+ {
+ bool allOk = negotiateReadyConnections (true, NetState_inBattle);
+ if (!allOk) {
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ goto AbortBattle;
+ }
+ }
+#endif /* NETPLAY */
+ bs.InputFunc = DoBattle;
+ bs.frame_cb = callback;
+ bs.first_time = inHQSpace ();
+
+ DoInput (&bs, FALSE);
+
+AbortBattle:
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ {
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ // Do not return to the main menu when a game is aborted,
+ // (just to the supermelee menu).
+#ifdef NETPLAY
+ waitResetConnections(NetState_inSetup);
+ // A connection may already be in inSetup (set from
+ // GetMeleeStarship). This is not a problem, although
+ // it will generate a warning in debug mode.
+#endif
+
+ GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
+ }
+ else
+ {
+ // Show the result of the battle.
+ MeleeGameOver ();
+ }
+ }
+
+#ifdef NETPLAY
+ uninitBattleInputBuffers();
+#ifdef NETPLAY_CHECKSUM
+ uninitChecksumBuffers ();
+#endif /* NETPLAY_CHECKSUM */
+ setBattleStateConnections (NULL);
+#endif /* NETPLAY */
+
+ StopDitty ();
+ StopMusic ();
+ StopSound ();
+ }
+
+ UninitShips ();
+ FreeBattleSong ();
+
+
+ return (BOOLEAN) (num_ships < 0);
+}
+
diff --git a/src/uqm/battle.h b/src/uqm/battle.h
new file mode 100644
index 0000000..2b1d912
--- /dev/null
+++ b/src/uqm/battle.h
@@ -0,0 +1,66 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef UQM_BATTLE_H_
+#define UQM_BATTLE_H_
+
+#include "options.h"
+#include "libs/compiler.h"
+
+#if defined (NETPLAY)
+typedef DWORD BattleFrameCounter;
+#endif
+
+#include "init.h"
+ // For NUM_SIDES
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// The callback function is called on every battle frame
+// just before the display queue is drawn
+typedef void (BattleFrameCallback) (void);
+
+typedef struct battlestate_struct {
+ BOOLEAN (*InputFunc) (struct battlestate_struct *pInputState);
+ BOOLEAN first_time;
+ DWORD NextTime;
+ BattleFrameCallback *frame_cb;
+} BATTLE_STATE;
+
+extern BYTE battle_counter[NUM_SIDES];
+extern BOOLEAN instantVictory;
+#if defined (NETPLAY)
+extern BattleFrameCounter battleFrameCount;
+#endif
+#ifdef NETPLAY
+COUNT GetPlayerOrder (COUNT i);
+#else
+# define GetPlayerOrder(i) (i)
+#endif
+
+BOOLEAN Battle (BattleFrameCallback *);
+
+#define BATTLE_FRAME_RATE (ONE_SECOND / 24)
+
+extern void BattleSong (BOOLEAN DoPlay);
+extern void FreeBattleSong (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_BATTLE_H_ */
diff --git a/src/uqm/battlecontrols.c b/src/uqm/battlecontrols.c
new file mode 100644
index 0000000..0f241e6
--- /dev/null
+++ b/src/uqm/battlecontrols.c
@@ -0,0 +1,100 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "battlecontrols.h"
+
+#include "intel.h"
+ // For computer_intelligence()
+#include "tactrans.h"
+ // For battleEndReady*
+#include "init.h"
+ // For NUM_PLAYERS
+#include "libs/memlib.h"
+ // For HMalloc(), HFree()
+#ifdef NETPLAY
+# include "supermelee/netplay/netmelee.h"
+#endif /* NETPLAY */
+
+InputContext *PlayerInput[NUM_PLAYERS];
+
+BattleInputHandlers ComputerInputHandlers = {
+ /* .frameInput = */ (BattleFrameInputFunction) computer_intelligence,
+ /* .selectShip = */ (SelectShipFunction) selectShipComputer,
+ /* .battleEndReady = */ (BattleEndReadyFunction) battleEndReadyComputer,
+ /* .deleteContext = */ InputContext_delete,
+};
+
+BattleInputHandlers HumanInputHandlers = {
+ /* .frameInput = */ (BattleFrameInputFunction) frameInputHuman,
+ /* .selectShip = */ (SelectShipFunction) selectShipHuman,
+ /* .battleEndReady = */ (BattleEndReadyFunction) battleEndReadyHuman,
+ /* .deleteContext = */ InputContext_delete,
+};
+
+#ifdef NETPLAY
+BattleInputHandlers NetworkInputHandlers = {
+ /* .frameInput = */ (BattleFrameInputFunction) networkBattleInput,
+ /* .selectShip = */ (SelectShipFunction) selectShipNetwork,
+ /* .battleEndReady = */ (BattleEndReadyFunction) battleEndReadyNetwork,
+ /* .deleteContext = */ InputContext_delete,
+};
+#endif
+
+
+void
+InputContext_init (InputContext *context, BattleInputHandlers *handlers,
+ COUNT playerNr)
+{
+ context->handlers = handlers;
+ context->playerNr = playerNr;
+}
+
+void
+InputContext_delete (InputContext *context)
+{
+ HFree (context);
+}
+
+ComputerInputContext *
+ComputerInputContext_new (COUNT playerNr)
+{
+ ComputerInputContext *result = HMalloc (sizeof (ComputerInputContext));
+ InputContext_init ((InputContext *) result,
+ &ComputerInputHandlers, playerNr);
+ return result;
+}
+
+HumanInputContext *
+HumanInputContext_new (COUNT playerNr)
+{
+ HumanInputContext *result = HMalloc (sizeof (HumanInputContext));
+ InputContext_init ((InputContext *) result,
+ &HumanInputHandlers, playerNr);
+ return result;
+}
+
+#ifdef NETPLAY
+NetworkInputContext *
+NetworkInputContext_new (COUNT playerNr)
+{
+ NetworkInputContext *result = HMalloc (sizeof (NetworkInputContext));
+ InputContext_init ((InputContext *) result,
+ &NetworkInputHandlers, playerNr);
+ return result;
+}
+#endif
+
+
diff --git a/src/uqm/battlecontrols.h b/src/uqm/battlecontrols.h
new file mode 100644
index 0000000..4d424b7
--- /dev/null
+++ b/src/uqm/battlecontrols.h
@@ -0,0 +1,99 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_BATTLECONTROLS_H_
+#define UQM_BATTLECONTROLS_H_
+
+typedef struct BattleInputHandlers BattleInputHandlers;
+typedef struct InputContext InputContext;
+typedef struct ComputerInputContext ComputerInputContext;
+typedef struct HumanInputContext HumanInputContext;
+#ifdef NETPLAY
+typedef struct NetworkInputContext NetworkInputContext;
+#endif /* NETPLAY */
+
+#include "controls.h"
+#include "supermelee/pickmele.h"
+#include "races.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef BATTLE_INPUT_STATE (*BattleFrameInputFunction) (
+ InputContext *context, STARSHIP *StarShipPtr);
+typedef BOOLEAN (*SelectShipFunction) (InputContext *context,
+ GETMELEE_STATE *gms);
+typedef bool (*BattleEndReadyFunction) (InputContext *context);
+typedef void (*DeleteInputContextFunction) (InputContext *context);
+
+
+struct BattleInputHandlers {
+ BattleFrameInputFunction frameInput;
+ SelectShipFunction selectShip;
+ BattleEndReadyFunction battleEndReady;
+ DeleteInputContextFunction deleteContext;
+};
+
+#define INPUT_CONTEXT_COMMON \
+ BattleInputHandlers *handlers; \
+ COUNT playerNr;
+
+// Base "class" for all ...InputContext structures
+struct InputContext {
+ INPUT_CONTEXT_COMMON
+};
+
+struct ComputerInputContext {
+ INPUT_CONTEXT_COMMON
+ // TODO: Put RNG Context used for the AI here.
+};
+
+struct HumanInputContext {
+ INPUT_CONTEXT_COMMON
+};
+
+#ifdef NETPLAY
+struct NetworkInputContext {
+ INPUT_CONTEXT_COMMON
+ // TODO: put NetworkConnection for this player here.
+};
+#endif /* NETPLAY */
+
+ComputerInputContext *ComputerInputContext_new (COUNT playerNr);
+HumanInputContext *HumanInputContext_new (COUNT playerNr);
+#ifdef NETPLAY
+NetworkInputContext *NetworkInputContext_new (COUNT playerNr);
+#endif /* NETPLAY */
+
+extern InputContext *PlayerInput[];
+
+
+BATTLE_INPUT_STATE frameInputHuman (HumanInputContext *context,
+ STARSHIP *StarShipPtr);
+void InputContext_init(InputContext *context, BattleInputHandlers *handlers,
+ COUNT playerNr);
+void InputContext_delete (InputContext *context);
+ // Do not call directly, only from the FreeInputContextFunction.
+ // Call InputContext->handlers->freeContext() to release an
+ // InputContext.
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_BATTLECONTROLS_H_ */
diff --git a/src/uqm/border.c b/src/uqm/border.c
new file mode 100644
index 0000000..193d19a
--- /dev/null
+++ b/src/uqm/border.c
@@ -0,0 +1,200 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include "libs/gfxlib.h"
+#include "libs/threadlib.h"
+#include "colors.h"
+#include "setup.h"
+#include "sis.h"
+#include "units.h"
+#include "util.h"
+
+
+void
+InitSISContexts (void)
+{
+ RECT r;
+
+ SetContext (StatusContext);
+
+ SetContext (SpaceContext);
+ SetContextFGFrame (Screen);
+
+ r.corner.x = SIS_ORG_X;
+ r.corner.y = SIS_ORG_Y;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = SIS_SCREEN_HEIGHT;
+ SetContextClipRect (&r);
+}
+
+void
+DrawSISFrame (void)
+{
+ RECT r;
+
+ SetContext (ScreenContext);
+
+ BatchGraphics ();
+ {
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = SIS_ORG_X + SIS_SCREEN_WIDTH + 1;
+ r.extent.height = SIS_ORG_Y - 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = SIS_ORG_X - 1;
+ r.extent.height = SIS_ORG_Y + SIS_SCREEN_HEIGHT + 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = 0;
+ r.corner.y = r.extent.height;
+ r.extent.width = SIS_ORG_X + SIS_SCREEN_WIDTH + 1;
+ r.extent.height = SCREEN_HEIGHT - SIS_ORG_Y + SIS_SCREEN_HEIGHT;
+ DrawFilledRectangle (&r);
+ r.corner.x = SIS_ORG_X + SIS_SCREEN_WIDTH + 1;
+ r.corner.y = 0;
+ r.extent.width = SCREEN_WIDTH - r.corner.x;
+ r.extent.height = SCREEN_HEIGHT;
+ DrawFilledRectangle (&r);
+
+ r.corner.x = SIS_ORG_X - 1;
+ r.corner.y = SIS_ORG_Y - 1;
+ r.extent.width = SIS_SCREEN_WIDTH + 2;
+ r.extent.height = SIS_SCREEN_HEIGHT + 2;
+ DrawStarConBox (&r, 1,
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19),
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F),
+ TRUE, BLACK_COLOR);
+
+ r.corner.y = 0;
+ r.extent.height = SIS_ORG_Y;
+
+ r.corner.x = SIS_ORG_X;
+ r.extent.width = SIS_MESSAGE_BOX_WIDTH;
+ DrawStarConBox (&r, 1,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0E), 0x54),
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x01, 0x1C), 0x4E),
+ TRUE, BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01));
+
+ r.extent.width = SIS_TITLE_BOX_WIDTH;
+ r.corner.x = SIS_ORG_X + SIS_SCREEN_WIDTH - SIS_TITLE_BOX_WIDTH;
+ DrawStarConBox (&r, 1,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0E), 0x54),
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x01, 0x1C), 0x4E),
+ TRUE, BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01));
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ r.corner.x = SAFE_X + SPACE_WIDTH - 1;
+ r.corner.y = 0;
+ r.extent.width = 1;
+ r.extent.height = SCREEN_HEIGHT;
+ DrawFilledRectangle (&r);
+ r.corner.x = SAFE_X + SPACE_WIDTH;
+ r.corner.y = SAFE_Y + 139;
+ DrawPoint (&r.corner);
+ r.corner.x = SCREEN_WIDTH - 1;
+ DrawPoint (&r.corner);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19));
+ r.corner.y = 1;
+ r.extent.width = 1;
+ r.extent.height = SAFE_Y + SIS_TITLE_HEIGHT;
+ r.corner.x = SIS_ORG_X - 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = SIS_ORG_X + SIS_SCREEN_WIDTH - SIS_TITLE_BOX_WIDTH - 1;
+ DrawFilledRectangle (&r);
+
+ r.corner.x = 0;
+ r.corner.y = SCREEN_HEIGHT - 1;
+ r.extent.width = SAFE_X + SPACE_WIDTH - 1;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = SAFE_X + SPACE_WIDTH - 2;
+ r.corner.y = 0;
+ r.extent.width = 1;
+ r.extent.height = SCREEN_HEIGHT - 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = SCREEN_WIDTH - 1;
+ r.corner.y = 0;
+ r.extent.width = 1;
+ r.extent.height = SAFE_Y + 139;
+ DrawFilledRectangle (&r);
+ r.corner.x = SAFE_X + SPACE_WIDTH;
+ r.corner.y = SCREEN_HEIGHT - 1;
+ r.extent.width = SCREEN_WIDTH - r.corner.x;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = SCREEN_WIDTH - 1;
+ r.corner.y = SAFE_Y + 140;
+ r.extent.width = 1;
+ r.extent.height = (SCREEN_HEIGHT - 1) - r.corner.y;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F));
+ r.corner.y = 1;
+ r.extent.width = 1;
+ r.extent.height = SAFE_Y + SIS_MESSAGE_HEIGHT;
+ r.corner.x = SIS_ORG_X + SIS_MESSAGE_BOX_WIDTH;
+ DrawFilledRectangle (&r);
+ r.corner.x = SIS_ORG_X + SIS_SCREEN_WIDTH;
+ ++r.extent.height;
+ DrawFilledRectangle (&r);
+ r.corner.y = 0;
+ r.extent.width = (SAFE_X + SPACE_WIDTH - 2) - r.corner.x;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = 0;
+ r.extent.width = SIS_ORG_X - r.corner.x;
+ DrawFilledRectangle (&r);
+ r.corner.x = SIS_ORG_X + SIS_MESSAGE_BOX_WIDTH;
+ r.extent.width = SIS_SPACER_BOX_WIDTH;
+ DrawFilledRectangle (&r);
+
+ r.corner.x = 0;
+ r.corner.y = 1;
+ r.extent.width = 1;
+ r.extent.height = (SCREEN_HEIGHT - 1) - r.corner.y;
+ DrawFilledRectangle (&r);
+ r.corner.x = SAFE_X + SPACE_WIDTH;
+ r.corner.y = 0;
+ r.extent.width = 1;
+ r.extent.height = SAFE_Y + 139;
+ DrawFilledRectangle (&r);
+ r.corner.x = SAFE_X + SPACE_WIDTH + 1;
+ r.corner.y = SAFE_Y + 139;
+ r.extent.width = STATUS_WIDTH - 2;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = SAFE_X + SPACE_WIDTH;
+ r.corner.y = SAFE_Y + 140;
+ r.extent.width = 1;
+ r.extent.height = SCREEN_HEIGHT - r.corner.y;
+ DrawFilledRectangle (&r);
+ }
+
+ InitSISContexts ();
+ ClearSISRect (DRAW_SIS_DISPLAY);
+
+ UnbatchGraphics ();
+}
+
diff --git a/src/uqm/build.c b/src/uqm/build.c
new file mode 100644
index 0000000..070453a
--- /dev/null
+++ b/src/uqm/build.c
@@ -0,0 +1,547 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "build.h"
+
+#include "races.h"
+#include "master.h"
+#include "sis.h"
+#include "setup.h"
+#include "libs/compiler.h"
+#include "libs/mathlib.h"
+
+
+// Allocate a new STARSHIP or SHIP_FRAGMENT and put it in the queue
+HLINK
+Build (QUEUE *pQueue, SPECIES_ID SpeciesID)
+{
+ HLINK hNewShip;
+ SHIP_BASE *ShipPtr;
+
+ assert (GetLinkSize (pQueue) == sizeof (STARSHIP) ||
+ GetLinkSize (pQueue) == sizeof (SHIP_FRAGMENT));
+
+ hNewShip = AllocLink (pQueue);
+ if (!hNewShip)
+ return 0;
+
+ ShipPtr = (SHIP_BASE *) LockLink (pQueue, hNewShip);
+ memset (ShipPtr, 0, GetLinkSize (pQueue));
+ ShipPtr->SpeciesID = SpeciesID;
+
+ UnlockLink (pQueue, hNewShip);
+ PutQueue (pQueue, hNewShip);
+
+ return hNewShip;
+}
+
+HLINK
+GetStarShipFromIndex (QUEUE *pShipQ, COUNT Index)
+{
+ HLINK hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (pShipQ);
+ Index > 0 && hStarShip; hStarShip = hNextShip, --Index)
+ {
+ LINK *StarShipPtr;
+
+ StarShipPtr = LockLink (pShipQ, hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockLink (pShipQ, hStarShip);
+ }
+
+ return (hStarShip);
+}
+
+HSHIPFRAG
+GetEscortByStarShipIndex (COUNT index)
+{
+ HSHIPFRAG hStarShip;
+ HSHIPFRAG hNextShip;
+ SHIP_FRAGMENT *StarShipPtr;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q));
+ hStarShip; hStarShip = hNextShip)
+ {
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ if (StarShipPtr->index == index)
+ {
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ break;
+ }
+
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+
+ return hStarShip;
+}
+
+/*
+ * Give the player 'count' ships of the specified race,
+ * limited by the number of free slots.
+ * Returns the number of ships added.
+ */
+COUNT
+AddEscortShips (COUNT race, SIZE count)
+{
+ HFLEETINFO hFleet;
+ BYTE which_window;
+ COUNT i;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race);
+ if (!hFleet)
+ return 0;
+
+ assert (count > 0);
+
+ which_window = 0;
+ for (i = 0; i < (COUNT) count; i++)
+ {
+ HSHIPFRAG hStarShip;
+ HSHIPFRAG hOldShip;
+ SHIP_FRAGMENT *StarShipPtr;
+
+ hStarShip = CloneShipFragment (race, &GLOBAL (built_ship_q), 0);
+ if (!hStarShip)
+ break;
+
+ RemoveQueue (&GLOBAL (built_ship_q), hStarShip);
+
+ /* Find first available escort window */
+ while ((hOldShip = GetStarShipFromIndex (
+ &GLOBAL (built_ship_q), which_window++)))
+ {
+ BYTE win_loc;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hOldShip);
+ win_loc = StarShipPtr->index;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hOldShip);
+ if (which_window <= win_loc)
+ break;
+ }
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ StarShipPtr->index = which_window - 1;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ InsertQueue (&GLOBAL (built_ship_q), hStarShip, hOldShip);
+ }
+
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+ return i;
+}
+
+/*
+ * Returns the total value of all the ships escorting the SIS.
+ */
+COUNT
+CalculateEscortsWorth (void)
+{
+ COUNT ShipCost[] =
+ {
+ RACE_SHIP_COST
+ };
+ COUNT total = 0;
+ HSHIPFRAG hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q));
+ hStarShip; hStarShip = hNextShip)
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ total += ShipCost[StarShipPtr->race_id];
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+ return total;
+}
+
+#if 0
+/*
+ * Returns the size of the fleet of the specified race when the starmap was
+ * last checked. If the race has no SoI, 0 is returned.
+ */
+COUNT
+GetRaceKnownSize (COUNT race)
+{
+ HFLEETINFO hFleet;
+ FLEET_INFO *FleetPtr;
+ COUNT result;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race);
+ if (!hFleet)
+ return 0;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+
+ result = FleetPtr->known_strength;
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ return result;
+}
+#endif
+
+/*
+ * Start or end an alliance with the specified race.
+ * Being in an alliance with a race makes their ships available for building
+ * in the shipyard.
+ * flag == TRUE: start an alliance
+ * flag == TRUE: end an alliance
+ */
+COUNT
+SetRaceAllied (COUNT race, BOOLEAN flag) {
+ HFLEETINFO hFleet;
+ FLEET_INFO *FleetPtr;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race);
+ if (!hFleet)
+ return 0;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+
+ if (FleetPtr->allied_state == DEAD_GUY)
+ {
+ /* Strange request, silently ignore it */
+ }
+ else
+ {
+ FleetPtr->allied_state = (flag ? GOOD_GUY : BAD_GUY);
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ return 1;
+}
+
+/*
+ * Make the sphere of influence for the specified race shown on the starmap
+ * in the future.
+ * The value returned is 'race', unless the type of ship is only available
+ * in SuperMelee, in which case 0 is returned.
+ */
+COUNT
+StartSphereTracking (COUNT race)
+{
+ HFLEETINFO hFleet;
+ FLEET_INFO *FleetPtr;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race);
+ if (!hFleet)
+ return 0;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+
+ if (FleetPtr->actual_strength == 0)
+ {
+ if (FleetPtr->allied_state == DEAD_GUY)
+ {
+ // Race is extinct.
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ return 0;
+ }
+ }
+ else if (FleetPtr->known_strength == 0
+ && FleetPtr->actual_strength != INFINITE_RADIUS)
+ {
+ FleetPtr->known_strength = 1;
+ FleetPtr->known_loc = FleetPtr->loc;
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ return race;
+}
+
+/*
+ * Returns the number of ships of the specified race among the
+ * escort ships.
+ */
+COUNT
+CountEscortShips (COUNT race)
+{
+ HFLEETINFO hFleet;
+ HSHIPFRAG hStarShip, hNextShip;
+ COUNT result = 0;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race);
+ if (!hFleet)
+ return 0;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)); hStarShip;
+ hStarShip = hNextShip)
+ {
+ BYTE ship_type;
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ ship_type = StarShipPtr->race_id;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ if (ship_type == race)
+ result++;
+ }
+ return result;
+}
+
+/*
+ * Returns true if and only if a ship of the specified race is among the
+ * escort ships.
+ */
+BOOLEAN
+HaveEscortShip (COUNT race)
+{
+ return (CountEscortShips (race) > 0);
+}
+
+/*
+ * Test if the SIS can have an escort of the specified race.
+ * Returns 0 if 'race' is not available.
+ * Otherwise, returns the number of ships that can be added.
+ */
+COUNT
+EscortFeasibilityStudy (COUNT race)
+{
+ HFLEETINFO hFleet;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race);
+ if (!hFleet)
+ return 0;
+
+ return (MAX_BUILT_SHIPS - CountLinks (&GLOBAL (built_ship_q)));
+}
+
+/*
+ * Test the alliance status of the specified race.
+ * Either DEAD_GUY (extinct), GOOD_GUY (allied), or BAD_GUY (not allied) is
+ * returned.
+ */
+COUNT
+CheckAlliance (COUNT race)
+{
+ HFLEETINFO hFleet;
+ UWORD flags;
+ FLEET_INFO *FleetPtr;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race);
+ if (!hFleet)
+ return 0;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ flags = FleetPtr->allied_state;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+
+ return flags;
+}
+
+/*
+ * Remove a number of escort ships of the specified race (if present).
+ * Returns the number of escort ships removed.
+ */
+COUNT
+RemoveSomeEscortShips (COUNT race, COUNT count)
+{
+ HSHIPFRAG hStarShip;
+ HSHIPFRAG hNextShip;
+
+ if (count == 0)
+ return 0;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)); hStarShip;
+ hStarShip = hNextShip)
+ {
+ BOOLEAN RemoveShip;
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ RemoveShip = (StarShipPtr->race_id == race);
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ if (RemoveShip)
+ {
+ RemoveQueue (&GLOBAL (built_ship_q), hStarShip);
+ FreeShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ count--;
+ if (count == 0)
+ break;
+ }
+ }
+
+ if (count > 0)
+ {
+ // Update the display.
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+ }
+
+ return count;
+}
+
+/*
+ * Remove all escort ships of the specified race.
+ */
+void
+RemoveEscortShips (COUNT race)
+{
+ RemoveSomeEscortShips (race, (COUNT) -1);
+}
+
+COUNT
+GetIndexFromStarShip (QUEUE *pShipQ, HLINK hStarShip)
+{
+ COUNT Index;
+
+ Index = 0;
+ while (hStarShip != GetHeadLink (pShipQ))
+ {
+ HLINK hNextShip;
+ LINK *StarShipPtr;
+
+ StarShipPtr = LockLink (pShipQ, hStarShip);
+ hNextShip = _GetPredLink (StarShipPtr);
+ UnlockLink (pShipQ, hStarShip);
+
+ hStarShip = hNextShip;
+ ++Index;
+ }
+
+ return Index;
+}
+
+BYTE
+NameCaptain (QUEUE *pQueue, SPECIES_ID SpeciesID)
+{
+ BYTE name_index;
+ HLINK hStarShip;
+
+ assert (GetLinkSize (pQueue) == sizeof (STARSHIP) ||
+ GetLinkSize (pQueue) == sizeof (SHIP_FRAGMENT));
+
+ do
+ {
+ HLINK hNextShip;
+
+ name_index = PickCaptainName ();
+ for (hStarShip = GetHeadLink (pQueue); hStarShip;
+ hStarShip = hNextShip)
+ {
+ SHIP_BASE *ShipPtr;
+ BYTE test_name_index = -1;
+
+ ShipPtr = (SHIP_BASE *) LockLink (pQueue, hStarShip);
+ hNextShip = _GetSuccLink (ShipPtr);
+ if (ShipPtr->SpeciesID == SpeciesID)
+ test_name_index = ShipPtr->captains_name_index;
+ UnlockLink (pQueue, hStarShip);
+
+ if (name_index == test_name_index)
+ break;
+ }
+ } while (hStarShip /* name matched another ship */);
+
+ return name_index;
+}
+
+// crew_level can be set to INFINITE_FLEET for a ship which is to
+// represent an infinite number of ships.
+HSHIPFRAG
+CloneShipFragment (COUNT shipIndex, QUEUE *pDstQueue, COUNT crew_level)
+{
+ HFLEETINFO hFleet;
+ HSHIPFRAG hBuiltShip;
+ FLEET_INFO *TemplatePtr;
+ BYTE captains_name_index;
+
+ assert (GetLinkSize (pDstQueue) == sizeof (SHIP_FRAGMENT));
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), shipIndex);
+ if (!hFleet)
+ return 0;
+
+ TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ if (shipIndex == SAMATRA_SHIP)
+ captains_name_index = 0;
+ else
+ captains_name_index = NameCaptain (pDstQueue,
+ TemplatePtr->SpeciesID);
+ hBuiltShip = Build (pDstQueue, TemplatePtr->SpeciesID);
+ if (hBuiltShip)
+ {
+ SHIP_FRAGMENT *ShipFragPtr;
+
+ ShipFragPtr = LockShipFrag (pDstQueue, hBuiltShip);
+ ShipFragPtr->captains_name_index = captains_name_index;
+ ShipFragPtr->race_strings = TemplatePtr->race_strings;
+ ShipFragPtr->icons = TemplatePtr->icons;
+ ShipFragPtr->melee_icon = TemplatePtr->melee_icon;
+ if (crew_level)
+ ShipFragPtr->crew_level = crew_level;
+ else
+ ShipFragPtr->crew_level = TemplatePtr->crew_level;
+ ShipFragPtr->max_crew = TemplatePtr->max_crew;
+ ShipFragPtr->energy_level = 0;
+ ShipFragPtr->max_energy = TemplatePtr->max_energy;
+ ShipFragPtr->race_id = (BYTE)shipIndex;
+ ShipFragPtr->index = 0;
+ UnlockShipFrag (pDstQueue, hBuiltShip);
+ }
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+
+ return hBuiltShip;
+}
+
+/* Set the crew and captain's name on the first fully-crewed escort
+ * ship of race 'which_ship' */
+int
+SetEscortCrewComplement (COUNT which_ship, COUNT crew_level, BYTE captain)
+{
+ HFLEETINFO hFleet;
+ FLEET_INFO *TemplatePtr;
+ HSHIPFRAG hStarShip, hNextShip;
+ SHIP_FRAGMENT *StarShipPtr = 0;
+ int Index;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), which_ship);
+ if (!hFleet)
+ return -1;
+ TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+
+ /* Find first ship of which_ship race */
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)), Index = 0;
+ hStarShip; hStarShip = hNextShip, ++Index)
+ {
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ if (which_ship == StarShipPtr->race_id &&
+ StarShipPtr->crew_level == TemplatePtr->crew_level)
+ break; /* found one */
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+ if (hStarShip)
+ {
+ StarShipPtr->crew_level = crew_level;
+ StarShipPtr->captains_name_index = captain;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+ else
+ Index = -1;
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ return Index;
+}
diff --git a/src/uqm/build.h b/src/uqm/build.h
new file mode 100644
index 0000000..4bf21cc
--- /dev/null
+++ b/src/uqm/build.h
@@ -0,0 +1,69 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_BUILD_H_
+#define UQM_BUILD_H_
+
+#include "races.h"
+#include "displist.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define NAME_OFFSET 5
+#define NUM_CAPTAINS_NAMES 16
+
+#define PickCaptainName() (((COUNT)TFB_Random () \
+ & (NUM_CAPTAINS_NAMES - 1)) \
+ + NAME_OFFSET)
+
+extern HLINK Build (QUEUE *pQueue, SPECIES_ID SpeciesID);
+extern HSHIPFRAG CloneShipFragment (COUNT shipIndex, QUEUE *pDstQueue,
+ COUNT crew_level);
+extern HLINK GetStarShipFromIndex (QUEUE *pShipQ, COUNT Index);
+extern HSHIPFRAG GetEscortByStarShipIndex (COUNT index);
+extern BYTE NameCaptain (QUEUE *pQueue, SPECIES_ID SpeciesID);
+
+extern COUNT ActivateStarShip (COUNT which_ship, SIZE state);
+extern COUNT GetIndexFromStarShip (QUEUE *pShipQ, HLINK hStarShip);
+extern int SetEscortCrewComplement (COUNT which_ship, COUNT crew_level,
+ BYTE captain);
+
+extern COUNT AddEscortShips (COUNT race, SIZE count);
+extern COUNT CalculateEscortsWorth (void);
+//extern COUNT GetRaceKnownSize (COUNT race);
+extern COUNT SetRaceAllied (COUNT race, BOOLEAN flag);
+extern COUNT StartSphereTracking (COUNT race);
+extern COUNT CountEscortShips (COUNT race);
+extern BOOLEAN HaveEscortShip (COUNT race);
+extern COUNT EscortFeasibilityStudy (COUNT race);
+extern COUNT CheckAlliance (COUNT race);
+extern COUNT RemoveSomeEscortShips (COUNT race, COUNT count);
+extern void RemoveEscortShips (COUNT race);
+
+extern RACE_DESC *load_ship (SPECIES_ID SpeciesID, BOOLEAN LoadBattleData);
+extern void free_ship (RACE_DESC *RaceDescPtr, BOOLEAN FreeIconData,
+ BOOLEAN FreeBattleData);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_BUILD_H_ */
diff --git a/src/uqm/cleanup.c b/src/uqm/cleanup.c
new file mode 100644
index 0000000..a6e4377
--- /dev/null
+++ b/src/uqm/cleanup.c
@@ -0,0 +1,99 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "nameref.h"
+#include "libs/reslib.h"
+#include "gamestr.h"
+#include "init.h"
+#include "element.h"
+#include "hyper.h"
+#include "planets/lander.h"
+#include "starcon.h"
+#include "setup.h"
+#include "planets/solarsys.h"
+#include "sounds.h"
+#include "libs/sndlib.h"
+#include "libs/vidlib.h"
+
+
+void
+FreeKernel (void)
+{
+ UninitPlayerInput ();
+
+ UninitResourceSystem ();
+
+ DestroyDrawable (ReleaseDrawable (Screen));
+ Screen = 0;
+ DestroyContext (ScreenContext);
+ ScreenContext = 0;
+
+ UninitVideoPlayer ();
+ UninitSound ();
+}
+
+static void
+UninitContexts (void)
+{
+ UninitQueue (&disp_q);
+
+ DestroyContext (OffScreenContext);
+ DestroyContext (SpaceContext);
+ DestroyContext (StatusContext);
+}
+
+static void
+UninitKernel (void)
+{
+ UninitSpace ();
+
+ DestroySound (ReleaseSound (MenuSounds));
+ DestroyFont (MicroFont);
+ DestroyStringTable (ReleaseStringTable (GameStrings));
+ DestroyDrawable (ReleaseDrawable (StatusFrame));
+ DestroyDrawable (ReleaseDrawable (ActivityFrame));
+ DestroyFont (TinyFont);
+ DestroyFont (StarConFont);
+
+ UninitQueue (&race_q[0]);
+ UninitQueue (&race_q[1]);
+
+ ActivityFrame = 0;
+}
+
+void
+FreeGameData (void)
+{
+ FreeSC2Data ();
+ FreeLanderData ();
+ FreeIPData ();
+ FreeHyperData ();
+}
+
+void
+UninitGameKernel (void)
+{
+ if (ActivityFrame)
+ {
+ FreeGameData ();
+
+ UninitKernel ();
+ UninitContexts ();
+ }
+}
+
diff --git a/src/uqm/clock.c b/src/uqm/clock.c
new file mode 100644
index 0000000..6660226
--- /dev/null
+++ b/src/uqm/clock.c
@@ -0,0 +1,314 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include "gameev.h"
+#include "globdata.h"
+#include "sis.h"
+ // for DrawStatusMessage()
+#include "setup.h"
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+#include "libs/tasklib.h"
+#include "libs/threadlib.h"
+#include "libs/log.h"
+#include "libs/misc.h"
+
+// the running of the game-clock is based on game framerates
+// *not* on the system (or translated) timer
+// and is hard-coded to the original 24 fps
+#define CLOCK_BASE_FRAMERATE 24
+
+// WARNING: Most of clock functions are only meant to be called by the
+// Starcon2Main thread! If you need access from other threads, examine
+// the locking system!
+// XXX: This mutex is only necessary because debugging functions
+// may access the clock and event data from a different thread
+static Mutex clock_mutex;
+
+static BOOLEAN
+IsLeapYear (COUNT year)
+{
+ // every 4th year but not 100s yet still 400s
+ return (year & 3) == 0 && ((year % 100) != 0 || (year % 400) == 0);
+}
+
+/* month is 1-based: 1=Jan, 2=Feb, etc. */
+static BYTE
+DaysInMonth (COUNT month, COUNT year)
+{
+ static const BYTE days_in_month[12] =
+ {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
+ };
+
+ if (month == 2 && IsLeapYear (year))
+ return 29; /* February, leap year */
+
+ return days_in_month[month - 1];
+}
+
+static void
+nextClockDay (void)
+{
+ ++GLOBAL (GameClock.day_index);
+ if (GLOBAL (GameClock.day_index) > DaysInMonth (
+ GLOBAL (GameClock.month_index),
+ GLOBAL (GameClock.year_index)))
+ {
+ GLOBAL (GameClock.day_index) = 1;
+ ++GLOBAL (GameClock.month_index);
+ if (GLOBAL (GameClock.month_index) > 12)
+ {
+ GLOBAL (GameClock.month_index) = 1;
+ ++GLOBAL (GameClock.year_index);
+ }
+ }
+
+ // update the date on screen
+ DrawStatusMessage (NULL);
+}
+
+static void
+processClockDayEvents (void)
+{
+ HEVENT hEvent;
+
+ while ((hEvent = GetHeadEvent ()))
+ {
+ EVENT *EventPtr;
+
+ LockEvent (hEvent, &EventPtr);
+
+ if (GLOBAL (GameClock.day_index) != EventPtr->day_index
+ || GLOBAL (GameClock.month_index) != EventPtr->month_index
+ || GLOBAL (GameClock.year_index) != EventPtr->year_index)
+ {
+ UnlockEvent (hEvent);
+ break;
+ }
+ RemoveEvent (hEvent);
+ EventHandler (EventPtr->func_index);
+
+ UnlockEvent (hEvent);
+ FreeEvent (hEvent);
+ }
+}
+
+BOOLEAN
+InitGameClock (void)
+{
+ if (!InitQueue (&GLOBAL (GameClock.event_q), NUM_EVENTS, sizeof (EVENT)))
+ return (FALSE);
+ clock_mutex = CreateMutex ("Clock Mutex", SYNC_CLASS_TOPLEVEL);
+ GLOBAL (GameClock.month_index) = 2;
+ GLOBAL (GameClock.day_index) = 17;
+ GLOBAL (GameClock.year_index) = START_YEAR; /* Feb 17, START_YEAR */
+ GLOBAL (GameClock.tick_count) = 0;
+ GLOBAL (GameClock.day_in_ticks) = 0;
+
+ return (TRUE);
+}
+
+BOOLEAN
+UninitGameClock (void)
+{
+ DestroyMutex (clock_mutex);
+ clock_mutex = NULL;
+
+ UninitQueue (&GLOBAL (GameClock.event_q));
+
+ return (TRUE);
+}
+
+// For debugging use only
+void
+LockGameClock (void)
+{
+ // Block the GameClockTick() for executing
+ if (clock_mutex)
+ LockMutex (clock_mutex);
+}
+
+// For debugging use only
+void
+UnlockGameClock (void)
+{
+ if (clock_mutex)
+ UnlockMutex (clock_mutex);
+}
+
+// For debugging use only
+BOOLEAN
+GameClockRunning (void)
+{
+ SIZE day_in_ticks;
+
+ if (!clock_mutex)
+ return FALSE;
+
+ LockMutex (clock_mutex);
+ day_in_ticks = GLOBAL (GameClock.day_in_ticks);
+ UnlockMutex (clock_mutex);
+
+ return day_in_ticks != 0;
+}
+
+void
+SetGameClockRate (COUNT seconds_per_day)
+{
+ SIZE new_day_in_ticks, new_tick_count;
+
+ new_day_in_ticks = (SIZE)(seconds_per_day * CLOCK_BASE_FRAMERATE);
+ if (GLOBAL (GameClock.day_in_ticks) == 0)
+ new_tick_count = new_day_in_ticks;
+ else if (GLOBAL (GameClock.tick_count) <= 0)
+ new_tick_count = 0;
+ else if ((new_tick_count = (SIZE)((DWORD)GLOBAL (GameClock.tick_count)
+ * new_day_in_ticks / GLOBAL (GameClock.day_in_ticks))) == 0)
+ new_tick_count = 1;
+ GLOBAL (GameClock.day_in_ticks) = new_day_in_ticks;
+ GLOBAL (GameClock.tick_count) = new_tick_count;
+}
+
+BOOLEAN
+ValidateEvent (EVENT_TYPE type, COUNT *pmonth_index, COUNT *pday_index,
+ COUNT *pyear_index)
+{
+ COUNT month_index, day_index, year_index;
+
+ month_index = *pmonth_index;
+ day_index = *pday_index;
+ year_index = *pyear_index;
+ if (type == RELATIVE_EVENT)
+ {
+ month_index += GLOBAL (GameClock.month_index) - 1;
+ year_index += GLOBAL (GameClock.year_index) + (month_index / 12);
+ month_index = (month_index % 12) + 1;
+
+ day_index += GLOBAL (GameClock.day_index);
+ while (day_index > DaysInMonth (month_index, year_index))
+ {
+ day_index -= DaysInMonth (month_index, year_index);
+ if (++month_index > 12)
+ {
+ month_index = 1;
+ ++year_index;
+ }
+ }
+
+ *pmonth_index = month_index;
+ *pday_index = day_index;
+ *pyear_index = year_index;
+ }
+
+ // translation: return (BOOLEAN) !(date < GLOBAL (Gameclock.date));
+ return (BOOLEAN) (!(year_index < GLOBAL (GameClock.year_index)
+ || (year_index == GLOBAL (GameClock.year_index)
+ && (month_index < GLOBAL (GameClock.month_index)
+ || (month_index == GLOBAL (GameClock.month_index)
+ && day_index < GLOBAL (GameClock.day_index))))));
+}
+
+HEVENT
+AddEvent (EVENT_TYPE type, COUNT month_index, COUNT day_index, COUNT
+ year_index, BYTE func_index)
+{
+ HEVENT hNewEvent;
+
+ if (type == RELATIVE_EVENT
+ && month_index == 0
+ && day_index == 0
+ && year_index == 0)
+ EventHandler (func_index);
+ else if (ValidateEvent (type, &month_index, &day_index, &year_index)
+ && (hNewEvent = AllocEvent ()))
+ {
+ EVENT *EventPtr;
+
+ LockEvent (hNewEvent, &EventPtr);
+ EventPtr->day_index = (BYTE)day_index;
+ EventPtr->month_index = (BYTE)month_index;
+ EventPtr->year_index = year_index;
+ EventPtr->func_index = func_index;
+ UnlockEvent (hNewEvent);
+
+ {
+ HEVENT hEvent, hSuccEvent;
+ for (hEvent = GetHeadEvent (); hEvent != 0; hEvent = hSuccEvent)
+ {
+ LockEvent (hEvent, &EventPtr);
+ if (year_index < EventPtr->year_index
+ || (year_index == EventPtr->year_index
+ && (month_index < EventPtr->month_index
+ || (month_index == EventPtr->month_index
+ && day_index < EventPtr->day_index))))
+ {
+ UnlockEvent (hEvent);
+ break;
+ }
+
+ hSuccEvent = GetSuccEvent (EventPtr);
+ UnlockEvent (hEvent);
+ }
+
+ InsertEvent (hNewEvent, hEvent);
+ }
+
+ return (hNewEvent);
+ }
+
+ return (0);
+}
+
+void
+GameClockTick (void)
+{
+ // XXX: This mutex is only necessary because debugging functions
+ // may access the clock and event data from a different thread
+ LockMutex (clock_mutex);
+
+ --GLOBAL (GameClock.tick_count);
+ if (GLOBAL (GameClock.tick_count) <= 0)
+ { // next day -- move the calendar
+ GLOBAL (GameClock.tick_count) = GLOBAL (GameClock.day_in_ticks);
+ // Do not do anything until the clock is inited
+ if (GLOBAL (GameClock.day_in_ticks) > 0)
+ {
+ nextClockDay ();
+ processClockDayEvents ();
+ }
+ }
+
+ UnlockMutex (clock_mutex);
+}
+
+void
+MoveGameClockDays (COUNT days)
+{
+ // XXX: This should theoretically hold the clock_mutex, but if
+ // someone manages to hit the debug button while this function
+ // runs, it's their own fault :-P
+
+ for ( ; days > 0; --days)
+ {
+ nextClockDay ();
+ processClockDayEvents ();
+ }
+ GLOBAL (GameClock.tick_count) = GLOBAL (GameClock.day_in_ticks);
+}
diff --git a/src/uqm/clock.h b/src/uqm/clock.h
new file mode 100644
index 0000000..5bd428c
--- /dev/null
+++ b/src/uqm/clock.h
@@ -0,0 +1,111 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_CLOCK_H_
+#define UQM_CLOCK_H_
+
+#include "libs/tasklib.h"
+#include "displist.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+#define START_YEAR 2155
+
+#define UPDATE_DAY (1 << 0)
+#define UPDATE_MONTH (1 << 1)
+#define UPDATE_YEAR (1 << 2)
+
+typedef struct
+{
+ BYTE day_index, month_index;
+ COUNT year_index;
+ SIZE tick_count, day_in_ticks;
+
+ QUEUE event_q;
+ /* Queue element is EVENT */
+} CLOCK_STATE;
+
+typedef HLINK HEVENT;
+
+typedef struct event
+{
+ // LINK elements; must be first
+ HEVENT pred, succ;
+
+ BYTE day_index, month_index;
+ COUNT year_index;
+ BYTE func_index;
+} EVENT;
+
+typedef enum
+{
+ ABSOLUTE_EVENT = 0,
+ RELATIVE_EVENT
+} EVENT_TYPE;
+
+#define AllocEvent() AllocLink (&GLOBAL (GameClock.event_q))
+#define PutEvent(h) PutQueue (&GLOBAL (GameClock.event_q), (h))
+#define InsertEvent(h,i) InsertQueue (&GLOBAL (GameClock.event_q), (h), (i))
+#define GetHeadEvent() GetHeadLink (&GLOBAL (GameClock.event_q))
+#define GetTailEvent() GetTailLink (&GLOBAL (GameClock.event_q))
+#define LockEvent(h,ppe) (*(ppe) = (EVENT*)LockLink (&GLOBAL (GameClock.event_q), h))
+#define UnlockEvent(h) UnlockLink (&GLOBAL (GameClock.event_q), (h))
+#define RemoveEvent(h) RemoveQueue (&GLOBAL (GameClock.event_q), (h))
+#define FreeEvent(h) FreeLink (&GLOBAL (GameClock.event_q), (h))
+#define GetPredEvent(l) _GetPredLink (l)
+#define GetSuccEvent(l) _GetSuccLink (l)
+#define ForAllEvents(callback, arg) ForAllLinks(&GLOBAL (GameClock.event_q), \
+ (void (*)(LINK *, void *)) (callback), (arg))
+
+// Rates are in seconds per game day
+#define HYPERSPACE_CLOCK_RATE 5
+// XXX: the IP rate is based on 24 ticks/second (see SetGameClockRate),
+// however, IP runs at 30 fps right now. So in reality, the IP clock
+// rate is closer to 23 seconds per game day. The clock is faster, but
+// the flagship also moves faster.
+#define INTERPLANETARY_CLOCK_RATE 30
+
+extern BOOLEAN InitGameClock (void);
+extern BOOLEAN UninitGameClock (void);
+
+extern void SetGameClockRate (COUNT seconds_per_day);
+extern BOOLEAN ValidateEvent (EVENT_TYPE type, COUNT *pmonth_index,
+ COUNT *pday_index, COUNT *pyear_index);
+extern HEVENT AddEvent (EVENT_TYPE type, COUNT month_index, COUNT
+ day_index, COUNT year_index, BYTE func_index);
+extern void EventHandler (BYTE selector);
+extern void GameClockTick (void);
+extern void MoveGameClockDays (COUNT days);
+
+// The lock/unlock/running functions are for debugging use only
+// Locking will block the GameClockTick() function and thus
+// the thread moving the clock.
+extern void LockGameClock (void);
+extern void UnlockGameClock (void);
+// A weak indicator of the clock moving. Suitable for debugging,
+// but not much else
+extern BOOLEAN GameClockRunning (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_CLOCK_H_ */
diff --git a/src/uqm/cnctdlg.c b/src/uqm/cnctdlg.c
new file mode 100644
index 0000000..47824eb
--- /dev/null
+++ b/src/uqm/cnctdlg.c
@@ -0,0 +1,630 @@
+//Copyright Michael Martin, 2006
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef NETPLAY
+
+#include "cnctdlg.h"
+#include "controls.h"
+#include "colors.h"
+#include "gamestr.h"
+#include "setup.h"
+#include "units.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "libs/graphics/widgets.h"
+#include "supermelee/netplay/netoptions.h"
+
+#define MCD_WIDTH 260
+#define MCD_HEIGHT 110
+
+#define MENU_FRAME_RATE (ONE_SECOND / 20)
+
+typedef struct connect_dialog_state
+{
+ BOOLEAN (*InputFunc) (struct connect_dialog_state *pInputState);
+
+ DWORD NextTime;
+ BOOLEAN Initialized;
+ int which_side;
+
+ int confirmed;
+} CONNECT_DIALOG_STATE;
+
+static void DrawConnectDialog (void);
+
+static WIDGET_MENU_SCREEN menu;
+static WIDGET_BUTTON buttons[3];
+static WIDGET_SLIDER slider;
+static WIDGET_TEXTENTRY texts[2];
+
+static WIDGET *menu_widgets[] = {
+ (WIDGET *)&buttons[1],
+ (WIDGET *)&texts[0],
+ (WIDGET *)&buttons[0],
+ (WIDGET *)&slider,
+ (WIDGET *)&texts[1],
+ (WIDGET *)&buttons[2] };
+
+static BOOLEAN done;
+
+/* This kind of sucks, but the Button callbacks need access to the
+ * CONNECT_DIALOG_STATE, so we need a pointer to it */
+
+static CONNECT_DIALOG_STATE *current_state;
+
+static FONT PlayerFont;
+
+static int do_connect (WIDGET *self, int event);
+static int do_listen (WIDGET *self, int event);
+static int do_cancel (WIDGET *self, int event);
+
+static void
+MCD_DrawMenuScreen (WIDGET *_self, int x, int y)
+{
+ int widget_index, widget_y;
+
+ WIDGET_MENU_SCREEN *self = (WIDGET_MENU_SCREEN *)_self;
+
+ widget_y = y + 8;
+ for (widget_index = 0; widget_index < self->num_children; widget_index++)
+ {
+ WIDGET *c = self->child[widget_index];
+ (*c->draw)(c, x, widget_y);
+ widget_y += (*c->height)(c) + 8;
+ }
+}
+
+static void
+MCD_DrawButton (WIDGET *_self, int x, int y)
+{
+ WIDGET_BUTTON *self = (WIDGET_BUTTON *)_self;
+ Color oldtext;
+ Color inactive, selected;
+ FONT oldfont = SetContextFont (StarConFont);
+ FRAME oldFontEffect = SetContextFontEffect (NULL);
+ TEXT t;
+
+ selected = MENU_HIGHLIGHT_COLOR;
+ inactive = MENU_TEXT_COLOR;
+
+ t.baseline.x = 160;
+ t.baseline.y = y;
+ t.align = ALIGN_CENTER;
+ t.CharCount = ~0;
+ t.pStr = self->name;
+ if (widget_focus == _self)
+ {
+ oldtext = SetContextForeGroundColor (selected);
+ }
+ else
+ {
+ oldtext = SetContextForeGroundColor (inactive);
+ }
+ font_DrawText (&t);
+ SetContextFontEffect (oldFontEffect);
+ SetContextFont (oldfont);
+ SetContextForeGroundColor (oldtext);
+ (void) x;
+}
+
+static void
+MCD_DrawSlider (WIDGET *_self, int x, int y)
+{
+ WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self;
+ Color oldtext;
+ Color default_color, selected;
+ FONT oldfont = SetContextFont (PlayerFont);
+ FRAME oldFontEffect = SetContextFontEffect (NULL);
+ TEXT t;
+ RECT r;
+ int tick = (MCD_WIDTH) / 8;
+
+ default_color = MENU_TEXT_COLOR;
+ selected = MENU_HIGHLIGHT_COLOR;
+
+ t.baseline.x = x;
+ t.baseline.y = y;
+ t.align = ALIGN_LEFT;
+ t.CharCount = ~0;
+ t.pStr = self->category;
+ if (widget_focus == _self)
+ {
+ oldtext = SetContextForeGroundColor (selected);
+ }
+ else
+ {
+ oldtext = SetContextForeGroundColor (default_color);
+ }
+ font_DrawText (&t);
+
+ r.corner.x = t.baseline.x + 3 * tick;
+ r.corner.y = t.baseline.y - 4;
+ r.extent.height = 2;
+ r.extent.width = 3 * tick;
+ DrawFilledRectangle (&r);
+
+ r.extent.width = 3;
+ r.extent.height = 8;
+ r.corner.y = t.baseline.y - 7;
+ r.corner.x = t.baseline.x + 3 * tick + (3 * tick *
+ (self->value - self->min) / (self->max - self->min)) - 1;
+ DrawFilledRectangle (&r);
+
+ (*self->draw_value)(self, t.baseline.x + 7 * tick, t.baseline.y);
+
+ SetContextFontEffect (oldFontEffect);
+ SetContextFont (oldfont);
+ SetContextForeGroundColor (oldtext);
+}
+
+static void
+MCD_DrawTextEntry (WIDGET *_self, int x, int y)
+{
+ WIDGET_TEXTENTRY *self = (WIDGET_TEXTENTRY *)_self;
+ Color oldtext;
+ Color inactive, default_color, selected;
+ FONT oldfont = SetContextFont (PlayerFont);
+ FRAME oldFontEffect = SetContextFontEffect (NULL);
+ TEXT t;
+
+ default_color = MENU_TEXT_COLOR;
+ selected = MENU_HIGHLIGHT_COLOR;
+ inactive = MENU_TEXT_COLOR;
+
+ BatchGraphics ();
+
+ t.baseline.x = x;
+ t.baseline.y = y;
+ t.align = ALIGN_LEFT;
+ t.CharCount = ~0;
+ t.pStr = self->category;
+ if (widget_focus == _self)
+ {
+ oldtext = SetContextForeGroundColor (selected);
+ }
+ else
+ {
+ oldtext = SetContextForeGroundColor (default_color);
+ }
+ font_DrawText (&t);
+
+ /* Force string termination */
+ self->value[WIDGET_TEXTENTRY_WIDTH-1] = 0;
+
+ t.baseline.y = y;
+ t.CharCount = utf8StringCount (self->value);
+ t.pStr = self->value;
+
+ if (!(self->state & WTE_EDITING))
+ { // normal or selected state
+ t.baseline.x = 160;
+ t.align = ALIGN_CENTER;
+
+ if (widget_focus == _self)
+ {
+ oldtext = SetContextForeGroundColor (selected);
+ }
+ else
+ {
+ oldtext = SetContextForeGroundColor (inactive);
+ }
+ font_DrawText (&t);
+ }
+ else
+ { // editing state
+ COUNT i;
+ RECT text_r;
+ BYTE char_deltas[WIDGET_TEXTENTRY_WIDTH];
+ BYTE *pchar_deltas;
+ RECT r;
+ SIZE leading;
+
+ t.baseline.x = x + 90;
+ t.align = ALIGN_LEFT;
+
+ // calc background box dimensions
+ // XXX: this may need some tuning, especially if a
+ // different font is used. The font 'leading' values
+ // are not what they should be.
+#define BOX_VERT_OFFSET 2
+ GetContextFontLeading (&leading);
+ r.corner.x = t.baseline.x - 1;
+ r.corner.y = t.baseline.y - leading + BOX_VERT_OFFSET;
+ r.extent.width = MCD_WIDTH - r.corner.x - 10;
+ r.extent.height = leading + 2;
+
+ TextRect (&t, &text_r, char_deltas);
+#if 0
+ // XXX: this should potentially be used in ChangeCallback
+ if ((text_r.extent.width + 2) >= r.extent.width)
+ { // the text does not fit the input box size and so
+ // will not fit when displayed later
+ UnbatchGraphics ();
+ // disallow the change
+ return (FALSE);
+ }
+#endif
+
+ oldtext = SetContextForeGroundColor (selected);
+ DrawFilledRectangle (&r);
+
+ // calculate the cursor position and draw it
+ pchar_deltas = char_deltas;
+ for (i = self->cursor_pos; i > 0; --i)
+ r.corner.x += (SIZE)*pchar_deltas++;
+ if (self->cursor_pos < t.CharCount) /* cursor mid-line */
+ --r.corner.x;
+ if (self->state & WTE_BLOCKCUR)
+ { // Use block cursor for keyboardless systems
+ if (self->cursor_pos == t.CharCount)
+ { // cursor at end-line -- use insertion point
+ r.extent.width = 1;
+ }
+ else if (self->cursor_pos + 1 == t.CharCount)
+ { // extra pixel for last char margin
+ r.extent.width = (SIZE)*pchar_deltas + 2;
+ }
+ else
+ { // normal mid-line char
+ r.extent.width = (SIZE)*pchar_deltas + 1;
+ }
+ }
+ else
+ { // Insertion point cursor
+ r.extent.width = 1;
+ }
+ // position cursor within input field rect
+ ++r.corner.x;
+ ++r.corner.y;
+ r.extent.height -= 2;
+ SetContextForeGroundColor (MENU_CURSOR_COLOR);
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (inactive);
+ font_DrawText (&t);
+ }
+
+ UnbatchGraphics ();
+ SetContextFontEffect (oldFontEffect);
+ SetContextFont (oldfont);
+ SetContextForeGroundColor (oldtext);
+}
+
+/* Text entry stuff, mostly C&Ped from setupmenu.c. Could use some
+ * refactoring, as redraw_menu () is the only real change. */
+
+static BOOLEAN
+OnTextEntryChange (TEXTENTRY_STATE *pTES)
+{
+ WIDGET_TEXTENTRY *widget = (WIDGET_TEXTENTRY *) pTES->CbParam;
+
+ widget->cursor_pos = pTES->CursorPos;
+ if (pTES->JoystickMode)
+ widget->state |= WTE_BLOCKCUR;
+ else
+ widget->state &= ~WTE_BLOCKCUR;
+
+ // XXX TODO: Here, we can examine the text entered so far
+ // to make sure it fits on the screen, for example,
+ // and return FALSE to disallow the last change
+
+ return TRUE; // allow change
+}
+
+static BOOLEAN
+OnTextEntryFrame (TEXTENTRY_STATE *pTES)
+{
+ DrawConnectDialog ();
+
+ SleepThreadUntil (pTES->NextTime);
+ pTES->NextTime = GetTimeCounter () + MENU_FRAME_RATE;
+
+ (void) pTES; // satisfying compiler
+ return TRUE; // continue
+}
+
+static int
+OnTextEntryEvent (WIDGET_TEXTENTRY *widget)
+{ // Going to edit the text
+ TEXTENTRY_STATE tes;
+ UNICODE revert_buf[256];
+
+ // position cursor at the end of text
+ widget->cursor_pos = utf8StringCount (widget->value);
+ widget->state = WTE_EDITING;
+ DrawConnectDialog ();
+
+ // make a backup copy for revert on cancel
+ utf8StringCopy (revert_buf, sizeof (revert_buf), widget->value);
+
+ // text entry setup
+ tes.Initialized = FALSE;
+ tes.NextTime = GetTimeCounter () + MENU_FRAME_RATE;
+ tes.BaseStr = widget->value;
+ tes.MaxSize = widget->maxlen;
+ tes.CursorPos = widget->cursor_pos;
+ tes.CbParam = widget;
+ tes.ChangeCallback = OnTextEntryChange;
+ tes.FrameCallback = OnTextEntryFrame;
+
+ // SetMenuSounds (0, MENU_SOUND_SELECT);
+ if (!DoTextEntry (&tes))
+ { // editing failed (canceled) -- revert the changes
+ utf8StringCopy (widget->value, widget->maxlen, revert_buf);
+ }
+ else
+ {
+ if (widget->onChange)
+ {
+ (*(widget->onChange))(widget);
+ }
+ }
+
+ widget->state = WTE_NORMAL;
+ DrawConnectDialog ();
+
+ return TRUE; // event handled
+}
+
+/* Button response routines */
+
+static int
+do_connect (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ /* These assignments are safe exactly because texts[] is file-scope) */
+ netplayOptions.peer[current_state->which_side].host = texts[0].value;
+ netplayOptions.peer[current_state->which_side].port = texts[1].value;
+ netplayOptions.peer[current_state->which_side].isServer = FALSE;
+ current_state->confirmed = TRUE;
+ netplayOptions.inputDelay = slider.value;
+
+ done = TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+do_listen (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ /* These assignments are safe exactly because texts[] is file-scope) */
+ netplayOptions.peer[current_state->which_side].port = texts[1].value;
+ netplayOptions.peer[current_state->which_side].isServer = TRUE;
+ netplayOptions.inputDelay = slider.value;
+ current_state->confirmed = TRUE;
+ done = TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+do_cancel (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ current_state->confirmed = FALSE;
+ done = TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+
+static void
+CreateWidgets (void)
+{
+ int i;
+
+ done = false;
+
+ for (i = 0; i < 3; i++)
+ {
+ buttons[i].tag = WIDGET_TYPE_BUTTON;
+ buttons[i].parent = NULL;
+ buttons[i].receiveFocus = Widget_ReceiveFocusSimple;
+ buttons[i].draw = MCD_DrawButton;
+ buttons[i].height = Widget_HeightOneLine;
+ buttons[i].width = Widget_WidthFullScreen;
+ }
+ buttons[0].name = GAME_STRING (NETMELEE_STRING_BASE + 19);
+ // "Connect to remote host"
+ buttons[1].name = GAME_STRING (NETMELEE_STRING_BASE + 20);
+ // "Wait for incoming connection"
+ buttons[2].name = GAME_STRING (NETMELEE_STRING_BASE + 21);
+ // "Cancel"
+
+ buttons[0].handleEvent = do_connect;
+ buttons[1].handleEvent = do_listen;
+ buttons[2].handleEvent = do_cancel;
+
+ menu.tag = WIDGET_TYPE_MENU_SCREEN;
+ menu.parent = NULL;
+ menu.receiveFocus = Widget_ReceiveFocusMenuScreen;
+ menu.draw = MCD_DrawMenuScreen;
+ menu.height = Widget_HeightFullScreen;
+ menu.width = Widget_WidthFullScreen;
+ menu.num_children = 6;
+ menu.child = menu_widgets;
+ menu.handleEvent = Widget_HandleEventMenuScreen;
+
+ slider.tag = WIDGET_TYPE_SLIDER;
+ slider.parent = NULL;
+ slider.handleEvent = Widget_HandleEventSlider;
+ slider.receiveFocus = Widget_ReceiveFocusSimple;
+ slider.draw = MCD_DrawSlider;
+ slider.height = Widget_HeightOneLine;
+ slider.width = Widget_WidthFullScreen;
+ slider.draw_value = Widget_Slider_DrawValue;
+ slider.min = 0;
+ slider.max = 9;
+ slider.step = 1;
+ slider.value = netplayOptions.inputDelay;
+ slider.category = GAME_STRING (NETMELEE_STRING_BASE + 24);
+ // "Net Delay"
+
+ for (i = 0; i < 2; i++)
+ {
+ texts[i].tag = WIDGET_TYPE_TEXTENTRY;
+ texts[i].parent = NULL;
+ texts[i].handleEvent = Widget_HandleEventTextEntry;
+ texts[i].receiveFocus = Widget_ReceiveFocusSimple;
+ texts[i].draw = MCD_DrawTextEntry;
+ texts[i].height = Widget_HeightOneLine;
+ texts[i].width = Widget_WidthFullScreen;
+ texts[i].handleEventSelect = OnTextEntryEvent;
+ texts[i].maxlen = WIDGET_TEXTENTRY_WIDTH-1;
+ texts[i].state = WTE_NORMAL;
+ texts[i].cursor_pos = 0;
+ }
+
+ texts[0].category = GAME_STRING (NETMELEE_STRING_BASE + 22);
+ // "Host"
+ texts[1].category = GAME_STRING (NETMELEE_STRING_BASE + 23);
+ // "Port"
+
+ /* We sometimes assign to these internals; cannot strncpy over self! */
+ if (texts[0].value != netplayOptions.peer[current_state->which_side].host)
+ {
+ strncpy (texts[0].value,
+ netplayOptions.peer[current_state->which_side].host,
+ texts[0].maxlen);
+ }
+ if (texts[1].value != netplayOptions.peer[current_state->which_side].port)
+ {
+ strncpy (texts[1].value,
+ netplayOptions.peer[current_state->which_side].port,
+ texts[1].maxlen);
+ }
+ texts[0].value[texts[0].maxlen]=0;
+ texts[1].value[texts[1].maxlen]=0;
+
+ menu.receiveFocus ((WIDGET *)&menu, WIDGET_EVENT_DOWN);
+}
+
+static void
+DrawConnectDialog (void)
+{
+ RECT r;
+
+ r.extent.width = MCD_WIDTH;
+ r.extent.height = MCD_HEIGHT;
+ r.corner.x = (SCREEN_WIDTH - r.extent.width) >> 1;
+ r.corner.y = (SCREEN_HEIGHT - r.extent.height) >> 1;
+
+
+ DrawShadowedBox (&r, SHADOWBOX_BACKGROUND_COLOR,
+ SHADOWBOX_DARK_COLOR, SHADOWBOX_MEDIUM_COLOR);
+
+ menu.draw ((WIDGET *)&menu, r.corner.x + 10, r.corner.y + 10);
+
+}
+
+static BOOLEAN
+DoMeleeConnectDialog (CONNECT_DIALOG_STATE *state)
+{
+ BOOLEAN changed;
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (!state->Initialized)
+ {
+ state->Initialized = TRUE;
+ SetDefaultMenuRepeatDelay ();
+ state->NextTime = GetTimeCounter ();
+ /* Prepare widgets, draw stuff, etc. */
+ CreateWidgets ();
+ DrawConnectDialog ();
+ }
+
+ changed = TRUE;
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ Widget_Event (WIDGET_EVENT_UP);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ Widget_Event (WIDGET_EVENT_DOWN);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ Widget_Event (WIDGET_EVENT_LEFT);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ Widget_Event (WIDGET_EVENT_RIGHT);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ Widget_Event (WIDGET_EVENT_SELECT);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ Widget_Event (WIDGET_EVENT_CANCEL);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DELETE])
+ {
+ Widget_Event (WIDGET_EVENT_DELETE);
+ }
+ else
+ {
+ changed = FALSE;
+ }
+
+ if (changed)
+ {
+ DrawConnectDialog ();
+ }
+
+ SleepThreadUntil (state->NextTime + MENU_FRAME_RATE);
+ state->NextTime = GetTimeCounter ();
+ return !((GLOBAL (CurrentActivity) & CHECK_ABORT) ||
+ done);
+}
+
+BOOLEAN
+MeleeConnectDialog (int side)
+{
+ CONNECT_DIALOG_STATE state;
+
+ PlayerFont = LoadFont (PLAYER_FONT);
+
+ state.Initialized = FALSE;
+ state.which_side = side;
+ state.InputFunc = DoMeleeConnectDialog;
+ state.confirmed = TRUE;
+
+ current_state = &state;
+
+ DoInput (&state, TRUE);
+
+ current_state = NULL;
+
+ DestroyFont (PlayerFont);
+
+ return state.confirmed;
+}
+
+#endif /* NETPLAY */
+
diff --git a/src/uqm/cnctdlg.h b/src/uqm/cnctdlg.h
new file mode 100644
index 0000000..c785e1c
--- /dev/null
+++ b/src/uqm/cnctdlg.h
@@ -0,0 +1,38 @@
+//Copyright Michael Martin, 2006
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef NETPLAY
+
+#ifndef UQM_CNCTDLG_H_
+#define UQM_CNCTDLG_H_
+
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+BOOLEAN MeleeConnectDialog (int side);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_CNCTDLG_H_ */
+
+#endif /* NETPLAY */
diff --git a/src/uqm/coderes.h b/src/uqm/coderes.h
new file mode 100644
index 0000000..add0970
--- /dev/null
+++ b/src/uqm/coderes.h
@@ -0,0 +1,43 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_CODERES_H_
+#define UQM_CODERES_H_
+
+#include "libs/reslib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern BOOLEAN InstallCodeResType (void);
+extern void *LoadCodeResInstance (RESOURCE res);
+extern void *CaptureCodeRes (void *hCode, void *pData, void **ppLocData);
+extern void *ReleaseCodeRes (void *CodeRef);
+extern BOOLEAN DestroyCodeRes (void *hCode);
+
+typedef struct
+{
+ UWORD size;
+} CODE_REF;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_CODERES_H_ */
diff --git a/src/uqm/collide.c b/src/uqm/collide.c
new file mode 100644
index 0000000..7275e42
--- /dev/null
+++ b/src/uqm/collide.c
@@ -0,0 +1,183 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "collide.h"
+#include "element.h"
+#include "races.h"
+#include "units.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+
+
+//#define DEBUG_COLLIDE
+
+void
+collide (ELEMENT *ElementPtr0, ELEMENT *ElementPtr1)
+{
+ SIZE speed;
+ SIZE dx0, dy0, dx1, dy1, dx_rel, dy_rel;
+ SIZE TravelAngle0, TravelAngle1, ImpactAngle0, ImpactAngle1;
+ SIZE RelTravelAngle, Directness;
+
+ dx_rel = ElementPtr0->next.location.x
+ - ElementPtr1->next.location.x;
+ dy_rel = ElementPtr0->next.location.y
+ - ElementPtr1->next.location.y;
+ ImpactAngle0 = ARCTAN (dx_rel, dy_rel);
+ ImpactAngle1 = NORMALIZE_ANGLE (ImpactAngle0 + HALF_CIRCLE);
+
+ GetCurrentVelocityComponents (&ElementPtr0->velocity, &dx0, &dy0);
+ TravelAngle0 = GetVelocityTravelAngle (&ElementPtr0->velocity);
+ GetCurrentVelocityComponents (&ElementPtr1->velocity, &dx1, &dy1);
+ TravelAngle1 = GetVelocityTravelAngle (&ElementPtr1->velocity);
+ dx_rel = dx0 - dx1;
+ dy_rel = dy0 - dy1;
+ RelTravelAngle = ARCTAN (dx_rel, dy_rel);
+ speed = square_root ((long)dx_rel * dx_rel + (long)dy_rel * dy_rel);
+
+ Directness = NORMALIZE_ANGLE (RelTravelAngle - ImpactAngle0);
+ if (Directness <= QUADRANT || Directness >= HALF_CIRCLE + QUADRANT)
+ /* shapes just scraped each other but still collided,
+ * they will collide again unless we fudge it.
+ */
+ {
+ Directness = HALF_CIRCLE;
+ ImpactAngle0 = TravelAngle0 + HALF_CIRCLE;
+ ImpactAngle1 = TravelAngle1 + HALF_CIRCLE;
+ }
+
+#ifdef DEBUG_COLLIDE
+ log_add (log_Debug, "Centers: <%d, %d> <%d, %d>",
+ ElementPtr0->next.location.x, ElementPtr0->next.location.y,
+ ElementPtr1->next.location.x, ElementPtr1->next.location.y);
+ log_add (log_Debug, "RelTravelAngle : %d, ImpactAngles <%d, %d>",
+ RelTravelAngle, ImpactAngle0, ImpactAngle1);
+#endif /* DEBUG_COLLIDE */
+
+ if (ElementPtr0->next.location.x == ElementPtr0->current.location.x
+ && ElementPtr0->next.location.y == ElementPtr0->current.location.y
+ && ElementPtr1->next.location.x == ElementPtr1->current.location.x
+ && ElementPtr1->next.location.y == ElementPtr1->current.location.y)
+ {
+ if (ElementPtr0->state_flags & ElementPtr1->state_flags & DEFY_PHYSICS)
+ {
+ ImpactAngle0 = TravelAngle0 + (HALF_CIRCLE - OCTANT);
+ ImpactAngle1 = TravelAngle1 + (HALF_CIRCLE - OCTANT);
+ ZeroVelocityComponents (&ElementPtr0->velocity);
+ ZeroVelocityComponents (&ElementPtr1->velocity);
+ }
+ ElementPtr0->state_flags |= (DEFY_PHYSICS | COLLISION);
+ ElementPtr1->state_flags |= (DEFY_PHYSICS | COLLISION);
+#ifdef DEBUG_COLLIDE
+ log_add (log_Debug, "No movement before collision -- "
+ "<(%d, %d) = %d, (%d, %d) = %d>",
+ dx0, dy0, ImpactAngle0 - OCTANT, dx1, dy1,
+ ImpactAngle1 - OCTANT);
+#endif /* DEBUG_COLLIDE */
+ }
+
+ {
+ SIZE mass0, mass1;
+ long scalar;
+
+ mass0 = ElementPtr0->mass_points /* << 2 */;
+ mass1 = ElementPtr1->mass_points /* << 2 */;
+ scalar = (long)SINE (Directness, speed << 1) * (mass0 * mass1);
+
+ if (!GRAVITY_MASS (ElementPtr0->mass_points + 1))
+ {
+ if (ElementPtr0->state_flags & PLAYER_SHIP)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+ if (!(ElementPtr0->state_flags & DEFY_PHYSICS))
+ {
+ if (ElementPtr0->turn_wait < COLLISION_TURN_WAIT)
+ ElementPtr0->turn_wait += COLLISION_TURN_WAIT;
+ if (ElementPtr0->thrust_wait < COLLISION_THRUST_WAIT)
+ ElementPtr0->thrust_wait += COLLISION_THRUST_WAIT;
+ }
+ }
+
+ speed = (SIZE)(scalar / ((long)mass0 * (mass0 + mass1)));
+ DeltaVelocityComponents (&ElementPtr0->velocity,
+ COSINE (ImpactAngle0, speed),
+ SINE (ImpactAngle0, speed));
+
+ GetCurrentVelocityComponents (&ElementPtr0->velocity, &dx0, &dy0);
+ if (dx0 < 0)
+ dx0 = -dx0;
+ if (dy0 < 0)
+ dy0 = -dy0;
+
+ if (VELOCITY_TO_WORLD (dx0 + dy0) < SCALED_ONE)
+ SetVelocityComponents (&ElementPtr0->velocity,
+ COSINE (ImpactAngle0,
+ WORLD_TO_VELOCITY (SCALED_ONE) - 1),
+ SINE (ImpactAngle0,
+ WORLD_TO_VELOCITY (SCALED_ONE) - 1));
+ }
+
+ if (!GRAVITY_MASS (ElementPtr1->mass_points + 1))
+ {
+ if (ElementPtr1->state_flags & PLAYER_SHIP)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+ if (!(ElementPtr1->state_flags & DEFY_PHYSICS))
+ {
+ if (ElementPtr1->turn_wait < COLLISION_TURN_WAIT)
+ ElementPtr1->turn_wait += COLLISION_TURN_WAIT;
+ if (ElementPtr1->thrust_wait < COLLISION_THRUST_WAIT)
+ ElementPtr1->thrust_wait += COLLISION_THRUST_WAIT;
+ }
+ }
+
+ speed = (SIZE)(scalar / ((long)mass1 * (mass0 + mass1)));
+ DeltaVelocityComponents (&ElementPtr1->velocity,
+ COSINE (ImpactAngle1, speed),
+ SINE (ImpactAngle1, speed));
+
+ GetCurrentVelocityComponents (&ElementPtr1->velocity, &dx1, &dy1);
+ if (dx1 < 0)
+ dx1 = -dx1;
+ if (dy1 < 0)
+ dy1 = -dy1;
+
+ if (VELOCITY_TO_WORLD (dx1 + dy1) < SCALED_ONE)
+ SetVelocityComponents (&ElementPtr1->velocity,
+ COSINE (ImpactAngle1,
+ WORLD_TO_VELOCITY (SCALED_ONE) - 1),
+ SINE (ImpactAngle1,
+ WORLD_TO_VELOCITY (SCALED_ONE) - 1));
+ }
+#ifdef DEBUG_COLLIDE
+ GetCurrentVelocityComponents (&ElementPtr0->velocity, &dx0, &dy0);
+ GetCurrentVelocityComponents (&ElementPtr1->velocity, &dx1, &dy1);
+ log_add (log_Debug, "After: <%d, %d> <%d, %d>\n",
+ dx0, dy0, dx1, dy1);
+#endif /* DEBUG_COLLIDE */
+ }
+}
+
diff --git a/src/uqm/collide.h b/src/uqm/collide.h
new file mode 100644
index 0000000..50cc5f1
--- /dev/null
+++ b/src/uqm/collide.h
@@ -0,0 +1,70 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_COLLIDE_H_
+#define UQM_COLLIDE_H_
+
+#include "element.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define COLLISION_TURN_WAIT 1
+#define COLLISION_THRUST_WAIT 3
+
+#define SKIP_COLLISION (NONSOLID | DISAPPEARING)
+#define CollidingElement(e) \
+ (!((e)->state_flags & SKIP_COLLISION))
+#define CollisionPossible(e0,e1) \
+ (CollidingElement (e0) \
+ && (!(((e1)->state_flags & (e0)->state_flags) & COLLISION) \
+ && ((!(((e1)->state_flags & (e0)->state_flags) & IGNORE_SIMILAR) \
+ || (e1)->pParent != (e0)->pParent)) \
+ && ((e1)->mass_points || (e0)->mass_points)))
+
+#define InitIntersectStartPoint(eptr) \
+{ \
+ (eptr)->IntersectControl.IntersectStamp.origin.x = \
+ WORLD_TO_DISPLAY ((eptr)->current.location.x); \
+ (eptr)->IntersectControl.IntersectStamp.origin.y = \
+ WORLD_TO_DISPLAY ((eptr)->current.location.y); \
+}
+
+#define InitIntersectEndPoint(eptr) \
+{ \
+ (eptr)->IntersectControl.EndPoint.x = \
+ WORLD_TO_DISPLAY ((eptr)->next.location.x); \
+ (eptr)->IntersectControl.EndPoint.y = \
+ WORLD_TO_DISPLAY ((eptr)->next.location.y); \
+}
+
+#define InitIntersectFrame(eptr) \
+{ \
+ (eptr)->IntersectControl.IntersectStamp.frame = \
+ SetEquFrameIndex ((eptr)->next.image.farray[0], \
+ (eptr)->next.image.frame); \
+}
+
+extern void collide (ELEMENT *ElementPtr0, ELEMENT *ElementPtr1);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_COLLIDE_H_ */
diff --git a/src/uqm/colors.h b/src/uqm/colors.h
new file mode 100644
index 0000000..318fe49
--- /dev/null
+++ b/src/uqm/colors.h
@@ -0,0 +1,440 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#ifndef UQM_COLORS_H_
+#define UQM_COLORS_H_
+
+// To be used as an indicator that the actual value of the color does not
+// matter, for instance in structure initialisations for fields which
+// are irrelevant in the context.
+#define UNDEFINED_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00)
+
+#if 0
+#define DEFAULT_COLOR_00 \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00)
+#define DEFAULT_COLOR_01 \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+#define DEFAULT_COLOR_02 \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02)
+#define DEFAULT_COLOR_03 \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)
+#define DEFAULT_COLOR_04 \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
+#define DEFAULT_COLOR_05 \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05)
+#define DEFAULT_COLOR_06 \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x00), 0x06)
+#define DEFAULT_COLOR_07 \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x07)
+#define DEFAULT_COLOR_08 \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08)
+#define DEFAULT_COLOR_09 \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+#define DEFAULT_COLOR_0A \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x0A), 0x0A)
+#define DEFAULT_COLOR_0B \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+#define DEFAULT_COLOR_0C \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)
+#define DEFAULT_COLOR_0D \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x1F), 0x0D)
+#define DEFAULT_COLOR_0E \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x0E)
+#define DEFAULT_COLOR_0F \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F)
+#endif
+
+
+#define BLACK_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00)
+#define LTGRAY_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x07)
+#define DKGRAY_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08)
+#define VDKGRAY_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x06, 0x06, 0x06), 0x00)
+#define WHITE_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F)
+#define BRIGHT_RED_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x04)
+#define BRIGHT_GREEN_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x1F, 0x00), 0x02)
+#define BRIGHT_BLUE_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x1F), 0x01)
+
+#define NORMAL_ILLUMINATED_COLOR \
+ WHITE_COLOR
+#define NORMAL_SHADOWED_COLOR \
+ DKGRAY_COLOR
+#define HIGHLIGHT_ILLUMINATED_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)
+#define HIGHLIGHT_SHADOWED_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
+#define MENU_BACKGROUND_COLOR \
+ LTGRAY_COLOR
+#define MENU_FOREGROUND_COLOR \
+ DKGRAY_COLOR
+#define MENU_TEXT_COLOR \
+ VDKGRAY_COLOR
+#define MENU_HIGHLIGHT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x0E)
+#define MENU_CURSOR_COLOR \
+ WHITE_COLOR
+
+#define STATUS_ILLUMINATED_COLOR \
+ WHITE_COLOR
+#define STATUS_SHADOWED_COLOR \
+ DKGRAY_COLOR
+#define STATUS_SHAPE_COLOR \
+ BLACK_COLOR
+#define STATUS_SHAPE_OUTLINE_COLOR \
+ WHITE_COLOR
+
+#define CONTROL_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+
+#define ALLIANCE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+#define HIERARCHY_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
+#define ALLIANCE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+#define HIERARCHY_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)
+#define ALLIANCE_BOX_HIGHLIGHT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+#define HIERARCHY_BOX_HIGHLIGHT_COLOR \
+ HIERARCHY_BACKGROUND_COLOR
+
+#define MESSAGE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+#define MESSAGE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)
+
+
+// Not highlighted dialog options in comm.
+#define COMM_PLAYER_TEXT_NORMAL_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)
+
+// Currently highlighted dialog option in comm.
+#define COMM_PLAYER_TEXT_HIGHLIGHT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1A, 0x1A, 0x1A), 0x12)
+
+// Background color of the area containing the player's dialog options.
+#define COMM_PLAYER_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+
+// "(In response to your statement)"
+#define COMM_RESPONSE_INTRO_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0C, 0x1F), 0x48)
+
+// Your dialog option after choosing it.
+#define COMM_FEEDBACK_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x12, 0x14, 0x4F), 0x44)
+
+// The background when reviewing the conversation history.
+#define COMM_HISTORY_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x05, 0x00), 0x00)
+
+// The text when reviewing the conversation history.
+#define COMM_HISTORY_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x10, 0x00), 0x6B)
+
+// The text "MORE" when reviewing the conversation history.
+#define COMM_MORE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x17, 0x00), 0x00)
+
+// Default colors for System Dialog Boxes (DrawShadowedBox)
+#define SHADOWBOX_BACKGROUND_COLOR \
+ MENU_BACKGROUND_COLOR
+
+#define SHADOWBOX_MEDIUM_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19)
+
+#define SHADOWBOX_DARK_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F)
+
+
+// === SIS ===
+
+// Left border of the "SIS" view (the part in which your ship flies).
+#define SIS_LEFT_BORDER_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19)
+
+// Right and bottom border of the "SIS" view.
+#define SIS_BOTTOM_RIGHT_BORDER_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F)
+
+// Text color of the string "CAPTAIN", when using PC fonts.
+#define PC_CAPTAIN_STRING_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x02, 0x04, 0x1E), 0x38)
+
+// Background color of the string "CAPTAIN", when using PC fonts.
+#define PC_CAPTAIN_STRING_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+
+// Text color of the captain's name.
+#define CAPTAIN_NAME_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x16, 0x0B, 0x1F), 0x38)
+
+// Background color of the captain's name.
+#define CAPTAIN_NAME_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+
+// Text color of the flagship's name, when using 3DO fonts.
+#define THREEDO_FLAGSHIP_NAME_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x0A, 0x00), 0x0C)
+
+// Background color of the flagship's name.
+#define FLAGSHIP_NAME_BACKGROUND_COLOR \
+ BLACK_COLOR
+
+// Text color for the message area (at the top of the screen, on the left
+// hand side, containing the name of the solar system.
+#define SIS_MESSAGE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33)
+
+// Color of autocompleted text after the current cursor position,
+// when editing in the title area.
+#define SIS_MESSAGE_EXTRA_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x12, 0x00, 0x12), 0x33)
+
+// Background color for the message area.
+#define SIS_MESSAGE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+
+// Cursor color when editing in the message area.
+#define SIS_MESSAGE_CURSOR_COLOR \
+ BLACK_COLOR
+
+// Text color of the title (at the top of the screen, on the right
+// hand side, containing the coordinates in HyperSpace, or the planet name
+// in IP.
+#define SIS_TITLE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33)
+
+// Background color of the title.
+#define SIS_TITLE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+
+// Text color of the status message, below the flagship overview, containing
+// the date, RU, etc.
+#define STATUS_MESSAGE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x10, 0x00), 0x6B)
+
+// Background color of the status message.
+#define STATUS_MESSAGE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x08, 0x00), 0x6E)
+
+// Pulsating color of the string "AUTO-PILOT"
+#define AUTOPILOT_COLOR_CYCLE_TABLE \
+ { \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0A, 0x14, 0x18), 0x5B), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x06, 0x10, 0x16), 0x5C), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x03, 0x0E, 0x14), 0x5D), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x02, 0x0C, 0x11), 0x5E), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x01, 0x0B, 0x0F), 0x5F), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x01, 0x09, 0x0D), 0x60), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x07, 0x0B), 0x61), \
+ }
+
+// Colors for the fuel in the fuel tanks as they are filled up,
+// when viewed from the shipyard.
+#define FUEL_COLOR_TABLE \
+ { \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2C), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7F), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7E), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7D), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7C), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7B), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7A), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x18, 0x00), 0x79), \
+ }
+
+// Colors for the crew in the crew pods as they are filled up,
+// when viewed from the shipyard, when using PC fonts.
+#define PC_CREW_COLOR_TABLE \
+ { \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0A, 0x1E, 0x09), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x1E, 0x00), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x1B, 0x00), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x18, 0x00), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x15, 0x00), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x12, 0x00), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x10, 0x00), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x0D, 0x00), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x0A, 0x00), 0x65), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x07, 0x00), 0x65), \
+ }
+
+// Colors for the crew in the crew pods as they are filled up,
+// when viewed from the shipyard, when using 3DO fonts.
+#define THREEDO_CREW_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x05, 0x10, 0x05), 0x65)
+
+// Colors for the minerals in the storage bays as they are filled up,
+// when viewed from the shipyard.
+#define STORAGE_BAY_COLOR_TABLE \
+ { \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1F, 0x1F), 0x0F), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1C, 0x1C, 0x1C), 0x11), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x18, 0x18, 0x18), 0x13), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x15, 0x15, 0x15), 0x15), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x12, 0x12, 0x12), 0x17), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x10, 0x10, 0x10), 0x19), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0D, 0x0D, 0x0D), 0x1B), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0A, 0x0A, 0x0A), 0x1D), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x08, 0x08, 0x08), 0x1F), \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x05, 0x05, 0x05), 0x21), \
+ }
+
+// Color of the storage bay indicator, as shown beneath the flagship,
+// for the parts which are full.
+#define STORAGE_BAY_FULL_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08)
+
+// Color of the storage bay indicator, as shown beneath the flagship,
+// for the parts which are empty.
+#define STORAGE_BAY_EMPTY_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x06, 0x06, 0x06), 0x20)
+
+
+// === PC Menus ===
+
+// Background color of the PC-style menus.
+#define PCMENU_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x15), 0x00)
+
+// Color of the top and left segments of the border around PC-style menus.
+#define PCMENU_TOP_LEFT_BORDER_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0F, 0x0F, 0x0F), 0x00)
+
+// Color of the bottom and right segments of the border around PC-style menus.
+#define PCMENU_BOTTOM_RIGHT_BORDER_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x06, 0x06, 0x06), 0x00)
+
+// Text color of an unselected menu item.
+#define PCMENU_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x15, 0x15), 0x00)
+
+// Text color of an selected menu item.
+#define PCMENU_SELECTION_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+
+// Background color of a selected menu item.
+#define PCMENU_SELECTION_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+
+// === 3DO menus ===
+#define THREEDOMENU_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x1F, 0x00), 0x00)
+
+
+// === Credits ===
+
+#define CREDITS_TEXT_COLOR \
+ WHITE_COLOR
+
+
+// === Cargo menu ===
+
+#define CARGO_BACK_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+
+#define CARGO_WORTH_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+
+#define CARGO_AMOUNT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)
+
+#define CARGO_SELECTED_BACK_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+
+#define CARGO_SELECTED_WORTH_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)
+
+#define CARGO_SELECTED_AMOUNT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+
+
+// === Devices menu ===
+
+#define DEVICES_BACK_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+
+#define DEVICES_NAME_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)
+
+#define DEVICES_SELECTED_BACK_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+
+#define DEVICES_SELECTED_NAME_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+
+
+// === Roster menu ===
+
+#define ROSTER_MODIFY_SHIP_COLOR \
+ WHITE_COLOR
+
+// === Scan menu and general ===
+
+#define SCAN_PC_TITLE_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x15), 0x3B)
+
+#define SCAN_INFO_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0F, 0x00, 0x19), 0x3B)
+
+#define SCAN_MINERAL_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2C)
+
+#define SCAN_ENERGY_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0C, 0x0C, 0x0C), 0x1C)
+
+#define SCAN_BIOLOGICAL_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x0E, 0x00), 0x6C)
+
+#define SCAN_MINERAL_TINT_COLOR \
+ BRIGHT_RED_COLOR_INIT
+
+#define SCAN_ENERGY_TINT_COLOR \
+ WHITE_COLOR_INIT
+
+#define SCAN_BIOLOGICAL_TINT_COLOR \
+ BRIGHT_GREEN_COLOR_INIT
+
+
+// Temporary, until we can use C'99 features:
+#define BLACK_COLOR_INIT \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x00), 0x00)
+#define WHITE_COLOR_INIT \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1F, 0x1F), 0x0F)
+#define BRIGHT_RED_COLOR_INIT \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x04)
+#define BRIGHT_GREEN_COLOR_INIT \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x1F, 0x00), 0x02)
+#define BRIGHT_BLUE_COLOR_INIT \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x1F), 0x01)
+#define UNDEFINED_COLOR_INIT \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x00), 0x00)
+
+
+#endif /* UQM_COLORS_H_ */
+
diff --git a/src/uqm/comm.c b/src/uqm/comm.c
new file mode 100644
index 0000000..ff2818c
--- /dev/null
+++ b/src/uqm/comm.c
@@ -0,0 +1,1649 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define COMM_INTERNAL
+#include "comm.h"
+
+#include "build.h"
+#include "commanim.h"
+#include "commglue.h"
+#include "controls.h"
+#include "colors.h"
+#include "sis.h"
+#include "units.h"
+#include "encount.h"
+#include "starmap.h"
+#include "endian_uqm.h"
+#include "gamestr.h"
+#include "options.h"
+#include "oscill.h"
+#include "save.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "nameref.h"
+#include "uqmdebug.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/inplib.h"
+#include "libs/sound/sound.h"
+#include "libs/sound/trackplayer.h"
+#include "libs/log.h"
+
+#include <ctype.h>
+
+#define MAX_RESPONSES 8
+#define BACKGROUND_VOL (usingSpeech ? (NORMAL_VOLUME / 2) : NORMAL_VOLUME)
+#define FOREGROUND_VOL NORMAL_VOLUME
+
+// Oscilloscope frame rate
+// Should be <= COMM_ANIM_RATE
+// XXX: was 32 picked experimentally?
+#define OSCILLOSCOPE_RATE (ONE_SECOND / 32)
+
+// Maximum comm animation frame rate (actual execution rate)
+// A gfx frame is not always produced during an execution frame,
+// and several animations are combined into one gfx frame.
+// The rate was originally 120fps which allowed for more animation
+// precision which is ultimately wasted on the human eye anyway.
+// The highest known stable animation rate is 40fps, so that's what we use.
+#define COMM_ANIM_RATE (ONE_SECOND / 40)
+
+static CONTEXT AnimContext;
+
+LOCDATA CommData;
+UNICODE shared_phrase_buf[2048];
+
+static BOOLEAN TalkingFinished;
+static CommIntroMode curIntroMode = CIM_DEFAULT;
+static TimeCount fadeTime;
+
+typedef struct response_entry
+{
+ RESPONSE_REF response_ref;
+ TEXT response_text;
+ RESPONSE_FUNC response_func;
+} RESPONSE_ENTRY;
+
+typedef struct encounter_state
+{
+ BOOLEAN (*InputFunc) (struct encounter_state *pES);
+
+ COUNT Initialized;
+ TimeCount NextTime; // framerate control
+ BYTE num_responses;
+ BYTE cur_response;
+ BYTE top_response;
+ RESPONSE_ENTRY response_list[MAX_RESPONSES];
+
+ UNICODE phrase_buf[1024];
+} ENCOUNTER_STATE;
+
+static ENCOUNTER_STATE *pCurInputState;
+
+static BOOLEAN clear_subtitles;
+static TEXT SubtitleText;
+static const UNICODE *last_subtitle;
+
+static CONTEXT TextCacheContext;
+static FRAME TextCacheFrame;
+
+
+RECT CommWndRect = {
+ // default values; actually inited by HailAlien()
+ {SIS_ORG_X, SIS_ORG_Y},
+ {0, 0}
+};
+
+static void ClearSubtitles (void);
+static void CheckSubtitles (void);
+static void RedrawSubtitles (void);
+
+
+/* _count_lines - sees how many lines a given input string would take to
+ * display given the line wrapping information
+ */
+static int
+_count_lines (TEXT *pText)
+{
+ SIZE text_width;
+ const char *pStr;
+ int numLines = 0;
+ BOOLEAN eol;
+
+ text_width = CommData.AlienTextWidth;
+ SetContextFont (CommData.AlienFont);
+
+ pStr = pText->pStr;
+ do
+ {
+ ++numLines;
+ pText->pStr = pStr;
+ eol = getLineWithinWidth (pText, &pStr, text_width, (COUNT)~0);
+ } while (!eol);
+ pText->pStr = pStr;
+
+ return numLines;
+}
+
+// status == -1: draw highlighted player dialog option
+// status == -2: draw non-highlighted player dialog option
+// status == -4: use current context, and baseline from pTextIn
+// status == 1: draw alien speech; subtitle cache is used
+static COORD
+add_text (int status, TEXT *pTextIn)
+{
+ COUNT maxchars, numchars;
+ TEXT locText;
+ TEXT *pText;
+ SIZE leading;
+ const char *pStr;
+ SIZE text_width;
+ int num_lines = 0;
+ static COORD last_baseline;
+ BOOLEAN eol;
+ CONTEXT OldContext = NULL;
+
+ BatchGraphics ();
+
+ maxchars = (COUNT)~0;
+ if (status == 1)
+ {
+ if (last_subtitle == pTextIn->pStr)
+ {
+ // draws cached subtitle
+ STAMP s;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = TextCacheFrame;
+ DrawStamp (&s);
+ UnbatchGraphics ();
+ return last_baseline;
+ }
+ else
+ {
+ // draw to subtitle cache; prepare first
+ OldContext = SetContext (TextCacheContext);
+ ClearDrawable ();
+
+ last_subtitle = pTextIn->pStr;
+ }
+
+ text_width = CommData.AlienTextWidth;
+ SetContextFont (CommData.AlienFont);
+ GetContextFontLeading (&leading);
+
+ pText = pTextIn;
+ }
+ else if (GetContextFontLeading (&leading), status <= -4)
+ {
+ text_width = (SIZE) (SIS_SCREEN_WIDTH - 8 - (TEXT_X_OFFS << 2));
+
+ pText = pTextIn;
+ }
+ else
+ {
+ text_width = (SIZE) (SIS_SCREEN_WIDTH - 8 - (TEXT_X_OFFS << 2));
+
+ switch (status)
+ {
+ case -3:
+ // Unknown. Never reached; color matches the background color.
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01));
+ break;
+ case -2:
+ // Not highlighted dialog options.
+ SetContextForeGroundColor (COMM_PLAYER_TEXT_NORMAL_COLOR);
+ break;
+ case -1:
+ // Currently highlighted dialog option.
+ SetContextForeGroundColor (COMM_PLAYER_TEXT_HIGHLIGHT_COLOR);
+ break;
+ }
+
+ maxchars = pTextIn->CharCount;
+ locText = *pTextIn;
+ locText.baseline.x -= 8;
+ locText.CharCount = (COUNT)~0;
+ locText.pStr = STR_BULLET;
+ font_DrawText (&locText);
+
+ locText = *pTextIn;
+ pText = &locText;
+ pText->baseline.y -= leading;
+ }
+
+ numchars = 0;
+ pStr = pText->pStr;
+
+ if (status > 0 && (CommData.AlienTextValign &
+ (VALIGN_MIDDLE | VALIGN_BOTTOM)))
+ {
+ num_lines = _count_lines(pText);
+ if (CommData.AlienTextValign == VALIGN_BOTTOM)
+ pText->baseline.y -= (leading * num_lines);
+ else if (CommData.AlienTextValign == VALIGN_MIDDLE)
+ pText->baseline.y -= ((leading * num_lines) / 2);
+ if (pText->baseline.y < 0)
+ pText->baseline.y = 0;
+ }
+
+ do
+ {
+ pText->pStr = pStr;
+ pText->baseline.y += leading;
+
+ eol = getLineWithinWidth (pText, &pStr, text_width, maxchars);
+
+ maxchars -= pText->CharCount;
+ if (maxchars != 0)
+ --maxchars;
+ numchars += pText->CharCount;
+
+ if (status <= 0)
+ {
+ // Player dialog option or (status == -4) other non-alien
+ // text.
+ if (pText->baseline.y < SIS_SCREEN_HEIGHT)
+ font_DrawText (pText);
+
+ if (status < -4 && pText->baseline.y >= -status - 10)
+ {
+ // Never actually reached. Status is never <-4.
+ ++pStr;
+ break;
+ }
+ }
+ else
+ {
+ // Alien speech
+ font_DrawTracedText (pText,
+ CommData.AlienTextFColor, CommData.AlienTextBColor);
+ }
+ } while (!eol && maxchars);
+ pText->pStr = pStr;
+
+ if (status == 1)
+ {
+ STAMP s;
+
+ // We were drawing to cache -- flush to screen
+ SetContext (OldContext);
+ s.origin.x = s.origin.y = 0;
+ s.frame = TextCacheFrame;
+ DrawStamp (&s);
+
+ last_baseline = pText->baseline.y;
+ }
+
+ UnbatchGraphics ();
+ return (pText->baseline.y);
+}
+
+// This function calculates how much of a string can be fitted within
+// a specific width, up to a newline or terminating \0.
+// pText is the text to be fitted. pText->CharCount will be set to the
+// number of characters that fitted.
+// startNext will be filled with the start of the first word that
+// doesn't fit in one line, or if an entire line fits, to the character
+// past the newline, or if the entire string fits, to the end of the
+// string.
+// maxWidth is the maximum number of pixels that a line may be wide
+// ASSUMPTION: there are no words in the text wider than maxWidth
+// maxChars is the maximum number of characters (not bytes) that are to
+// be fitted.
+// TRUE is returned if a complete line fitted
+// FALSE otherwise
+BOOLEAN
+getLineWithinWidth(TEXT *pText, const char **startNext,
+ SIZE maxWidth, COUNT maxChars)
+{
+ BOOLEAN eol;
+ // The end of the line of text has been reached.
+ BOOLEAN done;
+ // We cannot add any more words.
+ RECT rect;
+ COUNT oldCount;
+ const char *ptr;
+ const char *wordStart;
+ UniChar ch;
+ COUNT charCount;
+
+ //GetContextClipRect (&rect);
+
+ eol = FALSE;
+ done = FALSE;
+ oldCount = 1;
+ charCount = 0;
+ ch = '\0';
+ ptr = pText->pStr;
+ for (;;)
+ {
+ wordStart = ptr;
+
+ // Scan one word.
+ for (;;)
+ {
+ if (*ptr == '\0')
+ {
+ eol = TRUE;
+ done = TRUE;
+ break;
+ }
+ ch = getCharFromString (&ptr);
+ eol = ch == '\0' || ch == '\n' || ch == '\r';
+ done = eol || charCount >= maxChars;
+ if (done || ch == ' ')
+ break;
+ charCount++;
+ }
+
+ oldCount = pText->CharCount;
+ pText->CharCount = charCount;
+ TextRect (pText, &rect, NULL);
+
+ if (rect.extent.width >= maxWidth)
+ {
+ pText->CharCount = oldCount;
+ *startNext = wordStart;
+ return FALSE;
+ }
+
+ if (done)
+ {
+ *startNext = ptr;
+ return eol;
+ }
+ charCount++;
+ // For the space in between words.
+ }
+}
+
+static void
+DrawSISComWindow (void)
+{
+ CONTEXT OldContext;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
+ {
+ RECT r;
+
+ OldContext = SetContext (SpaceContext);
+
+ r.corner.x = 0;
+ r.corner.y = SLIDER_Y + SLIDER_HEIGHT;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = SIS_SCREEN_HEIGHT - r.corner.y;
+ SetContextForeGroundColor (COMM_PLAYER_BACKGROUND_COLOR);
+ DrawFilledRectangle (&r);
+
+ SetContext (OldContext);
+ }
+}
+
+void
+init_communication (void)
+{
+ // now a no-op
+}
+
+void
+uninit_communication (void)
+{
+ // now a no-op
+}
+
+static void
+RefreshResponses (ENCOUNTER_STATE *pES)
+{
+ COORD y;
+ BYTE response;
+ SIZE leading;
+ STAMP s;
+
+ SetContext (SpaceContext);
+ GetContextFontLeading (&leading);
+ BatchGraphics ();
+
+ DrawSISComWindow ();
+ y = SLIDER_Y + SLIDER_HEIGHT + 1;
+ for (response = pES->top_response; response < pES->num_responses;
+ ++response)
+ {
+ pES->response_list[response].response_text.baseline.x = TEXT_X_OFFS + 8;
+ pES->response_list[response].response_text.baseline.y = y + leading;
+ pES->response_list[response].response_text.align = ALIGN_LEFT;
+ if (response == pES->cur_response)
+ y = add_text (-1, &pES->response_list[response].response_text);
+ else
+ y = add_text (-2, &pES->response_list[response].response_text);
+ }
+
+ if (pES->top_response)
+ {
+ s.origin.y = SLIDER_Y + SLIDER_HEIGHT + 1;
+ s.frame = SetAbsFrameIndex (ActivityFrame, 6);
+ }
+ else if (y > SIS_SCREEN_HEIGHT)
+ {
+ s.origin.y = SIS_SCREEN_HEIGHT - 2;
+ s.frame = SetAbsFrameIndex (ActivityFrame, 7);
+ }
+ else
+ s.frame = 0;
+ if (s.frame)
+ {
+ RECT r;
+
+ GetFrameRect (s.frame, &r);
+ s.origin.x = SIS_SCREEN_WIDTH - r.extent.width - 1;
+ DrawStamp (&s);
+ }
+
+ UnbatchGraphics ();
+}
+
+static void
+FeedbackPlayerPhrase (UNICODE *pStr)
+{
+ SetContext (SpaceContext);
+
+ BatchGraphics ();
+ DrawSISComWindow ();
+ if (pStr[0])
+ {
+ TEXT ct;
+
+ ct.baseline.x = SIS_SCREEN_WIDTH >> 1;
+ ct.baseline.y = SLIDER_Y + SLIDER_HEIGHT + 13;
+ ct.align = ALIGN_CENTER;
+ ct.CharCount = (COUNT)~0;
+
+ ct.pStr = GAME_STRING (FEEDBACK_STRING_BASE);
+ // "(In response to your statement)"
+ SetContextForeGroundColor (COMM_RESPONSE_INTRO_TEXT_COLOR);
+ font_DrawText (&ct);
+
+ ct.baseline.y += 16;
+ SetContextForeGroundColor (COMM_FEEDBACK_TEXT_COLOR);
+ ct.pStr = pStr;
+ add_text (-4, &ct);
+ }
+ UnbatchGraphics ();
+}
+
+static void
+InitSpeechGraphics (void)
+{
+ InitOscilloscope (SetAbsFrameIndex (ActivityFrame, 9));
+
+ InitSlider (0, SLIDER_Y, SIS_SCREEN_WIDTH,
+ SetAbsFrameIndex (ActivityFrame, 5),
+ SetAbsFrameIndex (ActivityFrame, 2));
+}
+
+static void
+UpdateSpeechGraphics (void)
+{
+ static TimeCount NextTime;
+ CONTEXT OldContext;
+
+ if (GetTimeCounter () < NextTime)
+ return; // too early
+
+ NextTime = GetTimeCounter () + OSCILLOSCOPE_RATE;
+
+ OldContext = SetContext (RadarContext);
+ DrawOscilloscope ();
+ SetContext (SpaceContext);
+ DrawSlider ();
+ SetContext (OldContext);
+}
+
+static void
+UpdateAnimations (bool paused)
+{
+ static TimeCount NextTime;
+ CONTEXT OldContext;
+ BOOLEAN change;
+
+ if (GetTimeCounter () < NextTime)
+ return; // too early
+
+ NextTime = GetTimeCounter () + COMM_ANIM_RATE;
+
+ OldContext = SetContext (AnimContext);
+ BatchGraphics ();
+ // Advance and draw ambient, transit and talk animations
+ change = ProcessCommAnimations (clear_subtitles, paused);
+ if (change || clear_subtitles)
+ RedrawSubtitles ();
+ UnbatchGraphics ();
+ clear_subtitles = FALSE;
+ SetContext (OldContext);
+}
+
+static void
+UpdateCommGraphics (void)
+{
+ UpdateAnimations (false);
+ UpdateSpeechGraphics ();
+}
+
+// Derived from INPUT_STATE_DESC
+typedef struct talking_state
+{
+ // Fields required by DoInput()
+ BOOLEAN (*InputFunc) (struct talking_state *);
+
+ TimeCount NextTime; // framerate control
+ COUNT waitTrack;
+ bool rewind;
+ bool seeking;
+ bool ended;
+
+} TALKING_STATE;
+
+static BOOLEAN
+DoTalkSegue (TALKING_STATE *pTS)
+{
+ bool left = false;
+ bool right = false;
+ COUNT curTrack;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ pTS->ended = true;
+ return FALSE;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ JumpTrack ();
+ pTS->ended = true;
+ return FALSE;
+ }
+
+ if (optSmoothScroll == OPT_PC)
+ {
+ left = PulsedInputState.menu[KEY_MENU_LEFT] != 0;
+ right = PulsedInputState.menu[KEY_MENU_RIGHT] != 0;
+ }
+ else if (optSmoothScroll == OPT_3DO)
+ {
+ left = CurrentInputState.menu[KEY_MENU_LEFT] != 0;
+ right = CurrentInputState.menu[KEY_MENU_RIGHT] != 0;
+ }
+
+#if DEMO_MODE || CREATE_JOURNAL
+ left = false;
+ right = false;
+#endif
+
+ if (right)
+ {
+ SetSliderImage (SetAbsFrameIndex (ActivityFrame, 3));
+ if (optSmoothScroll == OPT_PC)
+ FastForward_Page ();
+ else if (optSmoothScroll == OPT_3DO)
+ FastForward_Smooth ();
+ pTS->seeking = true;
+ }
+ else if (left || pTS->rewind)
+ {
+ pTS->rewind = false;
+ SetSliderImage (SetAbsFrameIndex (ActivityFrame, 4));
+ if (optSmoothScroll == OPT_PC)
+ FastReverse_Page ();
+ else if (optSmoothScroll == OPT_3DO)
+ FastReverse_Smooth ();
+ pTS->seeking = true;
+ }
+ else if (pTS->seeking)
+ {
+ // This is only done once the seeking is over (in the smooth
+ // scroll case, once the user releases the seek button)
+ pTS->seeking = false;
+ SetSliderImage (SetAbsFrameIndex (ActivityFrame, 2));
+ }
+ else
+ {
+ // This used to have a buggy guard condition, which
+ // would cause the animations to remain paused in a couple cases
+ // after seeking back to the beginning.
+ // Broken cases were: Syreen "several hours later" and Starbase
+ // VUX Beast analysis by the scientist.
+ CheckSubtitles ();
+ }
+
+ // XXX: When seeking, all animations (talking and ambient) stop
+ // progressing. This is an original 3DO behavior, and I see no
+ // reason why the animations cannot continue while seeking.
+ UpdateAnimations (pTS->seeking);
+ UpdateSpeechGraphics ();
+
+ curTrack = PlayingTrack ();
+ pTS->ended = !pTS->seeking && !curTrack;
+
+ SleepThreadUntil (pTS->NextTime);
+ // Need a high enough framerate for 3DO smooth seeking
+ pTS->NextTime = GetTimeCounter () + ONE_SECOND / 60;
+
+ return pTS->seeking || (curTrack && curTrack <= pTS->waitTrack);
+}
+
+static void
+runCommAnimFrame (void)
+{
+ UpdateCommGraphics ();
+ SleepThread (COMM_ANIM_RATE);
+}
+
+static BOOLEAN
+TalkSegue (COUNT wait_track)
+{
+ TALKING_STATE talkingState;
+
+ // Transition animation to talking state, if necessary
+ if (wantTalkingAnim () && haveTalkingAnim ())
+ {
+ if (haveTransitionAnim ())
+ setRunIntroAnim ();
+
+ setRunTalkingAnim ();
+
+ // wait until the transition finishes
+ while (runningIntroAnim ())
+ runCommAnimFrame ();
+ }
+
+ memset (&talkingState, 0, sizeof talkingState);
+
+ if (wait_track == 0)
+ { // Restarting with a rewind
+ wait_track = WAIT_TRACK_ALL;
+ talkingState.rewind = true;
+ }
+ else if (!PlayingTrack ())
+ { // initial start of player
+ PlayTrack ();
+ assert (PlayingTrack ());
+ }
+
+ // Run the talking controls
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ talkingState.InputFunc = DoTalkSegue;
+ talkingState.waitTrack = wait_track;
+ DoInput (&talkingState, FALSE);
+
+ ClearSubtitles ();
+
+ if (talkingState.ended)
+ { // reached the end; set STOP icon
+ SetSliderImage (SetAbsFrameIndex (ActivityFrame, 8));
+ }
+
+ // transition back to silent, if necessary
+ if (runningTalkingAnim ())
+ setStopTalkingAnim ();
+
+ // Wait until the animation task stops "talking"
+ while (runningTalkingAnim ())
+ runCommAnimFrame ();
+
+ return talkingState.ended;
+}
+
+static void
+CommIntroTransition (void)
+{
+ if (curIntroMode == CIM_CROSSFADE_SCREEN)
+ {
+ ScreenTransition (3, NULL);
+ UnbatchGraphics ();
+ }
+ else if (curIntroMode == CIM_CROSSFADE_SPACE)
+ {
+ RECT r;
+ r.corner.x = SIS_ORG_X;
+ r.corner.y = SIS_ORG_Y;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = SIS_SCREEN_HEIGHT;
+ ScreenTransition (3, &r);
+ UnbatchGraphics ();
+ }
+ else if (curIntroMode == CIM_CROSSFADE_WINDOW)
+ {
+ ScreenTransition (3, &CommWndRect);
+ UnbatchGraphics ();
+ }
+ else if (curIntroMode == CIM_FADE_IN_SCREEN)
+ {
+ UnbatchGraphics ();
+ FadeScreen (FadeAllToColor, fadeTime);
+ }
+ else
+ { // Uknown transition
+ // Have to unbatch anyway or no more graphics, ever
+ UnbatchGraphics ();
+ assert (0 && "Unknown comm intro transition");
+ }
+
+ // Reset the mode for next time. Everything that needs a
+ // different one will let us know.
+ curIntroMode = CIM_DEFAULT;
+}
+
+void
+AlienTalkSegue (COUNT wait_track)
+{
+ // this skips any talk segues that follow an aborted one
+ if ((GLOBAL (CurrentActivity) & CHECK_ABORT) || TalkingFinished)
+ return;
+
+ if (!pCurInputState->Initialized)
+ {
+ InitSpeechGraphics ();
+ SetColorMap (GetColorMapAddress (CommData.AlienColorMap));
+ SetContext (AnimContext);
+ DrawAlienFrame (NULL, 0, TRUE);
+ UpdateSpeechGraphics ();
+ CommIntroTransition ();
+
+ pCurInputState->Initialized = TRUE;
+
+ PlayMusic (CommData.AlienSong, TRUE, 1);
+ SetMusicVolume (BACKGROUND_VOL);
+
+ InitCommAnimations ();
+
+ LastActivity &= ~CHECK_LOAD;
+ }
+
+ TalkingFinished = TalkSegue (wait_track);
+ if (TalkingFinished)
+ FadeMusic (FOREGROUND_VOL, ONE_SECOND);
+}
+
+
+typedef struct summary_state
+{
+ // standard state required by DoInput
+ BOOLEAN (*InputFunc) (struct summary_state *pSS);
+
+ // extended state
+ BOOLEAN Initialized;
+ BOOLEAN PrintNext;
+ SUBTITLE_REF NextSub;
+ const UNICODE *LeftOver;
+
+} SUMMARY_STATE;
+
+static BOOLEAN
+DoConvSummary (SUMMARY_STATE *pSS)
+{
+#define DELTA_Y_SUMMARY 8
+#define MAX_SUMM_ROWS ((SIS_SCREEN_HEIGHT - SLIDER_Y - SLIDER_HEIGHT) \
+ / DELTA_Y_SUMMARY) - 1
+
+ if (!pSS->Initialized)
+ {
+ pSS->PrintNext = TRUE;
+ pSS->NextSub = GetFirstTrackSubtitle ();
+ pSS->LeftOver = NULL;
+ pSS->InputFunc = DoConvSummary;
+ pSS->Initialized = TRUE;
+ DoInput (pSS, FALSE);
+ }
+ else if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ return FALSE; // bail out
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT]
+ || PulsedInputState.menu[KEY_MENU_CANCEL]
+ || PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ if (pSS->NextSub)
+ { // we want the next page
+ pSS->PrintNext = TRUE;
+ }
+ else
+ { // no more, we are done
+ return FALSE;
+ }
+ }
+ else if (pSS->PrintNext)
+ { // print the next page
+ RECT r;
+ TEXT t;
+ int row;
+
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = SIS_SCREEN_HEIGHT - SLIDER_Y - SLIDER_HEIGHT + 2;
+
+ SetContext (AnimContext);
+ SetContextForeGroundColor (COMM_HISTORY_BACKGROUND_COLOR);
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (COMM_HISTORY_TEXT_COLOR);
+
+ r.extent.width -= 2 + 2;
+ t.baseline.x = 2;
+ t.align = ALIGN_LEFT;
+ t.baseline.y = DELTA_Y_SUMMARY;
+ SetContextFont (TinyFont);
+
+ for (row = 0; row < MAX_SUMM_ROWS && pSS->NextSub;
+ ++row, pSS->NextSub = GetNextTrackSubtitle (pSS->NextSub))
+ {
+ const char *next = NULL;
+
+ if (pSS->LeftOver)
+ { // some text left from last subtitle
+ t.pStr = pSS->LeftOver;
+ pSS->LeftOver = NULL;
+ }
+ else
+ {
+ t.pStr = GetTrackSubtitleText (pSS->NextSub);
+ if (!t.pStr)
+ continue;
+ }
+
+ t.CharCount = (COUNT)~0;
+ for ( ; row < MAX_SUMM_ROWS &&
+ !getLineWithinWidth (&t, &next, r.extent.width, (COUNT)~0);
+ ++row)
+ {
+ font_DrawText (&t);
+ t.baseline.y += DELTA_Y_SUMMARY;
+ t.pStr = next;
+ t.CharCount = (COUNT)~0;
+ }
+
+ if (row >= MAX_SUMM_ROWS)
+ { // no more space on screen, but some text left over
+ // from the current subtitle
+ pSS->LeftOver = next;
+ break;
+ }
+
+ // this subtitle fit completely
+ font_DrawText (&t);
+ t.baseline.y += DELTA_Y_SUMMARY;
+ }
+
+ if (row >= MAX_SUMM_ROWS && (pSS->NextSub || pSS->LeftOver))
+ { // draw *MORE*
+ TEXT mt;
+ UNICODE buffer[80];
+
+ mt.baseline.x = SIS_SCREEN_WIDTH >> 1;
+ mt.baseline.y = t.baseline.y;
+ mt.align = ALIGN_CENTER;
+ snprintf (buffer, sizeof (buffer), "%s%s%s", // "MORE"
+ STR_MIDDLE_DOT, GAME_STRING (FEEDBACK_STRING_BASE + 1),
+ STR_MIDDLE_DOT);
+ mt.pStr = buffer;
+ SetContextForeGroundColor (COMM_MORE_TEXT_COLOR);
+ font_DrawText (&mt);
+ }
+
+
+ pSS->PrintNext = FALSE;
+ }
+ else
+ {
+ SleepThread (ONE_SECOND / 20);
+ }
+
+ return TRUE; // keep going
+}
+
+// Called when the player presses the select button on a response.
+static void
+SelectResponse (ENCOUNTER_STATE *pES)
+{
+ TEXT *response_text =
+ &pES->response_list[pES->cur_response].response_text;
+ utf8StringCopy (pES->phrase_buf, sizeof pES->phrase_buf,
+ response_text->pStr);
+ FeedbackPlayerPhrase (pES->phrase_buf);
+ StopTrack ();
+ ClearSubtitles ();
+ SetSliderImage (SetAbsFrameIndex (ActivityFrame, 2));
+
+ FadeMusic (BACKGROUND_VOL, ONE_SECOND);
+
+ TalkingFinished = FALSE;
+ pES->num_responses = 0;
+ (*pES->response_list[pES->cur_response].response_func)
+ (pES->response_list[pES->cur_response].response_ref);
+}
+
+// Called when the player presses the cancel button in comm.
+static void
+SelectConversationSummary (ENCOUNTER_STATE *pES)
+{
+ SUMMARY_STATE SummaryState;
+
+ if (pES)
+ FeedbackPlayerPhrase (pES->phrase_buf);
+
+ SummaryState.Initialized = FALSE;
+ DoConvSummary (&SummaryState);
+
+ if (pES)
+ RefreshResponses (pES);
+ clear_subtitles = TRUE;
+}
+
+static void
+SelectReplay (ENCOUNTER_STATE *pES)
+{
+ FadeMusic (BACKGROUND_VOL, ONE_SECOND);
+ if (pES)
+ FeedbackPlayerPhrase (pES->phrase_buf);
+
+ TalkSegue (0);
+}
+
+static void
+PlayerResponseInput (ENCOUNTER_STATE *pES)
+{
+ BYTE response;
+
+ if (pES->top_response == (BYTE)~0)
+ {
+ pES->top_response = 0;
+ RefreshResponses (pES);
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ SelectResponse (pES);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_CANCEL] &&
+ LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
+ {
+ SelectConversationSummary (pES);
+ }
+ else
+ {
+ response = pES->cur_response;
+ if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ SelectReplay (pES);
+
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ RefreshResponses (pES);
+ FadeMusic (FOREGROUND_VOL, ONE_SECOND);
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_UP])
+ response = (BYTE)((response + (BYTE)(pES->num_responses - 1))
+ % pES->num_responses);
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ response = (BYTE)((BYTE)(response + 1) % pES->num_responses);
+
+ if (response != pES->cur_response)
+ {
+ COORD y;
+
+ BatchGraphics ();
+ add_text (-2,
+ &pES->response_list[pES->cur_response].response_text);
+
+ pES->cur_response = response;
+
+ y = add_text (-1,
+ &pES->response_list[pES->cur_response].response_text);
+ if (response < pES->top_response)
+ {
+ pES->top_response = 0;
+ RefreshResponses (pES);
+ }
+ else if (y > SIS_SCREEN_HEIGHT)
+ {
+ pES->top_response = response;
+ RefreshResponses (pES);
+ }
+ UnbatchGraphics ();
+ }
+
+ UpdateCommGraphics ();
+
+ SleepThreadUntil (pES->NextTime);
+ pES->NextTime = GetTimeCounter () + COMM_ANIM_RATE;
+ }
+}
+
+// Derived from INPUT_STATE_DESC
+typedef struct last_replay_state
+{
+ // Fields required by DoInput()
+ BOOLEAN (*InputFunc) (struct last_replay_state *);
+
+ TimeCount NextTime; // framerate control
+ TimeCount TimeOut;
+
+} LAST_REPLAY_STATE;
+
+static BOOLEAN
+DoLastReplay (LAST_REPLAY_STATE *pLRS)
+{
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (GetTimeCounter () > pLRS->TimeOut)
+ return FALSE; // timed out and done
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL] &&
+ LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
+ {
+ FadeMusic (BACKGROUND_VOL, ONE_SECOND);
+ SelectConversationSummary (NULL);
+ pLRS->TimeOut = FadeMusic (0, ONE_SECOND * 2) + ONE_SECOND / 60;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ SelectReplay (NULL);
+ pLRS->TimeOut = FadeMusic (0, ONE_SECOND * 2) + ONE_SECOND / 60;
+ }
+
+ UpdateCommGraphics ();
+
+ SleepThreadUntil (pLRS->NextTime);
+ pLRS->NextTime = GetTimeCounter () + COMM_ANIM_RATE;
+
+ return TRUE;
+}
+
+static BOOLEAN
+DoCommunication (ENCOUNTER_STATE *pES)
+{
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN, MENU_SOUND_SELECT);
+
+ // First, finish playing all queued tracks if not done yet
+ if (!TalkingFinished)
+ {
+ AlienTalkSegue (WAIT_TRACK_ALL);
+ return TRUE;
+ }
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ ;
+ else if (pES->num_responses == 0)
+ {
+ // The player doesn't get a chance to say anything,
+ // but can still review alien's last phrases.
+ LAST_REPLAY_STATE replayState;
+
+ memset (&replayState, 0, sizeof replayState);
+ replayState.TimeOut = FadeMusic (0, ONE_SECOND * 3) + ONE_SECOND / 60;
+ replayState.InputFunc = DoLastReplay;
+ DoInput (&replayState, FALSE);
+ }
+ else
+ {
+ PlayerResponseInput (pES);
+ return TRUE;
+ }
+
+ SetContext (SpaceContext);
+ DestroyContext (AnimContext);
+ AnimContext = NULL;
+
+ FlushColorXForms ();
+ ClearSubtitles ();
+
+ StopMusic ();
+ StopSound ();
+ StopTrack ();
+ SleepThreadUntil (FadeMusic (NORMAL_VOLUME, 0) + ONE_SECOND / 60);
+
+ return FALSE;
+}
+
+void
+DoResponsePhrase (RESPONSE_REF R, RESPONSE_FUNC response_func,
+ UNICODE *ConstructStr)
+{
+ ENCOUNTER_STATE *pES = pCurInputState;
+ RESPONSE_ENTRY *pEntry;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return;
+
+ if (pES->num_responses == 0)
+ {
+ pES->cur_response = 0;
+ pES->top_response = (BYTE)~0;
+ }
+
+ pEntry = &pES->response_list[pES->num_responses];
+ pEntry->response_ref = R;
+ pEntry->response_text.pStr = ConstructStr;
+ if (pEntry->response_text.pStr)
+ pEntry->response_text.CharCount = (COUNT)~0;
+ else
+ {
+ STRING locString;
+
+ locString = SetAbsStringTableIndex (CommData.ConversationPhrases,
+ (COUNT) (R - 1));
+ pEntry->response_text.pStr =
+ (UNICODE *) GetStringAddress (locString);
+ pEntry->response_text.CharCount = GetStringLength (locString);
+//#define BVT_PROBLEM
+#ifdef BVT_PROBLEM
+ if (pEntry->response_text.pStr[pEntry->response_text.CharCount - 1]
+ == '\0')
+ --pEntry->response_text.CharCount;
+#endif /* BVT_PROBLEM */
+ }
+ pEntry->response_func = response_func;
+ ++pES->num_responses;
+}
+
+static void
+HailAlien (void)
+{
+ ENCOUNTER_STATE ES;
+ FONT PlayerFont, OldFont;
+ MUSIC_REF SongRef = 0;
+ Color TextBack;
+
+ pCurInputState = &ES;
+ memset (pCurInputState, 0, sizeof (*pCurInputState));
+
+ TalkingFinished = FALSE;
+
+ ES.InputFunc = DoCommunication;
+ PlayerFont = LoadFont (PLAYER_FONT);
+
+ CommData.AlienFrame = CaptureDrawable (
+ LoadGraphic (CommData.AlienFrameRes));
+ CommData.AlienFont = LoadFont (CommData.AlienFontRes);
+ CommData.AlienColorMap = CaptureColorMap (
+ LoadColorMap (CommData.AlienColorMapRes));
+ if ((CommData.AlienSongFlags & LDASF_USE_ALTERNATE)
+ && CommData.AlienAltSongRes)
+ SongRef = LoadMusic (CommData.AlienAltSongRes);
+ if (SongRef)
+ CommData.AlienSong = SongRef;
+ else
+ CommData.AlienSong = LoadMusic (CommData.AlienSongRes);
+
+ CommData.ConversationPhrases = CaptureStringTable (
+ LoadStringTable (CommData.ConversationPhrasesRes));
+
+ SubtitleText.baseline = CommData.AlienTextBaseline;
+ SubtitleText.align = CommData.AlienTextAlign;
+
+
+ // init subtitle cache context
+ TextCacheContext = CreateContext ("TextCacheContext");
+ TextCacheFrame = CaptureDrawable (
+ CreateDrawable (WANT_PIXMAP, SIS_SCREEN_WIDTH,
+ SIS_SCREEN_HEIGHT - SLIDER_Y - SLIDER_HEIGHT + 2, 1));
+ SetContext (TextCacheContext);
+ SetContextFGFrame (TextCacheFrame);
+ TextBack = BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x10), 0x00);
+ // Color key for the background.
+ SetContextBackGroundColor (TextBack);
+ ClearDrawable ();
+ SetFrameTransparentColor (TextCacheFrame, TextBack);
+
+ ES.phrase_buf[0] = '\0';
+
+ SetContext (SpaceContext);
+ OldFont = SetContextFont (PlayerFont);
+
+ {
+ RECT r;
+
+ AnimContext = CreateContext ("AnimContext");
+ SetContext (AnimContext);
+ SetContextFGFrame (Screen);
+ GetFrameRect (CommData.AlienFrame, &r);
+ r.extent.width = SIS_SCREEN_WIDTH;
+ CommWndRect.extent = r.extent;
+
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ r.corner = CommWndRect.corner;
+ SetContextClipRect (&r);
+ }
+ else
+ {
+ r.corner.x = SIS_ORG_X;
+ r.corner.y = SIS_ORG_Y;
+ SetContextClipRect (&r);
+ CommWndRect.corner = r.corner;
+
+ DrawSISFrame ();
+ // TODO: find a better way to do this, perhaps set the titles
+ // forward from callers.
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) == (BYTE)~0
+ && GET_GAME_STATE (STARBASE_AVAILABLE))
+ { // Talking to allied Starbase
+ DrawSISMessage (GAME_STRING (STARBASE_STRING_BASE + 1));
+ // "Starbase Commander"
+ DrawSISTitle (GAME_STRING (STARBASE_STRING_BASE + 0));
+ // "Starbase"
+ }
+ else
+ { // Default titles: star name + planet name
+ DrawSISMessage (NULL);
+ DrawSISTitle (GLOBAL_SIS (PlanetName));
+ }
+ }
+
+ DrawSISComWindow ();
+ }
+
+
+ LastActivity |= CHECK_LOAD; /* prevent spurious input */
+ (*CommData.init_encounter_func) ();
+ DoInput (&ES, FALSE);
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ (*CommData.post_encounter_func) ();
+ (*CommData.uninit_encounter_func) ();
+
+ SetContext (SpaceContext);
+ SetContextFont (OldFont);
+
+ DestroyStringTable (ReleaseStringTable (CommData.ConversationPhrases));
+ DestroyMusic (CommData.AlienSong);
+ DestroyColorMap (ReleaseColorMap (CommData.AlienColorMap));
+ DestroyFont (CommData.AlienFont);
+ DestroyDrawable (ReleaseDrawable (CommData.AlienFrame));
+
+ DestroyContext (TextCacheContext);
+ DestroyDrawable (ReleaseDrawable (TextCacheFrame));
+
+ DestroyFont (PlayerFont);
+
+ // Some support code tests either of these to see if the
+ // game is currently in comm or encounter
+ CommData.ConversationPhrasesRes = 0;
+ CommData.ConversationPhrases = 0;
+ pCurInputState = 0;
+}
+
+void
+SetCommIntroMode (CommIntroMode newMode, TimeCount howLong)
+{
+ curIntroMode = newMode;
+ fadeTime = howLong;
+}
+
+COUNT
+InitCommunication (CONVERSATION which_comm)
+{
+ COUNT status;
+ LOCDATA *LocDataPtr;
+
+#ifdef DEBUG
+ if (disableInteractivity)
+ return 0;
+#endif
+
+
+ if (LastActivity & CHECK_LOAD)
+ {
+ LastActivity &= ~CHECK_LOAD;
+ if (which_comm != COMMANDER_CONVERSATION)
+ {
+ if (LOBYTE (LastActivity) == 0)
+ {
+ DrawSISFrame ();
+ }
+ else
+ {
+ ClearSISRect (DRAW_SIS_DISPLAY);
+ RepairSISBorder ();
+ }
+ DrawSISMessage (NULL);
+ if (inHQSpace ())
+ DrawHyperCoords (GLOBAL (ShipStamp.origin));
+ else if (GLOBAL (ip_planet) == 0)
+ DrawHyperCoords (CurStarDescPtr->star_pt);
+ else
+ DrawSISTitle (GLOBAL_SIS (PlanetName));
+ }
+ }
+
+
+ if (which_comm == URQUAN_DRONE_CONVERSATION)
+ {
+ status = URQUAN_DRONE_SHIP;
+ which_comm = URQUAN_CONVERSATION;
+ }
+ else
+ {
+ if (which_comm == YEHAT_REBEL_CONVERSATION)
+ {
+ status = YEHAT_REBEL_SHIP;
+ which_comm = YEHAT_CONVERSATION;
+ }
+ else
+ {
+ COUNT commToShip[] = {
+ RACE_SHIP_FOR_COMM
+ };
+ status = commToShip[which_comm];
+ if (status >= YEHAT_REBEL_SHIP) {
+ /* conversation exception, set to self */
+ status = HUMAN_SHIP;
+ }
+ }
+ StartSphereTracking (status);
+
+ if (which_comm == ORZ_CONVERSATION
+ || (which_comm == TALKING_PET_CONVERSATION
+ && (!GET_GAME_STATE (TALKING_PET_ON_SHIP)
+ || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE))
+ || (which_comm != CHMMR_CONVERSATION
+ && which_comm != SYREEN_CONVERSATION
+ ))//&& CheckAlliance (status) == BAD_GUY))
+ BuildBattle (NPC_PLAYER_NUM);
+ }
+
+ LocDataPtr = init_race (
+ status != YEHAT_REBEL_SHIP ? which_comm :
+ YEHAT_REBEL_CONVERSATION);
+ if (LocDataPtr)
+ { // We make a copy here
+ CommData = *LocDataPtr;
+ }
+
+ if (GET_GAME_STATE (BATTLE_SEGUE) == 0)
+ {
+ // Not offered the chance to attack.
+ status = HAIL;
+ }
+ else if ((status = InitEncounter ()) == HAIL && LocDataPtr)
+ {
+ // The player chose to talk.
+ SET_GAME_STATE (BATTLE_SEGUE, 0);
+ }
+ else
+ {
+ // The player chose to attack.
+ status = ATTACK;
+ SET_GAME_STATE (BATTLE_SEGUE, 1);
+ }
+
+ if (status == HAIL)
+ {
+ HailAlien ();
+ }
+ else if (LocDataPtr)
+ { // only when comm initied successfully
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ (*CommData.post_encounter_func) (); // process states
+
+ (*CommData.uninit_encounter_func) (); // cleanup
+ }
+
+ status = 0;
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ // The Sa-Matra battle is skipped when Cyborg is enabled.
+ // Most likely because the Cyborg is too dumb to know what
+ // to do in this battle.
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE
+ && (GLOBAL (glob_flags) & CYBORG_ENABLED))
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+
+ // Clear the location flags
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 0);
+ status = (GET_GAME_STATE (BATTLE_SEGUE)
+ && GetHeadLink (&GLOBAL (npc_built_ship_q)));
+ if (status)
+ {
+ // Start combat
+ BuildBattle (RPG_PLAYER_NUM);
+ EncounterBattle ();
+ }
+ else
+ {
+ SET_GAME_STATE (BATTLE_SEGUE, 0);
+ }
+ }
+
+ UninitEncounter ();
+
+ return (status);
+}
+
+void
+RaceCommunication (void)
+{
+ COUNT i, status;
+ HSHIPFRAG hStarShip;
+ SHIP_FRAGMENT *FragPtr;
+ HENCOUNTER hEncounter = 0;
+ CONVERSATION RaceComm[] =
+ {
+ RACE_COMMUNICATION
+ };
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ {
+ /* Going into talking pet conversation */
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ CloneShipFragment (SAMATRA_SHIP, &GLOBAL (npc_built_ship_q), 0);
+ InitCommunication (TALKING_PET_CONVERSATION);
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
+ {
+ GLOBAL (CurrentActivity) = WON_LAST_BATTLE;
+ }
+ return;
+ }
+ else if (NextActivity & CHECK_LOAD)
+ {
+ BYTE ec;
+
+ ec = GET_GAME_STATE (ESCAPE_COUNTER);
+
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI) == 1)
+ InitCommunication (SPATHI_CONVERSATION);
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) == 0)
+ InitCommunication (TALKING_PET_CONVERSATION);
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) &
+ ((1 << 4) | (1 << 5)))
+ // Communicate with the Ilwrath using a Hyperwave Broadcaster.
+ InitCommunication (ILWRATH_CONVERSATION);
+ else
+ InitCommunication (CHMMR_CONVERSATION);
+ if (GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
+ {
+ NextActivity = GLOBAL (CurrentActivity) & ~START_ENCOUNTER;
+ if (LOBYTE (NextActivity) == IN_INTERPLANETARY)
+ NextActivity |= START_INTERPLANETARY;
+ GLOBAL (CurrentActivity) |= CHECK_LOAD; /* fake a load game */
+ }
+
+ SET_GAME_STATE (ESCAPE_COUNTER, ec);
+ return;
+ }
+ else if (inHQSpace ())
+ {
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) >= 2)
+ {
+ InitCommunication (ARILOU_CONVERSATION);
+ return;
+ }
+ else
+ {
+ /* Encounter with a black globe in HS, prepare enemy ship list */
+ ENCOUNTER *EncounterPtr;
+
+ // The encounter globe that the flagship collided with is moved
+ // to the head of the queue in hyper.c:cleanup_hyperspace()
+ hEncounter = GetHeadEncounter ();
+ LockEncounter (hEncounter, &EncounterPtr);
+
+ for (i = 0; i < EncounterPtr->num_ships; ++i)
+ {
+ CloneShipFragment (EncounterPtr->race_id,
+ &GLOBAL (npc_built_ship_q),
+ EncounterPtr->ShipList[i].crew_level);
+ }
+
+ // XXX: Bug: CurStarDescPtr was abused to point within
+ // an ENCOUNTER struct, which is immediately unlocked
+ //CurStarDescPtr = (STAR_DESC*)&EncounterPtr->SD;
+ UnlockEncounter (hEncounter);
+ }
+ }
+
+ // First ship in the npc queue defines which alien race
+ // the player will be talking to
+ hStarShip = GetHeadLink (&GLOBAL (npc_built_ship_q));
+ FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ i = FragPtr->race_id;
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+
+ status = InitCommunication (RaceComm[i]);
+
+ if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ return;
+
+ if (i == CHMMR_SHIP)
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY)
+ {
+ /* if used destruct code in interplanetary */
+ if (i == SLYLANDRO_SHIP && status == 0)
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ }
+ else if (hEncounter)
+ {
+ /* Update HSpace encounter info, ships lefts, etc. */
+ BYTE i, NumShips;
+ ENCOUNTER *EncounterPtr;
+
+ LockEncounter (hEncounter, &EncounterPtr);
+
+ NumShips = CountLinks (&GLOBAL (npc_built_ship_q));
+ EncounterPtr->num_ships = NumShips;
+ EncounterPtr->flags |= ENCOUNTER_REFORMING;
+ if (status == 0)
+ EncounterPtr->flags |= ONE_SHOT_ENCOUNTER;
+
+ for (i = 0; i < NumShips; ++i)
+ {
+ HSHIPFRAG hStarShip;
+ SHIP_FRAGMENT *FragPtr;
+ BRIEF_SHIP_INFO *BSIPtr;
+
+ hStarShip = GetStarShipFromIndex (&GLOBAL (npc_built_ship_q), i);
+ FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ BSIPtr = &EncounterPtr->ShipList[i];
+ BSIPtr->race_id = FragPtr->race_id;
+ BSIPtr->crew_level = FragPtr->crew_level;
+ BSIPtr->max_crew = FragPtr->max_crew;
+ BSIPtr->max_energy = FragPtr->max_energy;
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ }
+
+ UnlockEncounter (hEncounter);
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ }
+}
+
+static void
+RedrawSubtitles (void)
+{
+ TEXT t;
+
+ if (!optSubtitles)
+ return;
+
+ if (SubtitleText.pStr)
+ {
+ t = SubtitleText;
+ add_text (1, &t);
+ }
+}
+
+static void
+ClearSubtitles (void)
+{
+ clear_subtitles = TRUE;
+ last_subtitle = NULL;
+ SubtitleText.pStr = NULL;
+ SubtitleText.CharCount = 0;
+}
+
+static void
+CheckSubtitles (void)
+{
+ const UNICODE *pStr;
+ POINT baseline;
+ TEXT_ALIGN align;
+
+ pStr = GetTrackSubtitle ();
+ baseline = CommData.AlienTextBaseline;
+ align = CommData.AlienTextAlign;
+
+ if (pStr != SubtitleText.pStr ||
+ SubtitleText.baseline.x != baseline.x ||
+ SubtitleText.baseline.y != baseline.y ||
+ SubtitleText.align != align)
+ { // Subtitles changed
+ clear_subtitles = TRUE;
+ // Baseline may be updated by the ZFP
+ SubtitleText.baseline = baseline;
+ SubtitleText.align = align;
+ // Make a note in the logs if the update was multiframe
+ if (SubtitleText.pStr == pStr)
+ {
+ log_add (log_Warning, "Dialog text and location changed out of sync");
+ }
+
+ SubtitleText.pStr = pStr;
+ // may have been cleared too
+ if (pStr)
+ SubtitleText.CharCount = (COUNT)~0;
+ else
+ SubtitleText.CharCount = 0;
+ }
+}
+
+void
+EnableTalkingAnim (BOOLEAN enable)
+{
+ if (enable)
+ CommData.AlienTalkDesc.AnimFlags &= ~PAUSE_TALKING;
+ else
+ CommData.AlienTalkDesc.AnimFlags |= PAUSE_TALKING;
+}
diff --git a/src/uqm/comm.h b/src/uqm/comm.h
new file mode 100644
index 0000000..e8f0182
--- /dev/null
+++ b/src/uqm/comm.h
@@ -0,0 +1,142 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_COMM_H_
+#define UQM_COMM_H_
+
+#include "globdata.h"
+#include "units.h"
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+#include "commglue.h"
+ // for CONVERSATION
+
+#ifdef COMM_INTERNAL
+
+#define SLIDER_Y 107
+#define SLIDER_HEIGHT 15
+
+#include "commanim.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern LOCDATA CommData;
+
+static inline BOOLEAN
+haveTalkingAnim (void)
+{
+ return CommData.AlienTalkDesc.NumFrames > 0;
+}
+
+static inline BOOLEAN
+haveTransitionAnim (void)
+{
+ return CommData.AlienTransitionDesc.NumFrames > 0;
+}
+
+static inline BOOLEAN
+wantTalkingAnim (void)
+{
+ return !(CommData.AlienTalkDesc.AnimFlags & PAUSE_TALKING);
+}
+
+static inline void
+setRunTalkingAnim (void)
+{
+ CommData.AlienTalkDesc.AnimFlags |= WAIT_TALKING;
+}
+
+static inline void
+clearRunTalkingAnim (void)
+{
+ CommData.AlienTalkDesc.AnimFlags &= ~WAIT_TALKING;
+}
+
+static inline BOOLEAN
+runningTalkingAnim (void)
+{
+ return (CommData.AlienTalkDesc.AnimFlags & WAIT_TALKING);
+}
+
+static inline void
+setRunIntroAnim (void)
+{
+ CommData.AlienTransitionDesc.AnimFlags |= TALK_INTRO;
+}
+
+static inline BOOLEAN
+runningIntroAnim (void)
+{
+ return (CommData.AlienTransitionDesc.AnimFlags & TALK_INTRO);
+}
+
+static inline void
+setStopTalkingAnim (void)
+{
+ CommData.AlienTalkDesc.AnimFlags |= TALK_DONE;
+}
+
+static inline void
+clearStopTalkingAnim (void)
+{
+ CommData.AlienTalkDesc.AnimFlags &= ~TALK_DONE;
+}
+
+static inline BOOLEAN
+signaledStopTalkingAnim (void)
+{
+ return CommData.AlienTalkDesc.AnimFlags & TALK_DONE;
+}
+
+#endif
+
+#define TEXT_X_OFFS 1
+#define TEXT_Y_OFFS 1
+#define SIS_TEXT_WIDTH (SIS_SCREEN_WIDTH - (TEXT_X_OFFS << 1))
+
+extern void init_communication (void);
+extern void uninit_communication (void);
+
+extern COUNT InitCommunication (CONVERSATION which_comm);
+extern void RaceCommunication (void);
+
+#define WAIT_TRACK_ALL ((COUNT)~0)
+extern void AlienTalkSegue (COUNT wait_track);
+BOOLEAN getLineWithinWidth(TEXT *pText, const char **startNext,
+ SIZE maxWidth, COUNT maxChars);
+
+extern RECT CommWndRect; /* comm window rect */
+
+typedef enum
+{
+ CIM_CROSSFADE_SPACE,
+ CIM_CROSSFADE_WINDOW,
+ CIM_CROSSFADE_SCREEN,
+ CIM_FADE_IN_SCREEN,
+
+ CIM_DEFAULT = CIM_CROSSFADE_SPACE,
+} CommIntroMode;
+extern void SetCommIntroMode (CommIntroMode, TimeCount howLong);
+
+extern void EnableTalkingAnim (BOOLEAN enable);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_COMM_H_ */
diff --git a/src/uqm/comm/Makeinfo b/src/uqm/comm/Makeinfo
new file mode 100644
index 0000000..3f87e5b
--- /dev/null
+++ b/src/uqm/comm/Makeinfo
@@ -0,0 +1,4 @@
+uqm_SUBDIRS="arilou blackur chmmr comandr druuge ilwrath melnorm mycon
+ orz pkunk rebel shofixt slyhome slyland spahome spathi starbas supox
+ syreen talkpet thradd umgah urquan utwig vux yehat zoqfot"
+uqm_HFILES="commall.h"
diff --git a/src/uqm/comm/arilou/Makeinfo b/src/uqm/comm/arilou/Makeinfo
new file mode 100644
index 0000000..27cb053
--- /dev/null
+++ b/src/uqm/comm/arilou/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="arilouc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/arilou/arilouc.c b/src/uqm/comm/arilou/arilouc.c
new file mode 100644
index 0000000..13f2d72
--- /dev/null
+++ b/src/uqm/comm/arilou/arilouc.c
@@ -0,0 +1,855 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/gameev.h"
+
+
+static LOCDATA arilou_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ ARILOU_PMAP_ANIM, /* AlienFrame */
+ ARILOU_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ ARILOU_COLOR_MAP, /* AlienColorMap */
+ ARILOU_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ ARILOU_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 20, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 4, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 1) | (1L << 16)
+ },
+ {
+ 13, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 0) | (1L << 16)
+ },
+ {
+ 22, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1L << 16)
+ },
+ {
+ 31, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4)
+ },
+ {
+ 40, /* StartIndex */
+ 10, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 3)
+ },
+ {
+ 50, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 7)
+ },
+ {
+ 59, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 67, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5)
+ },
+ {
+ 76, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 9)
+ },
+ {
+ 85, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 8)
+ },
+ {
+ 94, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 103, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 13)
+ },
+ {
+ 112, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 121, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 11)
+ },
+ {
+ 129, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1L << 15)
+ },
+ {
+ 138, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 14)
+ },
+ {
+ 146, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 0) | (1 << 1) | (1 << 2)
+ },
+ { /* Hands moving (right up) */
+ 155, /* StartIndex */
+ 2, /* NumFrames */
+ CIRCULAR_ANIM | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 19), /* BlockMask */
+ },
+ { /* Hands moving (left up) */
+ 157, /* StartIndex */
+ 2, /* NumFrames */
+ CIRCULAR_ANIM | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 19), /* BlockMask */
+ },
+ { /* Stars flashing next to the head */
+ 159, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 17) | (1 << 18), /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (R, bye_angry_space))
+ NPCPhrase (GOODBYE_ANGRY_SPACE);
+ else if (PLAYER_SAID (R, bye_friendly_space))
+ NPCPhrase (GOODBYE_FRIENDLY_SPACE);
+ else if (PLAYER_SAID (R, bye_friendly_homeworld))
+ NPCPhrase (GOODBYE_FRDLY_HOMEWORLD);
+ else if (PLAYER_SAID (R, lets_fight))
+ NPCPhrase (NO_FIGHT);
+ else if (PLAYER_SAID (R, bug_eyed_fruitcakes))
+ {
+ NPCPhrase (WE_NEVER_FRIENDS);
+
+ SET_GAME_STATE (ARILOU_MANNER, 2);
+ }
+ else if (PLAYER_SAID (R, best_if_i_killed_you))
+ {
+ NPCPhrase (WICKED_HUMAN);
+
+ SET_GAME_STATE (ARILOU_MANNER, 2);
+ }
+}
+
+static void
+ArilouHome (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[4];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = pStr[3] = 0;
+ if (PLAYER_SAID (R, confused_by_hello))
+ NPCPhrase (CONFUSED_RESPONSE);
+ else if (PLAYER_SAID (R, happy_by_hello))
+ NPCPhrase (HAPPY_RESPONSE);
+ else if (PLAYER_SAID (R, miffed_by_hello))
+ NPCPhrase (MIFFED_RESPONSE);
+ else if (PLAYER_SAID (R, ok_lets_be_friends))
+ NPCPhrase (NO_ALLY_BUT_MUCH_GIVE);
+ else if (PLAYER_SAID (R, what_about_war))
+ {
+ NPCPhrase (ABOUT_WAR);
+
+ SET_GAME_STATE (ARILOU_STACK_1, 1);
+ }
+ else if (PLAYER_SAID (R, what_about_urquan))
+ {
+ NPCPhrase (ABOUT_URQUAN);
+
+ SET_GAME_STATE (ARILOU_STACK_1, 2);
+ }
+ else if (PLAYER_SAID (R, tell_arilou_about_tpet))
+ {
+ NPCPhrase (BAD_NEWS_ABOUT_TPET);
+
+ LastStack = 1;
+ SET_GAME_STATE (ARILOU_STACK_2, 1);
+ }
+ else if (PLAYER_SAID (R, what_do_about_tpet))
+ {
+ NPCPhrase (DANGEROUS_BUT_USEFUL);
+
+ LastStack = 1;
+ SET_GAME_STATE (ARILOU_STACK_2, 2);
+ }
+ else if (PLAYER_SAID (R, learned_about_umgah))
+ {
+ if (GET_GAME_STATE (ARILOU_CHECKED_UMGAH) != 2)
+ NPCPhrase (NO_NEWS_YET);
+ else
+ {
+ NPCPhrase (UMGAH_UNDER_COMPULSION);
+
+ LastStack = 1;
+ }
+
+ DISABLE_PHRASE (learned_about_umgah);
+ }
+ else if (PLAYER_SAID (R, umgah_acting_weird))
+ {
+ NPCPhrase (WELL_GO_CHECK);
+
+ SET_GAME_STATE (ARILOU_CHECKED_UMGAH, 1);
+ AddEvent (RELATIVE_EVENT, 0, 10, 0, ARILOU_UMGAH_CHECK);
+ DISABLE_PHRASE (umgah_acting_weird);
+ }
+ else if (PLAYER_SAID (R, what_do_now))
+ {
+ NPCPhrase (GO_FIND_OUT);
+
+ SET_GAME_STATE (ARILOU_CHECKED_UMGAH, 3);
+ }
+ else if (PLAYER_SAID (R, what_did_on_earth))
+ {
+ NPCPhrase (DID_THIS);
+
+ LastStack = 2;
+ SET_GAME_STATE (ARILOU_STACK_3, 1);
+ }
+ else if (PLAYER_SAID (R, why_did_this))
+ {
+ NPCPhrase (IDF_PARASITES);
+
+ LastStack = 2;
+ SET_GAME_STATE (ARILOU_STACK_3, 2);
+ }
+ else if (PLAYER_SAID (R, tell_more))
+ {
+ NPCPhrase (NOT_NOW);
+
+ LastStack = 2;
+ SET_GAME_STATE (ARILOU_STACK_3, 3);
+ }
+ else if (PLAYER_SAID (R, what_give_me))
+ {
+ NPCPhrase (ABOUT_PORTAL);
+
+ LastStack = 3;
+ SET_GAME_STATE (KNOW_ARILOU_WANT_WRECK, 1);
+
+ R = about_portal_again;
+ DISABLE_PHRASE (what_give_me);
+ }
+ else if (PLAYER_SAID (R, what_about_tpet))
+ {
+ NPCPhrase (ABOUT_TPET);
+
+ SET_GAME_STATE (ARILOU_STACK_4, 1);
+ }
+ else if (PLAYER_SAID (R, about_portal_again))
+ {
+ NPCPhrase (PORTAL_AGAIN);
+
+ DISABLE_PHRASE (about_portal_again);
+ }
+ else if (PLAYER_SAID (R, got_it))
+ {
+ if (GET_GAME_STATE (ARILOU_HOME_VISITS) == 1)
+ NPCPhrase (CLEVER_HUMAN);
+ NPCPhrase (GIVE_PORTAL);
+
+ SET_GAME_STATE (PORTAL_KEY_ON_SHIP, 0);
+ SET_GAME_STATE (PORTAL_SPAWNER, 1);
+ SET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP, 1);
+ }
+#ifdef NEVER
+ else if (PLAYER_SAID (R, got_tpet))
+ {
+ NPCPhrase (OK_GOT_TPET);
+
+ SET_GAME_STATE (ARILOU_STACK_2, 1);
+ }
+#endif /* NEVER */
+
+ switch (GET_GAME_STATE (ARILOU_STACK_1))
+ {
+ case 0:
+ pStr[0] = what_about_war;
+ break;
+ case 1:
+ pStr[0] = what_about_urquan;
+ break;
+ }
+ if (GET_GAME_STATE (TALKING_PET))
+ {
+#ifdef NEVER
+ if (GET_GAME_STATE (ARILOU_STACK_2) == 0)
+ pStr[1] = got_tpet;
+#endif /* NEVER */
+ }
+ else
+ {
+ if (GET_GAME_STATE (TALKING_PET_VISITS))
+ {
+ switch (GET_GAME_STATE (ARILOU_STACK_2))
+ {
+ case 0:
+ pStr[1] = tell_arilou_about_tpet;
+ break;
+ case 1:
+ pStr[1] = what_do_about_tpet;
+ break;
+ }
+ }
+ else if (GET_GAME_STATE (KNOW_UMGAH_ZOMBIES))
+ {
+ if (!GET_GAME_STATE (ARILOU_CHECKED_UMGAH))
+ pStr[1] = umgah_acting_weird;
+ else if (PHRASE_ENABLED (learned_about_umgah) && PHRASE_ENABLED (umgah_acting_weird))
+ pStr[1] = learned_about_umgah;
+ else if (GET_GAME_STATE (ARILOU_CHECKED_UMGAH) == 2)
+ pStr[1] = what_do_now;
+ }
+ }
+ switch (GET_GAME_STATE (ARILOU_STACK_3))
+ {
+ case 0:
+ pStr[2] = what_did_on_earth;
+ break;
+ case 1:
+ pStr[2] = why_did_this;
+ break;
+ case 2:
+ pStr[2] = tell_more;
+ break;
+ }
+ if (!GET_GAME_STATE (KNOW_ARILOU_WANT_WRECK))
+ pStr[3] = what_give_me;
+ else if (!GET_GAME_STATE (ARILOU_STACK_4))
+ pStr[3] = what_about_tpet;
+
+ if (pStr[LastStack])
+ Response (pStr[LastStack], ArilouHome);
+ for (i = 0; i < 4; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ Response (pStr[i], ArilouHome);
+ }
+
+ if (GET_GAME_STATE (KNOW_ARILOU_WANT_WRECK))
+ {
+ if (GET_GAME_STATE (PORTAL_KEY_ON_SHIP))
+ Response (got_it, ArilouHome);
+ else if (PHRASE_ENABLED (about_portal_again) && !GET_GAME_STATE (PORTAL_SPAWNER))
+ Response (about_portal_again, ArilouHome);
+ }
+ if (GET_GAME_STATE (ARILOU_MANNER) != 3)
+ Response (best_if_i_killed_you, ExitConversation);
+ Response (bye_friendly_homeworld, ExitConversation);
+}
+
+static void
+AngryHomeArilou (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, invaders_from_mars))
+ {
+ NPCPhrase (HAD_OUR_REASONS);
+
+ DISABLE_PHRASE (invaders_from_mars);
+ }
+ else if (PLAYER_SAID (R, why_should_i_trust))
+ {
+ NPCPhrase (TRUST_BECAUSE);
+
+ DISABLE_PHRASE (why_should_i_trust);
+ }
+ else if (PLAYER_SAID (R, what_about_interference))
+ {
+ NPCPhrase (INTERFERENCE_NECESSARY);
+
+ DISABLE_PHRASE (what_about_interference);
+ }
+ else if (PLAYER_SAID (R, i_just_like_to_leave))
+ {
+ NPCPhrase (SORRY_NO_LEAVE);
+
+ DISABLE_PHRASE (i_just_like_to_leave);
+ }
+
+ if (PHRASE_ENABLED (invaders_from_mars))
+ Response (invaders_from_mars, AngryHomeArilou);
+ else
+ {
+ Response (bug_eyed_fruitcakes, ExitConversation);
+ }
+ if (PHRASE_ENABLED (why_should_i_trust))
+ Response (why_should_i_trust, AngryHomeArilou);
+ else if (PHRASE_ENABLED (what_about_interference))
+ Response (what_about_interference, AngryHomeArilou);
+ Response (ok_lets_be_friends, ArilouHome);
+ Response (i_just_like_to_leave, AngryHomeArilou);
+}
+
+static void
+AngrySpaceArilou (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, im_sorry))
+ {
+ NPCPhrase (APOLOGIZE_AT_HOMEWORLD);
+
+ DISABLE_PHRASE (im_sorry);
+ }
+
+ Response (lets_fight, ExitConversation);
+ if (PHRASE_ENABLED (im_sorry))
+ {
+ Response (im_sorry, AngrySpaceArilou);
+ }
+ Response (bye_angry_space, ExitConversation);
+}
+
+static void
+FriendlySpaceArilou (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, confused_by_hello))
+ NPCPhrase (CONFUSED_RESPONSE);
+ else if (PLAYER_SAID (R, happy_by_hello))
+ NPCPhrase (HAPPY_RESPONSE);
+ else if (PLAYER_SAID (R, miffed_by_hello))
+ NPCPhrase (MIFFED_RESPONSE);
+ else if (PLAYER_SAID (R, whats_up_1)
+ || PLAYER_SAID (R, whats_up_2))
+ {
+ NumVisits = GET_GAME_STATE (ARILOU_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ARILOU_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_2);
+ }
+ else if (PLAYER_SAID (R, why_you_here))
+ {
+ NPCPhrase (LEARN_THINGS);
+
+ SET_GAME_STATE (ARILOU_STACK_5, 1);
+ }
+ else if (PLAYER_SAID (R, what_things))
+ {
+ NPCPhrase (THESE_THINGS);
+
+ SET_GAME_STATE (ARILOU_STACK_5, 2);
+ }
+ else if (PLAYER_SAID (R, why_do_it))
+ {
+ NPCPhrase (DO_IT_BECAUSE);
+
+ SET_GAME_STATE (ARILOU_STACK_5, 3);
+ }
+ else if (PLAYER_SAID (R, give_me_info_1)
+ || PLAYER_SAID (R, give_me_info_2))
+ {
+ NumVisits = GET_GAME_STATE (ARILOU_HINTS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ARILOU_HINTS_1);
+ break;
+ case 1:
+ NPCPhrase (ARILOU_HINTS_2);
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) < 2)
+ {
+ SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 2);
+ }
+ break;
+ case 2:
+ NPCPhrase (ARILOU_HINTS_3);
+ SET_GAME_STATE (KNOW_URQUAN_STORY, 1);
+ SET_GAME_STATE (KNOW_KOHR_AH_STORY, 1);
+ break;
+ case 3:
+ NPCPhrase (ARILOU_HINTS_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ARILOU_HINTS, NumVisits);
+
+ DISABLE_PHRASE (give_me_info_2);
+ }
+
+ switch (GET_GAME_STATE (ARILOU_STACK_5))
+ {
+ case 0:
+ Response (why_you_here, FriendlySpaceArilou);
+ break;
+ case 1:
+ Response (what_things, FriendlySpaceArilou);
+ break;
+ case 2:
+ Response (why_do_it, FriendlySpaceArilou);
+ break;
+ }
+ if (PHRASE_ENABLED (whats_up_2))
+ {
+ if (GET_GAME_STATE (ARILOU_INFO) == 0)
+ Response (whats_up_1, FriendlySpaceArilou);
+ else
+ Response (whats_up_2, FriendlySpaceArilou);
+ }
+ if (PHRASE_ENABLED (give_me_info_2))
+ {
+ if (GET_GAME_STATE (ARILOU_HINTS) == 0)
+ Response (give_me_info_1, FriendlySpaceArilou);
+ else
+ Response (give_me_info_2, FriendlySpaceArilou);
+ }
+ Response (bye_friendly_space, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits, Manner;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+ else if (!GET_GAME_STATE (MET_ARILOU))
+ {
+ RESPONSE_FUNC RespFunc;
+
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ NPCPhrase (INIT_HELLO);
+ RespFunc = (RESPONSE_FUNC)FriendlySpaceArilou;
+ }
+ else
+ {
+ NPCPhrase (FRDLY_HOMEWORLD_HELLO_1);
+ RespFunc = (RESPONSE_FUNC)ArilouHome;
+ SET_GAME_STATE (ARILOU_HOME_VISITS, 1);
+ }
+ Response (confused_by_hello, RespFunc);
+ Response (happy_by_hello, RespFunc);
+ Response (miffed_by_hello, RespFunc);
+ SET_GAME_STATE (MET_ARILOU, 1);
+ return;
+ }
+
+ Manner = GET_GAME_STATE (ARILOU_MANNER);
+ if (Manner == 2)
+ {
+ NumVisits = GET_GAME_STATE (ARILOU_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_GOODBYE_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_GOODBYE_2);
+ break;
+ case 2:
+ NPCPhrase (HOSTILE_GOODBYE_3);
+ break;
+ case 3:
+ NPCPhrase (HOSTILE_GOODBYE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ARILOU_VISITS, NumVisits);
+
+ setSegue (Segue_peace);
+ }
+ else if (Manner == 1)
+ {
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) > 1)
+ {
+ NPCPhrase (INIT_ANGRY_HWLD_HELLO);
+ SET_GAME_STATE (ARILOU_HOME_VISITS, 1);
+
+ AngryHomeArilou ((RESPONSE_REF)0);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (ARILOU_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ANGRY_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ANGRY_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ARILOU_VISITS, NumVisits);
+
+ AngrySpaceArilou ((RESPONSE_REF)0);
+ }
+ }
+ else
+ {
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ NumVisits = GET_GAME_STATE (ARILOU_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ARILOU_VISITS, NumVisits);
+
+ FriendlySpaceArilou ((RESPONSE_REF)0);
+ }
+ else
+ {
+ if (!GET_GAME_STATE (PORTAL_SPAWNER)
+ && GET_GAME_STATE (KNOW_ARILOU_WANT_WRECK))
+ {
+ NumVisits = GET_GAME_STATE (NO_PORTAL_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GOT_PART_YET_1);
+ break;
+ case 1:
+ NPCPhrase (GOT_PART_YET_1);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (NO_PORTAL_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (ARILOU_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (FRDLY_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (FRDLY_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (FRDLY_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (FRDLY_HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ARILOU_HOME_VISITS, NumVisits);
+ }
+
+ ArilouHome ((RESPONSE_REF)0);
+ }
+ }
+}
+
+static COUNT
+uninit_arilou (void)
+{
+ return (0);
+}
+
+static void
+post_arilou_enc (void)
+{
+ BYTE Manner;
+
+ if (getSegue () == Segue_hostile
+ && (Manner = GET_GAME_STATE (ARILOU_MANNER)) != 2)
+ {
+ SET_GAME_STATE (ARILOU_MANNER, 1);
+ if (Manner != 1)
+ {
+ SET_GAME_STATE (ARILOU_VISITS, 0);
+ SET_GAME_STATE (ARILOU_HOME_VISITS, 0);
+ }
+ }
+
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) > 1
+ && GET_GAME_STATE (ARILOU_HOME_VISITS) <= 1)
+ {
+ SET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES, 1);
+ SET_GAME_STATE (UMGAH_VISITS, 0);
+ SET_GAME_STATE (UMGAH_HOME_VISITS, 0);
+
+ if (GET_GAME_STATE (ARILOU_MANNER) < 2)
+ {
+ SET_GAME_STATE (ARILOU_MANNER, 3);
+ }
+ }
+}
+
+LOCDATA*
+init_arilou_comm (void)
+{
+ LOCDATA *retval;
+
+ arilou_desc.init_encounter_func = Intro;
+ arilou_desc.post_encounter_func = post_arilou_enc;
+ arilou_desc.uninit_encounter_func = uninit_arilou;
+
+ arilou_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ arilou_desc.AlienTextBaseline.y = 0;
+ arilou_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) > 1
+ || GET_GAME_STATE (ARILOU_MANNER) == 3
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &arilou_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/arilou/resinst.h b/src/uqm/comm/arilou/resinst.h
new file mode 100644
index 0000000..991ea37
--- /dev/null
+++ b/src/uqm/comm/arilou/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ARILOU_COLOR_MAP "comm.arilou.colortable"
+#define ARILOU_CONVERSATION_PHRASES "comm.arilou.dialogue"
+#define ARILOU_FONT "comm.arilou.font"
+#define ARILOU_MUSIC "comm.arilou.music"
+#define ARILOU_PMAP_ANIM "comm.arilou.graphics"
diff --git a/src/uqm/comm/arilou/strings.h b/src/uqm/comm/arilou/strings.h
new file mode 100644
index 0000000..1f80468
--- /dev/null
+++ b/src/uqm/comm/arilou/strings.h
@@ -0,0 +1,123 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ARILOU_STRINGS_H
+#define ARILOU_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ INIT_HELLO,
+ confused_by_hello,
+ CONFUSED_RESPONSE,
+ happy_by_hello,
+ HAPPY_RESPONSE,
+ miffed_by_hello,
+ MIFFED_RESPONSE,
+ FRIENDLY_SPACE_HELLO_1,
+ FRIENDLY_SPACE_HELLO_2,
+ FRIENDLY_SPACE_HELLO_3,
+ FRIENDLY_SPACE_HELLO_4,
+ FRDLY_HOMEWORLD_HELLO_1,
+ FRDLY_HOMEWORLD_HELLO_2,
+ FRDLY_HOMEWORLD_HELLO_3,
+ FRDLY_HOMEWORLD_HELLO_4,
+ whats_up_1,
+ whats_up_2,
+ GENERAL_INFO_1,
+ GENERAL_INFO_2,
+ GENERAL_INFO_3,
+ GENERAL_INFO_4,
+ why_you_here,
+ LEARN_THINGS,
+ what_things,
+ THESE_THINGS,
+ why_do_it,
+ DO_IT_BECAUSE,
+ give_me_info_1,
+ ARILOU_HINTS_1,
+ give_me_info_2,
+ ARILOU_HINTS_2,
+ ARILOU_HINTS_3,
+ ARILOU_HINTS_4,
+ bye_friendly_space,
+ GOODBYE_FRIENDLY_SPACE,
+ GOT_PART_YET_1,
+ GOT_PART_YET_2,
+ INIT_ANGRY_HWLD_HELLO,
+ invaders_from_mars,
+ HAD_OUR_REASONS,
+ bug_eyed_fruitcakes,
+ WE_NEVER_FRIENDS,
+ ok_lets_be_friends,
+ NO_ALLY_BUT_MUCH_GIVE,
+ why_should_i_trust,
+ TRUST_BECAUSE,
+ what_about_interference,
+ INTERFERENCE_NECESSARY,
+ i_just_like_to_leave,
+ SORRY_NO_LEAVE,
+ what_about_war,
+ ABOUT_WAR,
+ what_about_urquan,
+ ABOUT_URQUAN,
+ best_if_i_killed_you,
+ WICKED_HUMAN,
+ what_did_on_earth,
+ DID_THIS,
+ why_did_this,
+ IDF_PARASITES,
+ tell_more,
+ NOT_NOW,
+ umgah_acting_weird,
+ learned_about_umgah,
+ WELL_GO_CHECK,
+ NO_NEWS_YET,
+ UMGAH_UNDER_COMPULSION,
+ what_do_now,
+ GO_FIND_OUT,
+ tell_arilou_about_tpet,
+ BAD_NEWS_ABOUT_TPET,
+ what_do_about_tpet,
+ DANGEROUS_BUT_USEFUL,
+ what_give_me,
+ ABOUT_PORTAL,
+ what_about_tpet,
+ ABOUT_TPET,
+ about_portal_again,
+ PORTAL_AGAIN,
+ got_it,
+ CLEVER_HUMAN,
+ GIVE_PORTAL,
+ bye_friendly_homeworld,
+ GOODBYE_FRDLY_HOMEWORLD,
+ HOSTILE_GOODBYE_1,
+ HOSTILE_GOODBYE_2,
+ HOSTILE_GOODBYE_3,
+ HOSTILE_GOODBYE_4,
+ ANGRY_SPACE_HELLO_1,
+ ANGRY_SPACE_HELLO_2,
+ lets_fight,
+ NO_FIGHT,
+ im_sorry,
+ APOLOGIZE_AT_HOMEWORLD,
+ bye_angry_space,
+ GOODBYE_ANGRY_SPACE,
+ OUT_TAKES,
+};
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/blackur/Makeinfo b/src/uqm/comm/blackur/Makeinfo
new file mode 100644
index 0000000..1927a56
--- /dev/null
+++ b/src/uqm/comm/blackur/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="blackurc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/blackur/blackurc.c b/src/uqm/comm/blackur/blackurc.c
new file mode 100644
index 0000000..1b47a7b
--- /dev/null
+++ b/src/uqm/comm/blackur/blackurc.c
@@ -0,0 +1,567 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+static LOCDATA blackurq_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ BLACKURQ_PMAP_ANIM, /* AlienFrame */
+ BLACKURQ_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ BLACKURQ_COLOR_MAP, /* AlienColorMap */
+ BLACKURQ_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ BLACKURQ_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 8, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 7, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 13, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 20, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 23, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 26, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 29, /* StartIndex */
+ 4, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND / 10, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 33, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 38, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 1, /* StartIndex */
+ 2, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 6, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 2, /* StartIndex */
+ 5, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+CombatIsInevitable (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, bye))
+ {
+ if (GET_GAME_STATE (KOHR_AH_BYES) == 0)
+ NPCPhrase (GOODBYE_AND_DIE);
+ else
+ NPCPhrase (DIE_HUMAN /* GOODBYE_AND_DIE_2 */);
+
+ SET_GAME_STATE (KOHR_AH_BYES, 1);
+ }
+ else if (PLAYER_SAID (R, guess_thats_all))
+ NPCPhrase (THEN_DIE);
+ else if (PLAYER_SAID (R, what_are_you_hovering_over))
+ {
+ NPCPhrase (BONE_PILE);
+
+ SET_GAME_STATE (KOHR_AH_INFO, 1);
+ }
+ else if (PLAYER_SAID (R, you_sure_are_creepy))
+ {
+ NPCPhrase (YES_CREEPY);
+
+ SET_GAME_STATE (KOHR_AH_INFO, 2);
+ }
+ else if (PLAYER_SAID (R, stop_that_gross_blinking))
+ {
+ NPCPhrase (DIE_HUMAN);
+
+ SET_GAME_STATE (KOHR_AH_INFO, 3);
+ }
+ else if (PLAYER_SAID (R, threat_1)
+ || PLAYER_SAID (R, threat_2)
+ || PLAYER_SAID (R, threat_3)
+ || PLAYER_SAID (R, threat_4))
+ {
+ NumVisits = GET_GAME_STATE (KOHR_AH_REASONS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (RESISTANCE_IS_USELESS_1);
+ break;
+ case 1:
+ NPCPhrase (RESISTANCE_IS_USELESS_2);
+ break;
+ case 2:
+ NPCPhrase (RESISTANCE_IS_USELESS_3);
+ break;
+ case 3:
+ NPCPhrase (RESISTANCE_IS_USELESS_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (KOHR_AH_REASONS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, plead_1)
+ || PLAYER_SAID (R, plead_2)
+ || PLAYER_SAID (R, plead_3)
+ || PLAYER_SAID (R, plead_4))
+ {
+ NumVisits = GET_GAME_STATE (KOHR_AH_PLEAD);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (PLEADING_IS_USELESS_1);
+ break;
+ case 1:
+ NPCPhrase (PLEADING_IS_USELESS_2);
+ break;
+ case 2:
+ // This response disabled due to lack of a speech file.
+ // NPCPhrase (PLEADING_IS_USELESS_3);
+ // break;
+ case 3:
+ NPCPhrase (PLEADING_IS_USELESS_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (KOHR_AH_PLEAD, NumVisits);
+ }
+ else if (PLAYER_SAID (R, why_kill_all_1)
+ || PLAYER_SAID (R, why_kill_all_2)
+ || PLAYER_SAID (R, why_kill_all_3)
+ || PLAYER_SAID (R, why_kill_all_4))
+ {
+ NumVisits = GET_GAME_STATE (KOHR_AH_REASONS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (KILL_BECAUSE_1);
+ break;
+ case 1:
+ NPCPhrase (KILL_BECAUSE_2);
+ break;
+ case 2:
+ NPCPhrase (KILL_BECAUSE_3);
+ break;
+ case 3:
+ NPCPhrase (KILL_BECAUSE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (KOHR_AH_REASONS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, please_dont_kill_1)
+ || PLAYER_SAID (R, please_dont_kill_2)
+ || PLAYER_SAID (R, please_dont_kill_3)
+ || PLAYER_SAID (R, please_dont_kill_4))
+ {
+ NumVisits = GET_GAME_STATE (KOHR_AH_PLEAD);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (WILL_KILL_1);
+ break;
+ case 1:
+ NPCPhrase (WILL_KILL_2);
+ break;
+ case 2:
+ NPCPhrase (WILL_KILL_3);
+ break;
+ case 3:
+ NPCPhrase (WILL_KILL_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (KOHR_AH_PLEAD, NumVisits);
+ }
+ else if (PLAYER_SAID (R, bye_frenzy_1)
+ || PLAYER_SAID (R, bye_frenzy_2)
+ || PLAYER_SAID (R, bye_frenzy_3)
+ || PLAYER_SAID (R, bye_frenzy_4))
+ {
+ NumVisits = GET_GAME_STATE (KOHR_AH_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GOODBYE_AND_DIE_FRENZY_1);
+ break;
+ case 1:
+ NPCPhrase (GOODBYE_AND_DIE_FRENZY_2);
+ break;
+ case 2:
+ NPCPhrase (GOODBYE_AND_DIE_FRENZY_3);
+ break;
+ case 3:
+ NPCPhrase (GOODBYE_AND_DIE_FRENZY_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (KOHR_AH_INFO, NumVisits);
+ }
+}
+
+static void
+Frenzy (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ switch (GET_GAME_STATE (KOHR_AH_REASONS))
+ {
+ case 0:
+ Response (why_kill_all_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (why_kill_all_2, CombatIsInevitable);
+ break;
+ case 2:
+ Response (why_kill_all_3, CombatIsInevitable);
+ break;
+ case 3:
+ Response (why_kill_all_4, CombatIsInevitable);
+ break;
+ }
+ switch (GET_GAME_STATE (KOHR_AH_PLEAD))
+ {
+ case 0:
+ Response (please_dont_kill_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (please_dont_kill_2, CombatIsInevitable);
+ break;
+ case 2:
+ Response (please_dont_kill_3, CombatIsInevitable);
+ break;
+ case 3:
+ Response (please_dont_kill_4, CombatIsInevitable);
+ break;
+ }
+ switch (GET_GAME_STATE (KOHR_AH_INFO))
+ {
+ case 0:
+ Response (bye_frenzy_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (bye_frenzy_2, CombatIsInevitable);
+ break;
+ case 2:
+ Response (bye_frenzy_3, CombatIsInevitable);
+ break;
+ case 3:
+ Response (bye_frenzy_4, CombatIsInevitable);
+ break;
+ }
+}
+
+static void
+KohrAhStory (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, key_phrase))
+ {
+ NPCPhrase (RESPONSE_TO_KEY_PHRASE);
+
+ SET_GAME_STATE (KNOW_KOHR_AH_STORY, 2);
+ }
+ else if (PLAYER_SAID (R, why_do_you_destroy))
+ {
+ NPCPhrase (WE_WERE_SLAVES);
+
+ DISABLE_PHRASE (why_do_you_destroy);
+ }
+ else if (PLAYER_SAID (R, relationship_with_urquan))
+ {
+ NPCPhrase (WE_ARE_URQUAN_TOO);
+
+ DISABLE_PHRASE (relationship_with_urquan);
+ }
+ else if (PLAYER_SAID (R, what_about_culture))
+ {
+ NPCPhrase (BONE_GARDENS);
+
+ DISABLE_PHRASE (what_about_culture);
+ }
+ else if (PLAYER_SAID (R, how_leave_me_alone))
+ {
+ NPCPhrase (YOU_DIE);
+
+ DISABLE_PHRASE (how_leave_me_alone);
+ }
+
+ if (PHRASE_ENABLED (why_do_you_destroy))
+ Response (why_do_you_destroy, KohrAhStory);
+ if (PHRASE_ENABLED (relationship_with_urquan))
+ Response (relationship_with_urquan, KohrAhStory);
+ if (PHRASE_ENABLED (what_about_culture))
+ Response (what_about_culture, KohrAhStory);
+ if (PHRASE_ENABLED (how_leave_me_alone))
+ Response (how_leave_me_alone, KohrAhStory);
+ Response (guess_thats_all, CombatIsInevitable);
+}
+
+static void
+DieHuman (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ switch (GET_GAME_STATE (KOHR_AH_REASONS))
+ {
+ case 0:
+ Response (threat_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (threat_2, CombatIsInevitable);
+ break;
+ case 2:
+ Response (threat_3, CombatIsInevitable);
+ break;
+ case 3:
+ Response (threat_4, CombatIsInevitable);
+ break;
+ }
+ if (GET_GAME_STATE (KNOW_KOHR_AH_STORY) == 1)
+ {
+ Response (key_phrase, KohrAhStory);
+ }
+ switch (GET_GAME_STATE (KOHR_AH_INFO))
+ {
+ case 0:
+ Response (what_are_you_hovering_over, CombatIsInevitable);
+ break;
+ case 1:
+ Response (you_sure_are_creepy, CombatIsInevitable);
+ break;
+ case 2:
+ Response (stop_that_gross_blinking, CombatIsInevitable);
+ break;
+ }
+ switch (GET_GAME_STATE (KOHR_AH_PLEAD))
+ {
+ case 0:
+ Response (plead_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (plead_2, CombatIsInevitable);
+ break;
+ case 2:
+ // This response disabled due to lack of a speech file.
+ // Response (plead_3, CombatIsInevitable);
+ // break;
+ case 3:
+ Response (plead_4, CombatIsInevitable);
+ break;
+ }
+ Response (bye, CombatIsInevitable);
+}
+
+static void
+Intro (void)
+{
+ DWORD GrpOffs;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (GET_GAME_STATE (KOHR_AH_KILLED_ALL))
+ {
+ NPCPhrase (GAME_OVER_DUDE);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (!GET_GAME_STATE (KOHR_AH_SENSES_EVIL)
+ && GET_GAME_STATE (TALKING_PET_ON_SHIP))
+ {
+ NPCPhrase (SENSE_EVIL);
+ SET_GAME_STATE (KOHR_AH_SENSES_EVIL, 1);
+ }
+
+ GrpOffs = GET_GAME_STATE_32 (SAMATRA_GRPOFFS0);
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && GLOBAL (BattleGroupRef)
+ && GLOBAL (BattleGroupRef) == GrpOffs)
+ {
+ NPCPhrase (HELLO_SAMATRA);
+
+ SET_GAME_STATE (AWARE_OF_SAMATRA, 1);
+ setSegue (Segue_hostile);
+ }
+ else
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (KOHR_AH_VISITS);
+ if (GET_GAME_STATE (KOHR_AH_FRENZY))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (WE_KILL_ALL_1);
+ break;
+ case 1:
+ NPCPhrase (WE_KILL_ALL_2);
+ break;
+ case 2:
+ NPCPhrase (WE_KILL_ALL_3);
+ break;
+ case 3:
+ NPCPhrase (WE_KILL_ALL_4);
+ --NumVisits;
+ break;
+ }
+
+ Frenzy ((RESPONSE_REF)0);
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_AND_DIE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_AND_DIE_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_AND_DIE_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_AND_DIE_4);
+ --NumVisits;
+ break;
+ }
+
+ DieHuman ((RESPONSE_REF)0);
+ }
+ SET_GAME_STATE (KOHR_AH_VISITS, NumVisits);
+ }
+}
+
+static COUNT
+uninit_blackurq (void)
+{
+ return (0);
+}
+
+static void
+post_blackurq_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_blackurq_comm (void)
+{
+ LOCDATA *retval;
+
+ blackurq_desc.init_encounter_func = Intro;
+ blackurq_desc.post_encounter_func = post_blackurq_enc;
+ blackurq_desc.uninit_encounter_func = uninit_blackurq;
+
+ blackurq_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ blackurq_desc.AlienTextBaseline.y = 0;
+ blackurq_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (!GET_GAME_STATE (KOHR_AH_KILLED_ALL)
+ && LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
+ {
+ setSegue (Segue_hostile);
+ }
+ else
+ {
+ setSegue (Segue_peace);
+ }
+ retval = &blackurq_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/blackur/resinst.h b/src/uqm/comm/blackur/resinst.h
new file mode 100644
index 0000000..d0c2313
--- /dev/null
+++ b/src/uqm/comm/blackur/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define BLACKURQ_COLOR_MAP "comm.kohrah.colortable"
+#define BLACKURQ_CONVERSATION_PHRASES "comm.kohrah.dialogue"
+#define BLACKURQ_FONT "comm.kohrah.font"
+#define BLACKURQ_MUSIC "comm.kohrah.music"
+#define BLACKURQ_PMAP_ANIM "comm.kohrah.graphics"
diff --git a/src/uqm/comm/blackur/strings.h b/src/uqm/comm/blackur/strings.h
new file mode 100644
index 0000000..06a9351
--- /dev/null
+++ b/src/uqm/comm/blackur/strings.h
@@ -0,0 +1,103 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef BLACKUR_STRINGS_H
+#define BLACKUR_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ SENSE_EVIL,
+ HELLO_AND_DIE_1,
+ HELLO_AND_DIE_2,
+ HELLO_AND_DIE_3,
+ HELLO_AND_DIE_4,
+ HELLO_AND_DIE_5,
+ HELLO_AND_DIE_6,
+ HELLO_AND_DIE_7,
+ HELLO_AND_DIE_8,
+ HELLO_SAMATRA,
+ WE_KILL_ALL_1,
+ WE_KILL_ALL_2,
+ WE_KILL_ALL_3,
+ WE_KILL_ALL_4,
+ why_kill_all_1,
+ why_kill_all_2,
+ why_kill_all_3,
+ why_kill_all_4,
+ KILL_BECAUSE_1,
+ KILL_BECAUSE_2,
+ KILL_BECAUSE_3,
+ KILL_BECAUSE_4,
+ please_dont_kill_1,
+ WILL_KILL_1,
+ please_dont_kill_2,
+ WILL_KILL_2,
+ please_dont_kill_3,
+ WILL_KILL_3,
+ please_dont_kill_4,
+ WILL_KILL_4,
+ bye_frenzy_1,
+ bye_frenzy_2,
+ bye_frenzy_3,
+ bye_frenzy_4,
+ GOODBYE_AND_DIE_FRENZY_1,
+ GOODBYE_AND_DIE_FRENZY_2,
+ GOODBYE_AND_DIE_FRENZY_3,
+ GOODBYE_AND_DIE_FRENZY_4,
+ threat_1,
+ RESISTANCE_IS_USELESS_1,
+ threat_2,
+ RESISTANCE_IS_USELESS_2,
+ threat_3,
+ RESISTANCE_IS_USELESS_3,
+ threat_4,
+ RESISTANCE_IS_USELESS_4,
+ key_phrase,
+ RESPONSE_TO_KEY_PHRASE,
+ why_do_you_destroy,
+ WE_WERE_SLAVES,
+ relationship_with_urquan,
+ WE_ARE_URQUAN_TOO,
+ what_about_culture,
+ BONE_GARDENS,
+ how_leave_me_alone,
+ YOU_DIE,
+ guess_thats_all,
+ THEN_DIE,
+ what_are_you_hovering_over,
+ BONE_PILE,
+ you_sure_are_creepy,
+ YES_CREEPY,
+ stop_that_gross_blinking,
+ DIE_HUMAN,
+ plead_1,
+ PLEADING_IS_USELESS_1,
+ plead_2,
+ PLEADING_IS_USELESS_2,
+ plead_3,
+ PLEADING_IS_USELESS_3,
+ plead_4,
+ PLEADING_IS_USELESS_4,
+ bye,
+ GOODBYE_AND_DIE,
+ GAME_OVER_DUDE,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/chmmr/Makeinfo b/src/uqm/comm/chmmr/Makeinfo
new file mode 100644
index 0000000..f01e2b8
--- /dev/null
+++ b/src/uqm/comm/chmmr/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="chmmrc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/chmmr/chmmrc.c b/src/uqm/comm/chmmr/chmmrc.c
new file mode 100644
index 0000000..1b35fc0
--- /dev/null
+++ b/src/uqm/comm/chmmr/chmmrc.c
@@ -0,0 +1,641 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/hyper.h"
+ // for SOL_X/SOL_Y
+
+
+static LOCDATA chmmr_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ CHMMR_PMAP_ANIM, /* AlienFrame */
+ CHMMR_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ CHMMR_COLOR_MAP, /* AlienColorMap */
+ CHMMR_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ CHMMR_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 6, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 12, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 17, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 27, /* StartIndex */
+ 20, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 47, /* StartIndex */
+ 14, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 61, /* StartIndex */
+ 24, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 11, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 60, 0, /* FrameRate */
+ ONE_SECOND / 60, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (R, bye))
+ NPCPhrase (GOODBYE);
+ else if (PLAYER_SAID (R, bye_shielded))
+ NPCPhrase (GOODBYE_SHIELDED);
+ else if (PLAYER_SAID (R, bye_after_bomb))
+ NPCPhrase (GOODBYE_AFTER_BOMB);
+ else if (PLAYER_SAID (R, proceed))
+ {
+ int i;
+
+ NPCPhrase (TAKE_2_WEEKS);
+
+ SetRaceAllied (CHMMR_SHIP, TRUE);
+
+ SET_GAME_STATE (CHMMR_HOME_VISITS, 0);
+ SET_GAME_STATE (CHMMR_STACK, 0);
+ SET_GAME_STATE (CHMMR_BOMB_STATE, 2);
+ SET_GAME_STATE (UTWIG_BOMB_ON_SHIP, 0);
+ GLOBAL_SIS (ResUnits) = 1000000L;
+ GLOBAL_SIS (NumLanders) = 0;
+ GLOBAL (ModuleCost[PLANET_LANDER]) = 0;
+
+#define EARTH_INDEX 2 /* earth is 3rd planet --> 3 - 1 = 2 */
+/* Magic numbers for Earth */
+#define EARTH_OUTER_X (-725)
+#define EARTH_OUTER_Y (597)
+#define EARTH_INNER_X (121)
+#define EARTH_INNER_Y (113)
+/* Magic numbers for Earth Starbase */
+#define STARBASE_INNER_X (86)
+#define STARBASE_INNER_Y (113)
+
+ /* transport player to Earth */
+ GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX (SOL_X);
+ GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY (SOL_Y);
+ GLOBAL (ShipFacing) = 1;
+ /* At Earth or at Starbase */
+ GLOBAL (ip_planet) = EARTH_INDEX + 1;
+ GLOBAL (in_orbit) = 0;
+ /* XXX : this should be unhardcoded eventually */
+ GLOBAL (ip_location.x) = EARTH_OUTER_X;
+ GLOBAL (ip_location.y) = EARTH_OUTER_Y;
+
+ if (GET_GAME_STATE (STARBASE_AVAILABLE))
+ { /* Normal game mode - you are transported to Starbase */
+ GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE;
+ GLOBAL_SIS (CrewEnlisted) = 0;
+ GLOBAL_SIS (TotalElementMass) = 0;
+ GLOBAL (ModuleCost[STORAGE_BAY]) = 0; /* disable Storage Bay */
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i)
+ GLOBAL_SIS (ElementAmounts[i]) = 0;
+ for (i = NUM_BOMB_MODULES; i < NUM_MODULE_SLOTS; ++i)
+ GLOBAL_SIS (ModuleSlots[i]) = EMPTY_SLOT + 2;
+
+ /* XXX : this should be unhardcoded eventually */
+ /* transport to Starbase */
+ GLOBAL (ShipStamp.origin.x) = STARBASE_INNER_X - SAFE_X;
+ GLOBAL (ShipStamp.origin.y) = STARBASE_INNER_Y - SAFE_Y;
+ }
+ else
+ { /* 'Beating Game Differently' mode - never visited Starbase,
+ * so you are transported to Earth */
+ /* compress the layout -- move all to front */
+ for (i = NUM_MODULE_SLOTS - 1; i > 0; --i)
+ {
+ int m;
+
+ /* find next unused slot */
+ for (; i > 0
+ && GLOBAL_SIS (ModuleSlots[i]) != EMPTY_SLOT + 2;
+ --i)
+ ;
+ if (i == 0)
+ break;
+ /* find next module to move */
+ for (m = i - 1; m >= 0
+ && GLOBAL_SIS (ModuleSlots[m]) == EMPTY_SLOT + 2;
+ --m)
+ ;
+ if (m < 0)
+ break;
+
+ /* move the module */
+ GLOBAL_SIS (ModuleSlots[i]) = GLOBAL_SIS (ModuleSlots[m]);
+ GLOBAL_SIS (ModuleSlots[m]) = EMPTY_SLOT + 2;
+ }
+
+ /* XXX : this should be unhardcoded eventually */
+ /* transport to Earth itself */
+ GLOBAL (ShipStamp.origin.x) = EARTH_INNER_X - SAFE_X;
+ GLOBAL (ShipStamp.origin.y) = EARTH_INNER_Y - SAFE_Y;
+ }
+
+ /* install Chmmr-supplied modules */
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ GLOBAL_SIS (DriveSlots[i]) = FUSION_THRUSTER;
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ GLOBAL_SIS (JetSlots[i]) = TURNING_JETS;
+ GLOBAL_SIS (ModuleSlots[0]) = BOMB_MODULE_4;
+ GLOBAL_SIS (ModuleSlots[1]) = BOMB_MODULE_5;
+ GLOBAL_SIS (ModuleSlots[2]) = BOMB_MODULE_3;
+ GLOBAL_SIS (ModuleSlots[3]) = BOMB_MODULE_1;
+ GLOBAL_SIS (ModuleSlots[4]) = BOMB_MODULE_0;
+ GLOBAL_SIS (ModuleSlots[5]) = BOMB_MODULE_1;
+ GLOBAL_SIS (ModuleSlots[6]) = BOMB_MODULE_3;
+ GLOBAL_SIS (ModuleSlots[7]) = BOMB_MODULE_4;
+ GLOBAL_SIS (ModuleSlots[8]) = BOMB_MODULE_5;
+ GLOBAL_SIS (ModuleSlots[9]) = BOMB_MODULE_2;
+ }
+}
+
+static void
+NotReady (RESPONSE_REF R)
+{
+ if (R == 0)
+ NPCPhrase (RETURN_WHEN_READY);
+ else if (PLAYER_SAID (R, further_assistance))
+ {
+ NPCPhrase (NO_FURTHER_ASSISTANCE);
+
+ DISABLE_PHRASE (further_assistance);
+ }
+ else if (PLAYER_SAID (R, tech_help))
+ {
+ NPCPhrase (USE_OUR_SHIPS_BEFORE);
+
+ SetRaceAllied (CHMMR_SHIP, TRUE);
+ }
+ else if (PLAYER_SAID (R, where_weapon))
+ {
+ NPCPhrase (PRECURSOR_WEAPON);
+
+ DISABLE_PHRASE (where_weapon);
+ }
+ else if (PLAYER_SAID (R, where_distraction))
+ {
+ NPCPhrase (PSYCHIC_WEAPONRY);
+
+ DISABLE_PHRASE (where_distraction);
+ }
+
+ if (CheckAlliance (CHMMR_SHIP) != GOOD_GUY)
+ Response (tech_help, NotReady);
+ else if (PHRASE_ENABLED (further_assistance))
+ Response (further_assistance, NotReady);
+ if (PHRASE_ENABLED (where_weapon) && !GET_GAME_STATE (UTWIG_BOMB_ON_SHIP))
+ Response (where_weapon, NotReady);
+ if (PHRASE_ENABLED (where_distraction) && !GET_GAME_STATE (TALKING_PET_ON_SHIP))
+ Response (where_distraction, NotReady);
+ Response (bye, ExitConversation);
+}
+
+static void
+ImproveBomb (RESPONSE_REF R)
+{
+ if (R == 0)
+ NPCPhrase (WE_WILL_IMPROVE_BOMB);
+ else if (PLAYER_SAID (R, what_now))
+ {
+ NPCPhrase (MODIFY_VESSEL);
+
+ DISABLE_PHRASE (what_now);
+ }
+ else if (PLAYER_SAID (R, wont_hurt_my_ship))
+ {
+ NPCPhrase (WILL_DESTROY_IT);
+
+ DISABLE_PHRASE (wont_hurt_my_ship);
+ }
+ else if (PLAYER_SAID (R, bummer_about_my_ship))
+ {
+ NPCPhrase (DEAD_SILENCE);
+
+ DISABLE_PHRASE (bummer_about_my_ship);
+ }
+ else if (PLAYER_SAID (R, other_assistance))
+ {
+ NPCPhrase (USE_OUR_SHIPS_AFTER);
+
+ SetRaceAllied (CHMMR_SHIP, TRUE);
+ }
+
+ if (PHRASE_ENABLED (what_now))
+ Response (what_now, ImproveBomb);
+ else if (PHRASE_ENABLED (wont_hurt_my_ship))
+ Response (wont_hurt_my_ship, ImproveBomb);
+ else if (PHRASE_ENABLED (bummer_about_my_ship))
+ Response (bummer_about_my_ship, ImproveBomb);
+ if (CheckAlliance (CHMMR_SHIP) != GOOD_GUY)
+ Response (other_assistance, ImproveBomb);
+ Response (proceed, ExitConversation);
+}
+
+static void
+ChmmrFree (RESPONSE_REF R)
+{
+ if (R == 0
+ || PLAYER_SAID (R, i_am_captain0)
+ || PLAYER_SAID (R, i_am_savior)
+ || PLAYER_SAID (R, i_am_silly))
+ {
+ NPCPhrase (WHY_HAVE_YOU_FREED_US);
+ AlienTalkSegue ((COUNT)~0);
+ SET_GAME_STATE (CHMMR_EMERGING, 0);
+
+ Response (serious_1, ChmmrFree);
+ Response (serious_2, ChmmrFree);
+ Response (silly, ChmmrFree);
+ }
+ else
+ {
+ NPCPhrase (WILL_HELP_ANALYZE_LOGS);
+
+ if (GET_GAME_STATE (AWARE_OF_SAMATRA))
+ NPCPhrase (YOU_KNOW_SAMATRA);
+ else
+ {
+ NPCPhrase (DONT_KNOW_ABOUT_SAMATRA);
+
+ SET_GAME_STATE (AWARE_OF_SAMATRA, 1);
+ }
+
+ if (GET_GAME_STATE (TALKING_PET_ON_SHIP))
+ NPCPhrase (HAVE_TALKING_PET);
+ else
+ NPCPhrase (NEED_DISTRACTION);
+
+ if (GET_GAME_STATE (UTWIG_BOMB_ON_SHIP))
+ NPCPhrase (HAVE_BOMB);
+ else
+ NPCPhrase (NEED_WEAPON);
+
+ if (!GET_GAME_STATE (TALKING_PET_ON_SHIP)
+ || !GET_GAME_STATE (UTWIG_BOMB_ON_SHIP))
+ NotReady ((RESPONSE_REF)0);
+ else
+ ImproveBomb ((RESPONSE_REF)0);
+ }
+}
+
+static void ChmmrShielded (RESPONSE_REF R);
+
+static void
+ChmmrAdvice (RESPONSE_REF R)
+{
+ BYTE AdviceLeft;
+
+ if (PLAYER_SAID (R, need_advice))
+ NPCPhrase (WHAT_ADVICE);
+ else if (PLAYER_SAID (R, how_defeat_urquan))
+ {
+ NPCPhrase (DEFEAT_LIKE_SO);
+
+ SET_GAME_STATE (CHMMR_BOMB_STATE, 1);
+ DISABLE_PHRASE (how_defeat_urquan);
+ }
+ else if (PLAYER_SAID (R, what_about_tpet))
+ {
+ NPCPhrase (SCARY_BUT_USEFUL);
+
+ DISABLE_PHRASE (what_about_tpet);
+ }
+ else if (PLAYER_SAID (R, what_about_bomb))
+ {
+ NPCPhrase (ABOUT_BOMB);
+
+ DISABLE_PHRASE (what_about_bomb);
+ }
+ else if (PLAYER_SAID (R, what_about_sun_device))
+ {
+ NPCPhrase (ABOUT_SUN_DEVICE);
+
+ DISABLE_PHRASE (what_about_sun_device);
+ }
+ else if (PLAYER_SAID (R, what_about_samatra))
+ {
+ NPCPhrase (ABOUT_SAMATRA);
+
+ DISABLE_PHRASE (what_about_samatra);
+ }
+
+ AdviceLeft = 0;
+ if (PHRASE_ENABLED (how_defeat_urquan))
+ {
+ Response (how_defeat_urquan, ChmmrAdvice);
+ AdviceLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_tpet) && GET_GAME_STATE (TALKING_PET_ON_SHIP))
+ {
+ Response (what_about_tpet, ChmmrAdvice);
+ AdviceLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_bomb) && GET_GAME_STATE (UTWIG_BOMB_ON_SHIP))
+ {
+ Response (what_about_bomb, ChmmrAdvice);
+ AdviceLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_sun_device) && GET_GAME_STATE (SUN_DEVICE_ON_SHIP))
+ {
+ Response (what_about_sun_device, ChmmrAdvice);
+ AdviceLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_samatra) && GET_GAME_STATE (AWARE_OF_SAMATRA))
+ {
+ Response (what_about_samatra, ChmmrAdvice);
+ AdviceLeft = TRUE;
+ }
+ Response (enough_advice, ChmmrShielded);
+
+ if (!AdviceLeft)
+ DISABLE_PHRASE (need_advice);
+}
+
+static void
+ChmmrShielded (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, find_out_whats_up))
+ {
+ NPCPhrase (HYBRID_PROCESS);
+
+ DISABLE_PHRASE (find_out_whats_up);
+ }
+ else if (PLAYER_SAID (R, need_help))
+ {
+ NPCPhrase (CANT_HELP);
+
+ SET_GAME_STATE (CHMMR_STACK, 1);
+ }
+ else if (PLAYER_SAID (R, why_no_help))
+ {
+ NPCPhrase (LONG_TIME);
+
+ SET_GAME_STATE (CHMMR_STACK, 2);
+ }
+ else if (PLAYER_SAID (R, what_if_more_energy))
+ {
+ NPCPhrase (DANGER_TO_US);
+
+ SET_GAME_STATE (CHMMR_STACK, 3);
+ }
+ else if (PLAYER_SAID (R, enough_advice))
+ NPCPhrase (OK_ENOUGH_ADVICE);
+
+ switch (GET_GAME_STATE (CHMMR_STACK))
+ {
+ case 0:
+ Response (need_help, ChmmrShielded);
+ break;
+ case 1:
+ Response (why_no_help, ChmmrShielded);
+ break;
+ case 2:
+ Response (what_if_more_energy, ChmmrShielded);
+ break;
+ }
+ if (PHRASE_ENABLED (find_out_whats_up))
+ Response (find_out_whats_up, ChmmrShielded);
+ if (PHRASE_ENABLED (need_advice))
+ {
+ Response (need_advice, ChmmrAdvice);
+ }
+ Response (bye_shielded, ExitConversation);
+}
+
+static void
+AfterBomb (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_up_after_bomb))
+ {
+ if (GET_GAME_STATE (CHMMR_STACK))
+ NPCPhrase (GENERAL_INFO_AFTER_BOMB_2);
+ else
+ {
+ NPCPhrase (GENERAL_INFO_AFTER_BOMB_1);
+
+ SET_GAME_STATE (CHMMR_STACK, 1);
+ }
+
+ DISABLE_PHRASE (whats_up_after_bomb);
+ }
+ else if (PLAYER_SAID (R, what_do_after_bomb))
+ {
+ NPCPhrase (DO_AFTER_BOMB);
+
+ DISABLE_PHRASE (what_do_after_bomb);
+ }
+
+ if (PHRASE_ENABLED (whats_up_after_bomb))
+ Response (whats_up_after_bomb, AfterBomb);
+ if (PHRASE_ENABLED (what_do_after_bomb))
+ Response (what_do_after_bomb, AfterBomb);
+ Response (bye_after_bomb, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) >= 2)
+ {
+ NumVisits = GET_GAME_STATE (CHMMR_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_AFTER_BOMB_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_AFTER_BOMB_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (CHMMR_HOME_VISITS, NumVisits);
+
+ AfterBomb ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (CHMMR_UNLEASHED))
+ {
+ if (!GET_GAME_STATE (TALKING_PET_ON_SHIP)
+ || !GET_GAME_STATE (UTWIG_BOMB_ON_SHIP))
+ NotReady ((RESPONSE_REF)0);
+ else
+ {
+ NPCPhrase (YOU_ARE_READY);
+
+ ImproveBomb ((RESPONSE_REF)0);
+ }
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (CHMMR_HOME_VISITS);
+ if (!GET_GAME_STATE (CHMMR_EMERGING))
+ {
+ CommData.AlienColorMap = SetAbsColorMapIndex (
+ CommData.AlienColorMap, 1
+ );
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (WHY_YOU_HERE_1);
+ break;
+ case 1:
+ NPCPhrase (WHY_YOU_HERE_2);
+ break;
+ case 2:
+ NPCPhrase (WHY_YOU_HERE_3);
+ break;
+ case 3:
+ NPCPhrase (WHY_YOU_HERE_4);
+ --NumVisits;
+ break;
+ }
+
+ ChmmrShielded ((RESPONSE_REF)0);
+ }
+ else
+ {
+ SetCommIntroMode (CIM_FADE_IN_SCREEN, ONE_SECOND * 2);
+ NPCPhrase (WE_ARE_FREE);
+
+ if (NumVisits)
+ {
+ ChmmrFree ((RESPONSE_REF)0);
+ NumVisits = 0;
+ }
+ else
+ {
+ NPCPhrase (WHO_ARE_YOU);
+
+ construct_response (shared_phrase_buf,
+ i_am_captain0,
+ GLOBAL_SIS (CommanderName),
+ i_am_captain1,
+ GLOBAL_SIS (ShipName),
+ i_am_captain2,
+ (UNICODE*)NULL);
+ DoResponsePhrase (i_am_captain0, ChmmrFree, shared_phrase_buf);
+ Response (i_am_savior, ChmmrFree);
+ Response (i_am_silly, ChmmrFree);
+ }
+
+ SET_GAME_STATE (CHMMR_UNLEASHED, 1);
+ }
+ SET_GAME_STATE (CHMMR_HOME_VISITS, NumVisits);
+ }
+}
+
+static COUNT
+uninit_chmmr (void)
+{
+ return (0);
+}
+
+static void
+post_chmmr_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_chmmr_comm (void)
+{
+ LOCDATA *retval;
+
+ chmmr_desc.init_encounter_func = Intro;
+ chmmr_desc.post_encounter_func = post_chmmr_enc;
+ chmmr_desc.uninit_encounter_func = uninit_chmmr;
+
+ chmmr_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ chmmr_desc.AlienTextBaseline.y = 0;
+ chmmr_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ setSegue (Segue_peace);
+ retval = &chmmr_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/chmmr/resinst.h b/src/uqm/comm/chmmr/resinst.h
new file mode 100644
index 0000000..3365c97
--- /dev/null
+++ b/src/uqm/comm/chmmr/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define CHMMR_COLOR_MAP "comm.chmmr.colortable"
+#define CHMMR_CONVERSATION_PHRASES "comm.chmmr.dialogue"
+#define CHMMR_FONT "comm.chmmr.font"
+#define CHMMR_MUSIC "comm.chmmr.music"
+#define CHMMR_PMAP_ANIM "comm.chmmr.graphics"
diff --git a/src/uqm/comm/chmmr/strings.h b/src/uqm/comm/chmmr/strings.h
new file mode 100644
index 0000000..0952891
--- /dev/null
+++ b/src/uqm/comm/chmmr/strings.h
@@ -0,0 +1,105 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CHMMR_STRINGS_H
+#define CHMMR_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ WHY_YOU_HERE_1,
+ WHY_YOU_HERE_2,
+ WHY_YOU_HERE_3,
+ WHY_YOU_HERE_4,
+ find_out_whats_up,
+ HYBRID_PROCESS,
+ need_help,
+ CANT_HELP,
+ why_no_help,
+ LONG_TIME,
+ what_if_more_energy,
+ DANGER_TO_US,
+ need_advice,
+ WHAT_ADVICE,
+ how_defeat_urquan,
+ DEFEAT_LIKE_SO,
+ what_about_tpet,
+ SCARY_BUT_USEFUL,
+ what_about_bomb,
+ ABOUT_BOMB,
+ what_about_sun_device,
+ ABOUT_SUN_DEVICE,
+ what_about_samatra,
+ ABOUT_SAMATRA,
+ enough_advice,
+ OK_ENOUGH_ADVICE,
+ bye_shielded,
+ GOODBYE_SHIELDED,
+ WE_ARE_FREE,
+ WHO_ARE_YOU,
+ i_am_captain0,
+ i_am_captain1,
+ i_am_captain2,
+ i_am_savior,
+ i_am_silly,
+ WHY_HAVE_YOU_FREED_US,
+ serious_1,
+ serious_2,
+ silly,
+ WILL_HELP_ANALYZE_LOGS,
+ YOU_KNOW_SAMATRA,
+ DONT_KNOW_ABOUT_SAMATRA,
+ NEED_DISTRACTION,
+ HAVE_TALKING_PET,
+ NEED_WEAPON,
+ HAVE_BOMB,
+ RETURN_WHEN_READY,
+ YOU_ARE_READY,
+ further_assistance,
+ NO_FURTHER_ASSISTANCE,
+ tech_help,
+ USE_OUR_SHIPS_BEFORE,
+ where_weapon,
+ PRECURSOR_WEAPON,
+ where_distraction,
+ PSYCHIC_WEAPONRY,
+ what_now,
+ WE_WILL_IMPROVE_BOMB,
+ MODIFY_VESSEL,
+ wont_hurt_my_ship,
+ WILL_DESTROY_IT,
+ bummer_about_my_ship,
+ DEAD_SILENCE,
+ other_assistance,
+ USE_OUR_SHIPS_AFTER,
+ proceed,
+ TAKE_2_WEEKS,
+ HELLO_AFTER_BOMB_1,
+ HELLO_AFTER_BOMB_2,
+ whats_up_after_bomb,
+ GENERAL_INFO_AFTER_BOMB_1,
+ GENERAL_INFO_AFTER_BOMB_2,
+ what_do_after_bomb,
+ DO_AFTER_BOMB,
+ bye_after_bomb,
+ GOODBYE_AFTER_BOMB,
+ bye,
+ GOODBYE,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/comandr/Makeinfo b/src/uqm/comm/comandr/Makeinfo
new file mode 100644
index 0000000..36b63ed
--- /dev/null
+++ b/src/uqm/comm/comandr/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="comandr.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/comandr/comandr.c b/src/uqm/comm/comandr/comandr.c
new file mode 100644
index 0000000..57df233
--- /dev/null
+++ b/src/uqm/comm/comandr/comandr.c
@@ -0,0 +1,694 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/setup.h"
+#include "uqm/sis.h"
+ // for DeltaSISGauges(), DrawLanders()
+#include "libs/graphics/gfx_common.h"
+
+static LOCDATA commander_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ COMMANDER_PMAP_ANIM, /* AlienFrame */
+ COMMANDER_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_MIDDLE, /* AlienTextValign */
+ COMMANDER_COLOR_MAP, /* AlienColorMap */
+ COMMANDER_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ COMMANDER_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 3, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ { /* Blink */
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Running light */
+ 10, /* StartIndex */
+ 30, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ ONE_SECOND * 2, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM | COLORXFORM_ANIM,/* AnimFlags */
+ 0, ONE_SECOND / 30, /* FrameRate */
+ 0, ONE_SECOND / 15, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 4, /* StartIndex */
+ 6, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 7 / 60, ONE_SECOND / 12, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ByeBye (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, ok_i_will_get_radios))
+ NPCPhrase (THANKS_FOR_HELPING);
+ else if (PLAYER_SAID (R, well_go_get_them_now))
+ NPCPhrase (GLAD_WHEN_YOU_COME_BACK);
+ else if (PLAYER_SAID (R, we_will_take_care_of_base))
+ {
+ NPCPhrase (GOOD_LUCK_WITH_BASE);
+
+ SET_GAME_STATE (WILL_DESTROY_BASE, 1);
+ }
+ else if (PLAYER_SAID (R, take_care_of_base_again))
+ NPCPhrase (GOOD_LUCK_AGAIN);
+ else if (PLAYER_SAID (R, base_was_abandoned)
+ || PLAYER_SAID (R, i_lied_it_was_abandoned))
+ {
+ NPCPhrase (IT_WAS_ABANDONED);
+ NPCPhrase (HERE_COMES_ILWRATH);
+
+ SET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER, 1);
+ }
+ else if (PLAYER_SAID (R, oh_yes_big_fight))
+ {
+ NPCPhrase (IM_GLAD_YOU_WON);
+ NPCPhrase (HERE_COMES_ILWRATH);
+
+ SET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER, 1);
+ }
+ else if (PLAYER_SAID (R, i_cant_talk_about_it))
+ {
+ NPCPhrase (IM_SURE_IT_WAS_DIFFICULT);
+ NPCPhrase (HERE_COMES_ILWRATH);
+
+ SET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER, 1);
+ }
+ else if (PLAYER_SAID (R, cook_their_butts)
+ || PLAYER_SAID (R, overthrow_evil_aliens)
+ || PLAYER_SAID (R, annihilate_those_monsters))
+ {
+ SET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER, 0);
+
+ if (PLAYER_SAID (R, cook_their_butts))
+ NPCPhrase (COOK_BUTTS);
+ else if (PLAYER_SAID (R, overthrow_evil_aliens))
+ NPCPhrase (OVERTHROW_ALIENS);
+ else /* if (R == annihilate_those_monsters) */
+ NPCPhrase (KILL_MONSTERS);
+
+ construct_response (shared_phrase_buf,
+ name_40,
+ GLOBAL_SIS (CommanderName),
+ name_41,
+ (UNICODE*)NULL);
+
+ NPCPhrase (THIS_MAY_SEEM_SILLY);
+
+ Response (name_1, ByeBye);
+ Response (name_2, ByeBye);
+ Response (name_3, ByeBye);
+ DoResponsePhrase (name_40, ByeBye, shared_phrase_buf);
+
+ SET_GAME_STATE (STARBASE_AVAILABLE, 1);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, name_1))
+ {
+ NPCPhrase (OK_THE_NAFS);
+
+ SET_GAME_STATE (NEW_ALLIANCE_NAME, 0);
+ }
+ else if (PLAYER_SAID (R, name_2))
+ {
+ NPCPhrase (OK_THE_CAN);
+
+ SET_GAME_STATE (NEW_ALLIANCE_NAME, 1);
+ }
+ else if (PLAYER_SAID (R, name_3))
+ {
+ NPCPhrase (OK_THE_UFW);
+
+ SET_GAME_STATE (NEW_ALLIANCE_NAME, 2);
+ }
+ else /* if (PLAYER_SAID (R, name_4)) */
+ {
+ NPCPhrase (OK_THE_NAME_IS_EMPIRE0);
+ NPCPhrase (GLOBAL_PLAYER_NAME);
+ NPCPhrase (OK_THE_NAME_IS_EMPIRE1);
+
+ SET_GAME_STATE (NEW_ALLIANCE_NAME, 3);
+ }
+
+ NPCPhrase (STARBASE_WILL_BE_READY);
+ }
+}
+
+static void GiveRadios (RESPONSE_REF R);
+
+static void
+NoRadioactives (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, yes_this_is_supply_ship))
+ {
+ NPCPhrase (ABOUT_TIME);
+
+ if (GLOBAL_SIS (ElementAmounts[RADIOACTIVE]))
+ GiveRadios (0);
+ else
+ {
+ Response (i_lied, NoRadioactives);
+ Response (plumb_out, NoRadioactives);
+ }
+ }
+ else
+ {
+ if (PLAYER_SAID (R, where_can_i_get_radios))
+ {
+ NPCPhrase (RADIOS_ON_MERCURY);
+
+ DISABLE_PHRASE (where_can_i_get_radios);
+ }
+ else if (PLAYER_SAID (R, no_but_well_help0))
+ NPCPhrase (THE_WHAT_FROM_WHERE);
+ else if (PLAYER_SAID (R, what_slave_planet)
+ || PLAYER_SAID (R, i_lied))
+ NPCPhrase (DONT_KNOW_WHO_YOU_ARE);
+ else if (PLAYER_SAID (R, plumb_out))
+ NPCPhrase (WHAT_KIND_OF_IDIOT);
+ else if (PLAYER_SAID (R, i_lost_my_lander))
+ {
+ NPCPhrase (HERE_IS_A_NEW_LANDER);
+ ++GLOBAL_SIS (NumLanders);
+ DrawLanders ();
+ DeltaSISGauges (4, 0, 0);
+
+ SET_GAME_STATE (LANDERS_LOST, 1);
+ }
+ else if (PLAYER_SAID (R, i_lost_another_lander))
+ {
+ NPCPhrase (HERE_IS_ANOTHER_LANDER);
+ ++GLOBAL_SIS (NumLanders);
+ DrawLanders ();
+ DeltaSISGauges (4, 0, 0);
+ }
+ else if (PLAYER_SAID (R, need_fuel_mercury) ||
+ PLAYER_SAID (R, need_fuel_luna))
+ {
+ NPCPhrase (GIVE_FUEL);
+ DeltaSISGauges (0, 5 * FUEL_TANK_SCALE, 0);
+
+ SET_GAME_STATE (GIVEN_FUEL_BEFORE, 1);
+ }
+ else if (PLAYER_SAID (R, need_fuel_again))
+ {
+ NPCPhrase (GIVE_FUEL_AGAIN);
+ DeltaSISGauges (0, 5 * FUEL_TANK_SCALE, 0);
+ }
+
+ if (GLOBAL_SIS (ElementAmounts[RADIOACTIVE]))
+ GiveRadios (0);
+ else
+ {
+ if (GLOBAL_SIS (NumLanders) == 0
+ && GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
+ {
+ if (GET_GAME_STATE (LANDERS_LOST))
+ Response (i_lost_another_lander, NoRadioactives);
+ else
+ Response (i_lost_my_lander, NoRadioactives);
+ }
+ if (GLOBAL_SIS (FuelOnBoard) < 2 * FUEL_TANK_SCALE)
+ {
+ if (GET_GAME_STATE (GIVEN_FUEL_BEFORE))
+ Response (need_fuel_again, NoRadioactives);
+ else
+ Response (need_fuel_mercury, NoRadioactives);
+ }
+
+ Response (ok_i_will_get_radios, ByeBye);
+ if (PHRASE_ENABLED (where_can_i_get_radios))
+ {
+ Response (where_can_i_get_radios, NoRadioactives);
+ }
+ }
+ }
+}
+
+static void
+AskAfterRadios (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, i_lost_my_lander))
+ {
+ NPCPhrase (HERE_IS_A_NEW_LANDER);
+ ++GLOBAL_SIS (NumLanders);
+ DrawLanders ();
+ DeltaSISGauges (4, 0, 0);
+
+ SET_GAME_STATE (LANDERS_LOST, 1);
+ }
+ else if (PLAYER_SAID (R, i_lost_another_lander))
+ {
+ NPCPhrase (HERE_IS_ANOTHER_LANDER);
+ ++GLOBAL_SIS (NumLanders);
+ DrawLanders ();
+ DeltaSISGauges (4, 0, 0);
+ }
+ else if (PLAYER_SAID (R, need_fuel_mercury) ||
+ PLAYER_SAID (R, need_fuel_luna))
+ {
+ NPCPhrase (GIVE_FUEL);
+ DeltaSISGauges (0, 5 * FUEL_TANK_SCALE, 0);
+
+ SET_GAME_STATE (GIVEN_FUEL_BEFORE, 1);
+ }
+ else if (PLAYER_SAID (R, need_fuel_again))
+ {
+ NPCPhrase (GIVE_FUEL_AGAIN);
+ DeltaSISGauges (0, 5 * FUEL_TANK_SCALE, 0);
+ }
+ else if (PLAYER_SAID (R, where_get_radios))
+ {
+ NPCPhrase (RADIOS_ON_MERCURY);
+
+ DISABLE_PHRASE (where_get_radios);
+ }
+
+ {
+ if (GLOBAL_SIS (NumLanders) == 0
+ && GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
+ {
+ if (GET_GAME_STATE (LANDERS_LOST))
+ Response (i_lost_another_lander, AskAfterRadios);
+ else
+ Response (i_lost_my_lander, AskAfterRadios);
+ }
+ if (GLOBAL_SIS (FuelOnBoard) < 2 * FUEL_TANK_SCALE)
+ {
+ if (GET_GAME_STATE (GIVEN_FUEL_BEFORE))
+ Response (need_fuel_again, AskAfterRadios);
+ else
+ Response (need_fuel_mercury, AskAfterRadios);
+ }
+ Response (well_go_get_them_now, ByeBye);
+ if (PHRASE_ENABLED (where_get_radios))
+ {
+ Response (where_get_radios, AskAfterRadios);
+ }
+ }
+}
+
+static void
+BaseDestroyed (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, we_fought_them))
+ {
+ NPCPhrase (YOU_REALLY_FOUGHT_BASE);
+
+ Response (oh_yes_big_fight, ByeBye);
+ Response (i_lied_it_was_abandoned, ByeBye);
+ Response (i_cant_talk_about_it, ByeBye);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, we_are_here_to_help))
+ {
+ NPCPhrase (BASE_ON_MOON);
+ }
+ else
+ {
+ NPCPhrase (DEALT_WITH_BASE_YET);
+ }
+
+ Response (base_was_abandoned, ByeBye);
+ Response (we_fought_them, BaseDestroyed);
+ }
+}
+
+static void
+TellMoonBase (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ NPCPhrase (DEALT_WITH_BASE_YET);
+ }
+ else if (PLAYER_SAID (R, i_lost_my_lander))
+ {
+ NPCPhrase (HERE_IS_A_NEW_LANDER);
+ ++GLOBAL_SIS (NumLanders);
+ DrawLanders ();
+ DeltaSISGauges (4, 0, 0);
+
+ SET_GAME_STATE (LANDERS_LOST, 1);
+ }
+ else if (PLAYER_SAID (R, i_lost_another_lander))
+ {
+ NPCPhrase (HERE_IS_ANOTHER_LANDER);
+ ++GLOBAL_SIS (NumLanders);
+ DrawLanders ();
+ DeltaSISGauges (4, 0, 0);
+ }
+ else if (PLAYER_SAID (R, need_fuel_mercury) ||
+ PLAYER_SAID (R, need_fuel_luna))
+ {
+ NPCPhrase (GIVE_FUEL);
+ DeltaSISGauges (0, 5 * FUEL_TANK_SCALE, 0);
+
+ SET_GAME_STATE (GIVEN_FUEL_BEFORE, 1);
+ }
+ else if (PLAYER_SAID (R, need_fuel_again))
+ {
+ NPCPhrase (GIVE_FUEL_AGAIN);
+ DeltaSISGauges (0, 5 * FUEL_TANK_SCALE, 0);
+ }
+ else if (PLAYER_SAID (R, we_are_here_to_help))
+ {
+ NPCPhrase (BASE_ON_MOON);
+ }
+ else if (GET_GAME_STATE (STARBASE_YACK_STACK1) == 0)
+ {
+ NPCPhrase (ABOUT_BASE);
+
+ SET_GAME_STATE (STARBASE_YACK_STACK1, 1);
+ }
+ else
+ {
+ NPCPhrase (ABOUT_BASE_AGAIN);
+ }
+
+ if (GLOBAL_SIS (NumLanders) == 0
+ && GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
+ {
+ if (GET_GAME_STATE (LANDERS_LOST))
+ Response (i_lost_another_lander, TellMoonBase);
+ else
+ Response (i_lost_my_lander, TellMoonBase);
+ }
+ if (GLOBAL_SIS (FuelOnBoard) < 2 * FUEL_TANK_SCALE)
+ {
+ if (GET_GAME_STATE (GIVEN_FUEL_BEFORE))
+ Response (need_fuel_again, TellMoonBase);
+ else
+ Response (need_fuel_luna, TellMoonBase);
+ }
+ if (GET_GAME_STATE (WILL_DESTROY_BASE) == 0)
+ Response (we_will_take_care_of_base, ByeBye);
+ else
+ Response (take_care_of_base_again, ByeBye);
+ if (GET_GAME_STATE (STARBASE_YACK_STACK1) == 0)
+ Response (tell_me_about_base, TellMoonBase);
+ else
+ Response (tell_me_again, TellMoonBase);
+}
+
+static void RevealSelf (RESPONSE_REF R);
+
+static void
+TellProbe (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ NPCPhrase (THAT_WAS_PROBE);
+ DISABLE_PHRASE (what_was_red_thing);
+
+ Response (it_went_away, RevealSelf);
+ Response (we_destroyed_it, RevealSelf);
+ Response (what_probe, RevealSelf);
+}
+
+static void
+RevealSelf (RESPONSE_REF R)
+{
+ BYTE i, stack;
+
+ stack = 0;
+ if (PLAYER_SAID (R, we_are_vindicator0))
+ {
+ NPCPhrase (THATS_IMPOSSIBLE);
+
+ DISABLE_PHRASE (we_are_vindicator0);
+ }
+ else if (PLAYER_SAID (R, our_mission_was_secret))
+ {
+ NPCPhrase (ACKNOWLEDGE_SECRET);
+
+ DISABLE_PHRASE (our_mission_was_secret);
+ }
+ else if (PLAYER_SAID (R, first_give_info))
+ {
+ NPCPhrase (ASK_AWAY);
+
+ stack = 1;
+ DISABLE_PHRASE (first_give_info);
+ }
+ else if (PLAYER_SAID (R, whats_this_starbase))
+ {
+ NPCPhrase (STARBASE_IS);
+
+ stack = 1;
+ DISABLE_PHRASE (whats_this_starbase);
+ }
+ else if (PLAYER_SAID (R, what_about_earth))
+ {
+ NPCPhrase (HAPPENED_TO_EARTH);
+
+ stack = 1;
+ DISABLE_PHRASE (what_about_earth);
+ }
+ else if (PLAYER_SAID (R, where_are_urquan))
+ {
+ NPCPhrase (URQUAN_LEFT);
+
+ stack = 1;
+ DISABLE_PHRASE (where_are_urquan);
+ }
+ else if (PLAYER_SAID (R, it_went_away))
+ NPCPhrase (DEEP_TROUBLE);
+ else if (PLAYER_SAID (R, we_destroyed_it))
+ NPCPhrase (GOOD_NEWS);
+ else if (PLAYER_SAID (R, what_probe))
+ NPCPhrase (SURE_HOPE);
+
+ for (i = 0; i < 2; ++i, stack ^= 1)
+ {
+ if (stack == 1)
+ {
+ if (PHRASE_ENABLED (first_give_info))
+ Response (first_give_info, RevealSelf);
+ else if (PHRASE_ENABLED (whats_this_starbase))
+ Response (whats_this_starbase, RevealSelf);
+ else if (PHRASE_ENABLED (what_about_earth))
+ Response (what_about_earth, RevealSelf);
+ else if (PHRASE_ENABLED (where_are_urquan))
+ Response (where_are_urquan, RevealSelf);
+ else if (PHRASE_ENABLED (what_was_red_thing))
+ {
+ Response (what_was_red_thing, TellProbe);
+ }
+ }
+ else
+ {
+ if (PHRASE_ENABLED (we_are_vindicator0))
+ {
+ construct_response (shared_phrase_buf,
+ we_are_vindicator0,
+ GLOBAL_SIS (CommanderName),
+ we_are_vindicator1,
+ GLOBAL_SIS (ShipName),
+ we_are_vindicator2,
+ (UNICODE*)NULL);
+ DoResponsePhrase (we_are_vindicator0, RevealSelf, shared_phrase_buf);
+ }
+ else if (PHRASE_ENABLED (our_mission_was_secret))
+ Response (our_mission_was_secret, RevealSelf);
+ else
+ {
+ if (GET_GAME_STATE (MOONBASE_DESTROYED) == 0)
+ Response (we_are_here_to_help, TellMoonBase);
+ else
+ Response (we_are_here_to_help, BaseDestroyed);
+ }
+ }
+ }
+}
+
+static void
+GiveRadios (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, we_will_transfer_now))
+ {
+ SET_GAME_STATE (RADIOACTIVES_PROVIDED, 1);
+
+ NPCPhrase (FUEL_UP0);
+ NPCPhrase (FUEL_UP1);
+ AlienTalkSegue (1);
+
+ CommData.AlienAmbientArray[2].AnimFlags |= ANIM_DISABLED;
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 0)
+ ), ONE_SECOND / 2);
+
+ AlienTalkSegue ((COUNT)~0);
+
+ RevealSelf (0);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, what_will_you_give_us))
+ NPCPhrase (MESSAGE_GARBLED_1);
+ else if (PLAYER_SAID (R, before_radios_we_need_info))
+ NPCPhrase (MESSAGE_GARBLED_2);
+
+ Response (we_will_transfer_now, GiveRadios);
+ Response (what_will_you_give_us, GiveRadios);
+ Response (before_radios_we_need_info, GiveRadios);
+ }
+}
+
+static void
+Intro (void)
+{
+ if (GET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER))
+ {
+ NPCPhrase (VERY_IMPRESSIVE);
+
+ Response (cook_their_butts, ByeBye);
+ Response (overthrow_evil_aliens, ByeBye);
+ Response (annihilate_those_monsters, ByeBye);
+ }
+ else if (GET_GAME_STATE (STARBASE_VISITED))
+ {
+ if (GET_GAME_STATE (RADIOACTIVES_PROVIDED))
+ {
+ if (GET_GAME_STATE (MOONBASE_DESTROYED) == 0)
+ {
+ TellMoonBase (0);
+ }
+ else
+ {
+ BaseDestroyed (0);
+ }
+ }
+ else
+ {
+ CommData.AlienColorMap =
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1);
+ NPCPhrase (DO_YOU_HAVE_RADIO_THIS_TIME);
+
+ if (GLOBAL_SIS (ElementAmounts[RADIOACTIVE]))
+ GiveRadios (0);
+ else
+ AskAfterRadios (0);
+ }
+ }
+ else /* first visit */
+ {
+ CommData.AlienColorMap =
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1);
+
+ SET_GAME_STATE (STARBASE_VISITED, 1);
+
+ NPCPhrase (ARE_YOU_SUPPLY_SHIP);
+ construct_response (
+ shared_phrase_buf,
+ no_but_well_help0,
+ GLOBAL_SIS (ShipName),
+ no_but_well_help1,
+ (UNICODE*)NULL);
+ DoResponsePhrase (no_but_well_help0, NoRadioactives, shared_phrase_buf);
+ Response (yes_this_is_supply_ship, NoRadioactives);
+ Response (what_slave_planet, NoRadioactives);
+ }
+}
+
+static COUNT
+uninit_commander (void)
+{
+ return (0);
+}
+
+static void
+post_commander_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_commander_comm ()
+{
+ LOCDATA *retval;
+
+ commander_desc.init_encounter_func = Intro;
+ commander_desc.post_encounter_func = post_commander_enc;
+ commander_desc.uninit_encounter_func = uninit_commander;
+
+ if (GET_GAME_STATE (RADIOACTIVES_PROVIDED))
+ {
+ commander_desc.AlienAmbientArray[2].AnimFlags |= ANIM_DISABLED;
+ // regular track -- let's make sure
+ commander_desc.AlienSongFlags &= ~LDASF_USE_ALTERNATE;
+ }
+ else
+ {
+ commander_desc.AlienAmbientArray[2].AnimFlags &= ~ANIM_DISABLED;
+ // use alternate 'low-power' track if available
+ commander_desc.AlienAltSongRes = COMMANDER_LOWPOW_MUSIC;
+ commander_desc.AlienSongFlags |= LDASF_USE_ALTERNATE;
+ }
+
+ commander_desc.AlienTextWidth = 143;
+ commander_desc.AlienTextBaseline.x = 164;
+ commander_desc.AlienTextBaseline.y = 20;
+
+ setSegue (Segue_peace);
+ retval = &commander_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/comandr/resinst.h b/src/uqm/comm/comandr/resinst.h
new file mode 100644
index 0000000..7798214
--- /dev/null
+++ b/src/uqm/comm/comandr/resinst.h
@@ -0,0 +1,12 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define COMMANDER_COLOR_MAP "comm.commander.colortable"
+#define COMMANDER_CONVERSATION_PHRASES "comm.commander.dialogue"
+#define COMMANDER_FONT "comm.commander.font"
+#define COMMANDER_LOWPOW_MUSIC "comm.commander.lowpower.music"
+#define COMMANDER_MUSIC "comm.commander.music"
+#define COMMANDER_PMAP_ANIM "comm.commander.graphics"
+#define STARBASE_ALT_MUSIC "comm.starbase.music"
+#define STARBASE_CONVERSATION_PHRASES "comm.starbase.dialogue"
diff --git a/src/uqm/comm/comandr/strings.h b/src/uqm/comm/comandr/strings.h
new file mode 100644
index 0000000..fcc330e
--- /dev/null
+++ b/src/uqm/comm/comandr/strings.h
@@ -0,0 +1,127 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef COMANDR_STRINGS_H
+#define COMANDR_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ GLAD_WHEN_YOU_COME_BACK,
+ GIVE_FUEL,
+ GIVE_FUEL_AGAIN,
+ ARE_YOU_SUPPLY_SHIP,
+ DO_YOU_HAVE_RADIO_THIS_TIME,
+ HERE_IS_ANOTHER_LANDER,
+ THE_WHAT_FROM_WHERE,
+ ABOUT_TIME,
+ MESSAGE_GARBLED_1,
+ MESSAGE_GARBLED_2,
+ HERE_IS_A_NEW_LANDER,
+ THIS_MAY_SEEM_SILLY,
+ OK_THE_NAFS,
+ OK_THE_CAN,
+ OK_THE_UFW,
+ OK_THE_NAME_IS_EMPIRE0,
+ OK_THE_NAME_IS_EMPIRE1,
+ FUEL_UP0,
+ FUEL_UP1,
+ WHAT_KIND_OF_IDIOT,
+ DONT_KNOW_WHO_YOU_ARE,
+ THATS_IMPOSSIBLE,
+ ASK_AWAY,
+ RADIOS_ON_MERCURY,
+ THANKS_FOR_HELPING,
+ STARBASE_IS,
+ HAPPENED_TO_EARTH,
+ URQUAN_LEFT,
+ BASE_ON_MOON,
+ ACKNOWLEDGE_SECRET,
+ ABOUT_BASE,
+ GOOD_LUCK_WITH_BASE,
+ DEALT_WITH_BASE_YET,
+ HERE_COMES_ILWRATH,
+ VERY_IMPRESSIVE,
+ IT_WAS_ABANDONED,
+ YOU_REALLY_FOUGHT_BASE,
+ IM_GLAD_YOU_WON,
+ IM_SURE_IT_WAS_DIFFICULT,
+ THAT_WAS_PROBE,
+ DEEP_TROUBLE,
+ GOOD_NEWS,
+ SURE_HOPE,
+ ABOUT_BASE_AGAIN,
+ COOK_BUTTS,
+ OVERTHROW_ALIENS,
+ KILL_MONSTERS,
+ GOOD_LUCK_AGAIN,
+ STARBASE_WILL_BE_READY,
+
+ overthrow_evil_aliens,
+ annihilate_those_monsters,
+ cook_their_butts,
+ where_get_radios,
+ well_go_get_them_now,
+ we_will_transfer_now,
+ what_will_you_give_us,
+ before_radios_we_need_info,
+ no_but_well_help0,
+ no_but_well_help1,
+ yes_this_is_supply_ship,
+ what_slave_planet,
+ i_lied,
+ plumb_out,
+ we_are_vindicator0,
+ we_are_vindicator1,
+ we_are_vindicator2,
+ first_give_info,
+ we_must_go_now,
+ where_can_i_get_radios,
+ ok_i_will_get_radios,
+ whats_this_starbase,
+ what_about_earth,
+ where_are_urquan,
+ our_mission_was_secret,
+ we_are_here_to_help,
+ tell_me_about_base,
+ we_will_take_care_of_base,
+ tell_me_again,
+ base_was_abandoned,
+ we_fought_them,
+ oh_yes_big_fight,
+ i_lied_it_was_abandoned,
+ i_cant_talk_about_it,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ i_lost_my_lander,
+ i_lost_another_lander,
+ need_fuel_mercury,
+ need_fuel_luna,
+ need_fuel_again,
+ what_was_red_thing,
+ it_went_away,
+ we_destroyed_it,
+ what_probe,
+ take_care_of_base_again,
+ goodbye_commander,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/commall.h b/src/uqm/comm/commall.h
new file mode 100644
index 0000000..4f8ebed
--- /dev/null
+++ b/src/uqm/comm/commall.h
@@ -0,0 +1,26 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_COMM_COMMALL_H_
+#define UQM_COMM_COMMALL_H_
+
+#include "uqm/colors.h"
+#include "uqm/comm.h"
+#include "uqm/commglue.h"
+#include "libs/reslib.h"
+
+#endif /* UQM_COMM_COMMALL_H_ */
+
diff --git a/src/uqm/comm/druuge/Makeinfo b/src/uqm/comm/druuge/Makeinfo
new file mode 100644
index 0000000..733ed6c
--- /dev/null
+++ b/src/uqm/comm/druuge/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="druugec.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/druuge/druugec.c b/src/uqm/comm/druuge/druugec.c
new file mode 100644
index 0000000..9a082c1
--- /dev/null
+++ b/src/uqm/comm/druuge/druugec.c
@@ -0,0 +1,926 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/setup.h"
+#include "uqm/sis.h"
+ // for DeltaSISGauges()
+
+
+static LOCDATA druuge_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ DRUUGE_PMAP_ANIM, /* AlienFrame */
+ DRUUGE_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_MIDDLE, /* AlienTextValign */
+ DRUUGE_COLOR_MAP, /* AlienColorMap */
+ DRUUGE_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ DRUUGE_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 11, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 5, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 40, 0, /* FrameRate */
+ ONE_SECOND * 3 / 40, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 9, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 40, 0, /* FrameRate */
+ ONE_SECOND * 3 / 40, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 13, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 40, 0, /* FrameRate */
+ ONE_SECOND * 3 / 40, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 19, /* StartIndex */
+ 3, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 25, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 28, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 31, /* StartIndex */
+ 2, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 33, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND * 7, ONE_SECOND * 3,/* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 40, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 10, 0, /* FrameRate */
+ ONE_SECOND * 3 / 10, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 44, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 5, 0, /* FrameRate */
+ ONE_SECOND / 5, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 4, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 12, ONE_SECOND / 12, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static COUNT SlaveryCount = 0;
+static BOOLEAN AttemptedSalvage = FALSE;
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, bye))
+ {
+ setSegue (Segue_peace);
+
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ if (SlaveryCount)
+ {
+ UWORD PreviousSlaves;
+
+ PreviousSlaves = MAKE_WORD (
+ GET_GAME_STATE (CREW_SOLD_TO_DRUUGE0),
+ GET_GAME_STATE (CREW_SOLD_TO_DRUUGE1)
+ );
+ SlaveryCount += PreviousSlaves;
+ if (SlaveryCount > 250 && PreviousSlaves <= 250)
+ {
+ if (PreviousSlaves > 100)
+ GLOBAL (CrewCost) += (22 - 7);
+ else
+ GLOBAL (CrewCost) += 22;
+ }
+ else if (SlaveryCount > 100 && PreviousSlaves <= 100)
+ GLOBAL (CrewCost) += 7;
+
+ SET_GAME_STATE (CREW_SOLD_TO_DRUUGE0, LOBYTE (SlaveryCount));
+ SET_GAME_STATE (CREW_SOLD_TO_DRUUGE1, HIBYTE (SlaveryCount));
+ }
+
+ switch (GET_GAME_STATE (DRUUGE_HOME_VISITS))
+ {
+ case 1:
+ NPCPhrase (BYE_FROM_TRADE_WORLD_1);
+ break;
+ default:
+ NPCPhrase (BYE_FROM_TRADE_WORLD_2);
+ break;
+ }
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ NPCPhrase (GOODBYE_FROM_BOMB_PLANET);
+ else
+ NPCPhrase (GOODBYE_FROM_SPACE);
+ }
+ else /* if (R == then_we_take_bomb) */
+ {
+ setSegue (Segue_hostile);
+
+ NPCPhrase (FIGHT_FOR_BOMB);
+ }
+}
+
+static void TradeWorld (RESPONSE_REF R);
+
+static void
+Buy (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, want_to_buy)
+ || PLAYER_SAID (R, im_ready_to_buy))
+ {
+ NPCPhrase (READY_TO_SELL);
+ if (!GET_GAME_STATE (ROSY_SPHERE))
+ NPCPhrase (HAVE_SPHERE);
+ if (!GET_GAME_STATE (ARTIFACT_2_ON_SHIP))
+ NPCPhrase (HAVE_ART_1);
+ if (!GET_GAME_STATE (ARTIFACT_3_ON_SHIP))
+ NPCPhrase (HAVE_ART_2);
+ NPCPhrase (SHIPS_AND_FUEL);
+
+ SET_GAME_STATE (KNOW_DRUUGE_SLAVERS, 3);
+ }
+ else if (PLAYER_SAID (R, buy_druuge_ship))
+ {
+#define SHIP_CREW_COST 100
+ if (GLOBAL_SIS (CrewEnlisted) < SHIP_CREW_COST)
+ NPCPhrase (NOT_ENOUGH_CREW);
+ else if (EscortFeasibilityStudy (DRUUGE_SHIP) == 0)
+ NPCPhrase (NOT_ENOUGH_ROOM);
+ else
+ {
+ DeltaSISGauges (-SHIP_CREW_COST, 0, 0);
+ SlaveryCount += SHIP_CREW_COST;
+ AddEscortShips (DRUUGE_SHIP, 1);
+
+ NPCPhrase (BOUGHT_SHIP);
+ }
+ }
+#define ARTIFACT_CREW_COST 100
+ else if (PLAYER_SAID (R, buy_rosy_sphere))
+ {
+ if (GLOBAL_SIS (CrewEnlisted) < ARTIFACT_CREW_COST)
+ NPCPhrase (NOT_ENOUGH_CREW);
+ else
+ {
+ DeltaSISGauges (-ARTIFACT_CREW_COST, 0, 0);
+ SlaveryCount += ARTIFACT_CREW_COST;
+ SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 1);
+ SET_GAME_STATE (ROSY_SPHERE, 1);
+
+ NPCPhrase (BOUGHT_SPHERE);
+ }
+ }
+ else if (PLAYER_SAID (R, buy_art_1))
+ {
+ if (GLOBAL_SIS (CrewEnlisted) < ARTIFACT_CREW_COST)
+ NPCPhrase (NOT_ENOUGH_CREW);
+ else
+ {
+ DeltaSISGauges (-ARTIFACT_CREW_COST, 0, 0);
+ SlaveryCount += ARTIFACT_CREW_COST;
+ SET_GAME_STATE (ARTIFACT_2_ON_SHIP, 1);
+
+ NPCPhrase (BOUGHT_ART_1);
+ }
+ }
+ else if (PLAYER_SAID (R, buy_art_2))
+ {
+ if (GLOBAL_SIS (CrewEnlisted) < ARTIFACT_CREW_COST)
+ NPCPhrase (NOT_ENOUGH_CREW);
+ else
+ {
+ DeltaSISGauges (-ARTIFACT_CREW_COST, 0, 0);
+ SlaveryCount += ARTIFACT_CREW_COST;
+ SET_GAME_STATE (ARTIFACT_3_ON_SHIP, 1);
+
+ NPCPhrase (BOUGHT_ART_2);
+ }
+ }
+ else if (PLAYER_SAID (R, buy_fuel))
+ {
+#define FUEL_CREW_COST 10
+ if (GLOBAL_SIS (CrewEnlisted) < FUEL_CREW_COST)
+ NPCPhrase (NOT_ENOUGH_CREW);
+ else
+ {
+ DeltaSISGauges (-FUEL_CREW_COST,
+ FUEL_CREW_COST * FUEL_TANK_SCALE, 0);
+ SlaveryCount += FUEL_CREW_COST;
+
+ NPCPhrase (BOUGHT_FUEL);
+ }
+ }
+
+ Response (buy_druuge_ship, Buy);
+ if (!GET_GAME_STATE (ROSY_SPHERE))
+ Response (buy_rosy_sphere, Buy);
+ if (!GET_GAME_STATE (ARTIFACT_2_ON_SHIP))
+ Response (buy_art_1, Buy);
+ if (!GET_GAME_STATE (ARTIFACT_3_ON_SHIP))
+ Response (buy_art_2, Buy);
+ Response (buy_fuel, Buy);
+ Response (done_buying, TradeWorld);
+}
+
+static void Sell (RESPONSE_REF R);
+
+static RESPONSE_REF LastResponse = 0;
+
+static void
+Trade (RESPONSE_REF R)
+{
+ if (!PLAYER_SAID (R, whats_the_sphere_again))
+ {
+ NPCPhrase (TRADE_FOR_SPHERE);
+ LastResponse = R;
+ }
+ else
+ {
+ NPCPhrase (SPHERE_IS);
+
+ DISABLE_PHRASE (whats_the_sphere_again);
+ }
+
+ Response (no_way, Sell);
+ Response (way, Sell);
+ if (PHRASE_ENABLED (whats_the_sphere_again))
+ {
+ Response (whats_the_sphere_again, Trade);
+ }
+}
+
+static void
+DoTransaction (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, sell_maidens))
+ {
+ SET_GAME_STATE (MAIDENS_ON_SHIP, 0);
+ }
+ else if (PLAYER_SAID (R, sell_fragments))
+ {
+ BYTE num_frags;
+
+ if (GET_GAME_STATE (EGG_CASE0_ON_SHIP))
+ {
+ SET_GAME_STATE (EGG_CASE0_ON_SHIP, 0);
+ }
+ else if (GET_GAME_STATE (EGG_CASE1_ON_SHIP))
+ {
+ SET_GAME_STATE (EGG_CASE1_ON_SHIP, 0);
+ }
+ else if (GET_GAME_STATE (EGG_CASE2_ON_SHIP))
+ {
+ SET_GAME_STATE (EGG_CASE2_ON_SHIP, 0);
+ }
+
+ num_frags = GET_GAME_STATE (FRAGMENTS_BOUGHT) + 1;
+ SET_GAME_STATE (FRAGMENTS_BOUGHT, num_frags);
+ }
+ else if (PLAYER_SAID (R, sell_caster))
+ {
+ SET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP, 0);
+ }
+ else if (PLAYER_SAID (R, sell_spawner))
+ {
+ SET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP, 0);
+ }
+
+ if (!GET_GAME_STATE (ROSY_SPHERE)
+ && GET_GAME_STATE (ROSY_SPHERE_ON_SHIP))
+ {
+ SET_GAME_STATE (ROSY_SPHERE, 1);
+ }
+ else
+ {
+ BYTE trade_gas;
+ BYTE ship_slots, ships_to_trade;
+
+ trade_gas = 0;
+ ships_to_trade = 0;
+ ship_slots = EscortFeasibilityStudy (DRUUGE_SHIP);
+ if (PLAYER_SAID (R, sell_maidens))
+ {
+ NPCPhrase (BOUGHT_MAIDENS);
+ ships_to_trade = 6;
+ }
+ else if (PLAYER_SAID (R, sell_fragments))
+ {
+ NPCPhrase (BOUGHT_FRAGMENTS);
+ ships_to_trade = 1;
+ }
+ else if (PLAYER_SAID (R, sell_caster))
+ {
+ NPCPhrase (BOUGHT_CASTER);
+ ships_to_trade = 0;
+ trade_gas = 1;
+ }
+ else if (PLAYER_SAID (R, sell_spawner))
+ {
+ NPCPhrase (BOUGHT_SPAWNER);
+ ships_to_trade = 3;
+ trade_gas = 1;
+ }
+
+ NPCPhrase (YOU_GET);
+ if (ships_to_trade)
+ {
+ AddEscortShips (DRUUGE_SHIP, ships_to_trade);
+
+ if (ship_slots >= ships_to_trade)
+ NPCPhrase (DEAL_FOR_STATED_SHIPS);
+ else if (ship_slots == 0)
+ NPCPhrase (DEAL_FOR_NO_SHIPS);
+ else
+ NPCPhrase (DEAL_FOR_LESS_SHIPS);
+
+ if (trade_gas)
+ NPCPhrase (YOU_ALSO_GET);
+ }
+
+ if (trade_gas)
+ {
+ BYTE slot;
+ COUNT f;
+ DWORD capacity;
+
+ capacity = FUEL_RESERVE;
+ slot = NUM_MODULE_SLOTS - 1;
+ do
+ {
+ if (GLOBAL_SIS (ModuleSlots[slot]) == FUEL_TANK
+ || GLOBAL_SIS (ModuleSlots[slot]) == HIGHEFF_FUELSYS)
+ {
+ COUNT volume;
+
+ volume = GLOBAL_SIS (ModuleSlots[slot]) == FUEL_TANK
+ ? FUEL_TANK_CAPACITY : HEFUEL_TANK_CAPACITY;
+ capacity += volume;
+ }
+ } while (slot--);
+ capacity -= GLOBAL_SIS (FuelOnBoard);
+ f = (COUNT)((capacity + (FUEL_TANK_SCALE >> 1)) / FUEL_TANK_SCALE);
+
+ while (capacity > 0x3FFFL)
+ {
+ DeltaSISGauges (0, 0x3FFF, 0);
+ capacity -= 0x3FFF;
+ }
+ DeltaSISGauges (0, (SIZE)capacity, 0);
+
+ NPCPhrase (FUEL0);
+ NPCNumber (f, NULL);
+ NPCPhrase (FUEL1);
+
+ if (f >= 250)
+ NPCPhrase (HIDEOUS_DEAL);
+ else if (f >= 100)
+ NPCPhrase (BAD_DEAL);
+ else if (f >= 50)
+ NPCPhrase (FAIR_DEAL);
+ else if (f >= 10)
+ NPCPhrase (GOOD_DEAL);
+ else
+ NPCPhrase (FINE_DEAL);
+ }
+ }
+}
+
+static void
+Sell (RESPONSE_REF R)
+{
+ RESPONSE_FUNC RespFunc;
+
+ if (PLAYER_SAID (R, want_to_sell))
+ NPCPhrase (READY_TO_BUY);
+ else if (PLAYER_SAID (R, no_way)
+ || PLAYER_SAID (R, way))
+ {
+ if (PLAYER_SAID (R, no_way))
+ NPCPhrase (OK_REGULAR_DEAL);
+ else
+ {
+ NPCPhrase (OK_HERES_SPHERE);
+
+ SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 1);
+ }
+
+ DoTransaction (LastResponse);
+ }
+ else if (PLAYER_SAID (R, sell_maidens)
+ || PLAYER_SAID (R, sell_fragments)
+ || PLAYER_SAID (R, sell_caster)
+ || PLAYER_SAID (R, sell_spawner))
+ {
+ DoTransaction (R);
+ }
+
+ if (!GET_GAME_STATE (ROSY_SPHERE))
+ RespFunc = (RESPONSE_FUNC)Trade;
+ else
+ RespFunc = (RESPONSE_FUNC)Sell;
+ if (GET_GAME_STATE (MAIDENS_ON_SHIP))
+ Response (sell_maidens, RespFunc);
+ if ((GET_GAME_STATE (EGG_CASE0_ON_SHIP)
+ || GET_GAME_STATE (EGG_CASE1_ON_SHIP)
+ || GET_GAME_STATE (EGG_CASE2_ON_SHIP))
+ && GET_GAME_STATE (FRAGMENTS_BOUGHT) < 2)
+ Response (sell_fragments, RespFunc);
+ if (GET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP))
+ Response (sell_caster, RespFunc);
+ if (GET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP))
+ Response (sell_spawner, RespFunc);
+ Response (done_selling, TradeWorld);
+}
+
+static void
+ExplainSlaveTrade (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, want_to_buy))
+ NPCPhrase (WE_SELL_FOR_CREW);
+ else if (PLAYER_SAID (R, isnt_this_slave_trading))
+ {
+ NPCPhrase (NO_SLAVE_TRADE);
+
+ SET_GAME_STATE (KNOW_DRUUGE_SLAVERS, 1);
+ }
+ else if (PLAYER_SAID (R, what_do_with_crew))
+ {
+ NPCPhrase (HAVE_FUN);
+
+ SET_GAME_STATE (KNOW_DRUUGE_SLAVERS, 2);
+ }
+
+ switch (GET_GAME_STATE (KNOW_DRUUGE_SLAVERS))
+ {
+ case 0:
+ Response (isnt_this_slave_trading, ExplainSlaveTrade);
+ break;
+ case 1:
+ Response (what_do_with_crew, ExplainSlaveTrade);
+ break;
+ }
+ Response (i_will_never_trade_crew, TradeWorld);
+ Response (im_ready_to_buy, Buy);
+}
+
+static void
+TradeWorld (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_up_at_trade_world))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (DRUUGE_HOME_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GEN_INFO_AT_TRADE_WORLD_1);
+ break;
+ case 1:
+ NPCPhrase (GEN_INFO_AT_TRADE_WORLD_2);
+ break;
+ case 2:
+ NPCPhrase (GEN_INFO_AT_TRADE_WORLD_3);
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) < 2)
+ {
+ SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 2);
+ }
+ break;
+ case 3:
+ NPCPhrase (GEN_INFO_AT_TRADE_WORLD_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (DRUUGE_HOME_INFO, NumVisits);
+ DISABLE_PHRASE (whats_up_at_trade_world);
+ }
+ else if (PLAYER_SAID (R, done_selling))
+ NPCPhrase (OK_DONE_SELLING);
+ else if (PLAYER_SAID (R, done_buying))
+ NPCPhrase (OK_DONE_BUYING);
+ else if (PLAYER_SAID (R, i_will_never_trade_crew))
+ NPCPhrase (YOUR_LOSS);
+
+ if (PHRASE_ENABLED (whats_up_at_trade_world))
+ {
+ Response (whats_up_at_trade_world, TradeWorld);
+ }
+ Response (want_to_sell, Sell);
+ if (GET_GAME_STATE (KNOW_DRUUGE_SLAVERS) == 3)
+ Response (want_to_buy, Buy);
+ else
+ Response (want_to_buy, ExplainSlaveTrade);
+ Response (bye, ExitConversation);
+}
+
+static void
+BombAmbush (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_up_at_bomb_planet))
+ {
+ NPCPhrase (GEN_INFO_AT_BOMB_PLANET);
+ SET_GAME_STATE (BOMB_VISITS, 2);
+ }
+ else if (PLAYER_SAID (R, we_get_bomb))
+ {
+ NPCPhrase (NOT_GET_BOMB);
+ SET_GAME_STATE (BOMB_VISITS, 3);
+ }
+
+ switch (GET_GAME_STATE (BOMB_VISITS))
+ {
+ case 1:
+ Response (whats_up_at_bomb_planet, BombAmbush);
+ break;
+ case 2:
+ Response (we_get_bomb, BombAmbush);
+ break;
+ default:
+ Response (then_we_take_bomb, ExitConversation);
+ break;
+ }
+ Response (bye, ExitConversation);
+}
+
+static void
+Space (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_up_in_space))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (DRUUGE_SPACE_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_IN_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_IN_SPACE_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_IN_SPACE_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_IN_SPACE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (DRUUGE_SPACE_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_in_space);
+ }
+
+ if (PHRASE_ENABLED (whats_up_in_space))
+ {
+ Response (whats_up_in_space, Space);
+ }
+ Response (bye, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (GET_GAME_STATE (DRUUGE_MANNER))
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (DRUUGE_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HSTL_TRADE_WORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HSTL_TRADE_WORLD_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (DRUUGE_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (DRUUGE_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (DRUUGE_VISITS, NumVisits);
+ }
+
+ setSegue (Segue_hostile);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ {
+ NumVisits = GET_GAME_STATE (DRUUGE_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INITIAL_TRADE_WORLD_HELLO);
+ break;
+ case 1:
+ NPCPhrase (SSQ_TRADE_WORLD_HELLO_1);
+ break;
+ case 2:
+ NPCPhrase (SSQ_TRADE_WORLD_HELLO_2);
+ break;
+ case 3:
+ NPCPhrase (SSQ_TRADE_WORLD_HELLO_3);
+ break;
+ case 4:
+ NPCPhrase (SSQ_TRADE_WORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (DRUUGE_HOME_VISITS, NumVisits);
+ }
+ if (GET_GAME_STATE (ATTACKED_DRUUGE)
+ && !GET_GAME_STATE (DRUUGE_DISCLAIMER))
+ {
+ // There is no HOSTILE_TRADE voice track that we know of
+ // so this is currently disabled
+ //NPCPhrase (HOSTILE_TRADE);
+ SET_GAME_STATE (DRUUGE_DISCLAIMER, 1);
+ }
+ if (GET_GAME_STATE (MAIDENS_ON_SHIP)
+ && !GET_GAME_STATE (SCANNED_MAIDENS))
+ {
+ NPCPhrase (SCAN_MAIDENS);
+ SET_GAME_STATE (SCANNED_MAIDENS, 1);
+ }
+ if ((GET_GAME_STATE (EGG_CASE0_ON_SHIP)
+ || GET_GAME_STATE (EGG_CASE1_ON_SHIP)
+ || GET_GAME_STATE (EGG_CASE2_ON_SHIP))
+ && !GET_GAME_STATE (SCANNED_FRAGMENTS))
+ {
+ if (GET_GAME_STATE (FRAGMENTS_BOUGHT) < 2)
+ NPCPhrase (SCAN_FRAGMENTS);
+ else
+ NPCPhrase (ENOUGH_FRAGMENTS);
+ SET_GAME_STATE (SCANNED_FRAGMENTS, 1);
+ }
+ if (GET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP)
+ && !GET_GAME_STATE (SCANNED_CASTER))
+ {
+ NPCPhrase (SCAN_DRUUGE_CASTER);
+ SET_GAME_STATE (SCANNED_CASTER, 1);
+ }
+ if (GET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP)
+ && !GET_GAME_STATE (SCANNED_SPAWNER))
+ {
+ NPCPhrase (SCAN_ARILOU_SPAWNER);
+ SET_GAME_STATE (SCANNED_SPAWNER, 1);
+ }
+
+ TradeWorld ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ if (GET_GAME_STATE (BOMB_VISITS))
+ NPCPhrase (SUBSEQ_BOMB_WORLD_HELLO);
+ else
+ {
+ NPCPhrase (INIT_BOMB_WORLD_HELLO);
+ SET_GAME_STATE (BOMB_VISITS, 1);
+ }
+
+ BombAmbush ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (ATTACKED_DRUUGE))
+ {
+ NumVisits = GET_GAME_STATE (DRUUGE_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (DRUUGE_VISITS, NumVisits);
+
+ setSegue (Segue_hostile);
+ }
+ else
+ {
+ NumVisits = 0;
+ if (GetHeadLink (&GLOBAL (built_ship_q)) == 0)
+ {
+ for (NumVisits = 0; NumVisits < NUM_MODULE_SLOTS; ++NumVisits)
+ {
+ BYTE which_module;
+
+ which_module = GLOBAL_SIS (ModuleSlots[NumVisits]);
+ if (which_module >= GUN_WEAPON
+ && which_module <= CANNON_WEAPON)
+ {
+ NumVisits = 0;
+ break;
+ }
+ }
+ }
+
+ if (NumVisits)
+ {
+ NumVisits = GET_GAME_STATE (DRUUGE_SALVAGE);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (SALVAGE_YOUR_SHIP_1);
+ break;
+ case 1:
+ NPCPhrase (SALVAGE_YOUR_SHIP_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (DRUUGE_SALVAGE, NumVisits);
+
+ setSegue (Segue_hostile);
+ AttemptedSalvage = TRUE;
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (DRUUGE_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_SPACE_HELLO);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_SPACE_HELLO);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (DRUUGE_VISITS, NumVisits);
+
+ Space ((RESPONSE_REF)0);
+ }
+ }
+}
+
+static COUNT
+uninit_druuge (void)
+{
+ return (0);
+}
+
+static void
+post_druuge_enc (void)
+{
+ if (getSegue () == Segue_hostile
+ && !AttemptedSalvage
+ && !GET_GAME_STATE (DRUUGE_MANNER))
+ {
+ if (!GET_GAME_STATE (ATTACKED_DRUUGE))
+ {
+ SET_GAME_STATE (ATTACKED_DRUUGE, 1);
+ SET_GAME_STATE (DRUUGE_VISITS, 0);
+ }
+ }
+}
+
+LOCDATA*
+init_druuge_comm (void)
+{
+ LOCDATA *retval;
+
+ SlaveryCount = 0;
+ AttemptedSalvage = FALSE;
+
+ druuge_desc.init_encounter_func = Intro;
+ druuge_desc.post_encounter_func = post_druuge_enc;
+ druuge_desc.uninit_encounter_func = uninit_druuge;
+
+ druuge_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ druuge_desc.AlienTextBaseline.y = 70;
+ druuge_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if ((GET_GAME_STATE (DRUUGE_MANNER) == 0
+ && (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7)))
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &druuge_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/druuge/resinst.h b/src/uqm/comm/druuge/resinst.h
new file mode 100644
index 0000000..ca82a22
--- /dev/null
+++ b/src/uqm/comm/druuge/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define DRUUGE_COLOR_MAP "comm.druuge.colortable"
+#define DRUUGE_CONVERSATION_PHRASES "comm.druuge.dialogue"
+#define DRUUGE_FONT "comm.druuge.font"
+#define DRUUGE_MUSIC "comm.druuge.music"
+#define DRUUGE_PMAP_ANIM "comm.druuge.graphics"
diff --git a/src/uqm/comm/druuge/strings.h b/src/uqm/comm/druuge/strings.h
new file mode 100644
index 0000000..e9dfde9
--- /dev/null
+++ b/src/uqm/comm/druuge/strings.h
@@ -0,0 +1,132 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DRUUGE_STRINGS_H
+#define DRUUGE_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ AMBUSH_IS_FIRST_HELLO,
+ INIT_BOMB_WORLD_HELLO,
+ SUBSEQ_BOMB_WORLD_HELLO,
+ whats_up_at_bomb_planet,
+ GEN_INFO_AT_BOMB_PLANET,
+ we_get_bomb,
+ NOT_GET_BOMB,
+ then_we_take_bomb,
+ FIGHT_FOR_BOMB,
+ GOODBYE_FROM_BOMB_PLANET,
+ NOT_ENOUGH_ROOM,
+ TRADE_FOR_SPHERE,
+ no_way,
+ OK_REGULAR_DEAL,
+ way,
+ OK_HERES_SPHERE,
+ whats_the_sphere_again,
+ SPHERE_IS,
+ WE_SELL_FOR_CREW,
+ i_will_never_trade_crew,
+ YOUR_LOSS,
+ isnt_this_slave_trading,
+ NO_SLAVE_TRADE,
+ what_do_with_crew,
+ HAVE_FUN,
+ im_ready_to_buy,
+ THIS_FOR_SALE,
+ HAVE_SPHERE,
+ HAVE_ART_2,
+ HAVE_ART_1,
+ SHIPS_AND_FUEL,
+ BOUGHT_SHIP,
+ BOUGHT_FUEL,
+ BOUGHT_ART_2,
+ BOUGHT_ART_1,
+ BOUGHT_SPHERE,
+ repeat_what_to_sell,
+ INIT_SPACE_HELLO,
+ SUBSEQUENT_SPACE_HELLO,
+ whats_up_in_space,
+ GENERAL_INFO_IN_SPACE_1,
+ GENERAL_INFO_IN_SPACE_2,
+ GENERAL_INFO_IN_SPACE_3,
+ GENERAL_INFO_IN_SPACE_4,
+ GOODBYE_FROM_SPACE,
+ HSTL_TRADE_WORLD_HELLO_1,
+ HSTL_TRADE_WORLD_HELLO_2,
+ HOSTILE_SPACE_HELLO_1,
+ HOSTILE_SPACE_HELLO_2,
+ INITIAL_TRADE_WORLD_HELLO,
+ SSQ_TRADE_WORLD_HELLO_1,
+ SSQ_TRADE_WORLD_HELLO_2,
+ SSQ_TRADE_WORLD_HELLO_3,
+ SSQ_TRADE_WORLD_HELLO_4,
+ whats_up_at_trade_world,
+ GEN_INFO_AT_TRADE_WORLD_1,
+ GEN_INFO_AT_TRADE_WORLD_2,
+ GEN_INFO_AT_TRADE_WORLD_3,
+ GEN_INFO_AT_TRADE_WORLD_4,
+ SCAN_MAIDENS,
+ SCAN_FRAGMENTS,
+ SCAN_DRUUGE_CASTER,
+ SCAN_ARILOU_SPAWNER,
+ ENOUGH_FRAGMENTS,
+ READY_TO_BUY,
+ READY_TO_SELL,
+ BYE_FROM_TRADE_WORLD_1,
+ BYE_FROM_TRADE_WORLD_2,
+ NOT_ENOUGH_CREW,
+ EXCHANGE_MADE,
+ OK_DONE_BUYING,
+ OK_DONE_SELLING,
+ bye,
+ want_to_sell,
+ want_to_buy,
+ buy_druuge_ship,
+ buy_fuel,
+ buy_art_1,
+ buy_art_2,
+ buy_rosy_sphere,
+ done_buying,
+ done_selling,
+ sell_maidens,
+ sell_caster,
+ sell_fragments,
+ sell_spawner,
+ BOUGHT_MAIDENS,
+ BOUGHT_FRAGMENTS,
+ BOUGHT_CASTER,
+ YOU_GET,
+ YOU_ALSO_GET,
+ BOUGHT_SPAWNER,
+ SALVAGE_YOUR_SHIP_1,
+ SALVAGE_YOUR_SHIP_2,
+ DEAL_FOR_STATED_SHIPS,
+ DEAL_FOR_LESS_SHIPS,
+ DEAL_FOR_NO_SHIPS,
+ FUEL0,
+ FUEL1,
+ HIDEOUS_DEAL,
+ BAD_DEAL,
+ FAIR_DEAL,
+ GOOD_DEAL,
+ FINE_DEAL,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/ilwrath/Makeinfo b/src/uqm/comm/ilwrath/Makeinfo
new file mode 100644
index 0000000..3f40e79
--- /dev/null
+++ b/src/uqm/comm/ilwrath/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="ilwrathc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/ilwrath/ilwrathc.c b/src/uqm/comm/ilwrath/ilwrathc.c
new file mode 100644
index 0000000..1afd812
--- /dev/null
+++ b/src/uqm/comm/ilwrath/ilwrathc.c
@@ -0,0 +1,649 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/gameev.h"
+
+
+static LOCDATA ilwrath_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ ILWRATH_PMAP_ANIM, /* AlienFrame */
+ ILWRATH_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_MIDDLE, /* AlienTextValign */
+ ILWRATH_COLOR_MAP, /* AlienColorMap */
+ ILWRATH_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ ILWRATH_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 4, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 6, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 11, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 16, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 21, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 5, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+CombatIsInevitable (RESPONSE_REF R)
+{
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, you_are_weak))
+ NPCPhrase (STRENGTH_NOT_ALL);
+ else if (PLAYER_SAID (R, slay_by_thousands))
+ NPCPhrase (NO_SLAY_BY_THOUSANDS);
+ else if (PLAYER_SAID (R, ease_up))
+ NPCPhrase (NO_EASE_UP);
+ else if (PLAYER_SAID (R, bye_space))
+ NPCPhrase (GOODBYE_AND_DIE_SPACE);
+ else if (PLAYER_SAID (R, bye_homeworld))
+ NPCPhrase (GOODBYE_AND_DIE_HOMEWORLD);
+ else if (PLAYER_SAID (R, want_peace))
+ NPCPhrase (NO_PEACE);
+ else if (PLAYER_SAID (R, want_alliance))
+ NPCPhrase (NO_ALLIANCE);
+ else if (PLAYER_SAID (R, but_evil_is_defined))
+ NPCPhrase (DONT_CONFUSE_US);
+ else if (PLAYER_SAID (R, bye_gods))
+ {
+ NPCPhrase (GOODBYE_GODS);
+
+ setSegue (Segue_peace);
+ }
+ if (PLAYER_SAID (R, whats_up))
+ {
+ NPCPhrase (GENERAL_INFO);
+ Response (bye, CombatIsInevitable);
+ }
+ else if (PLAYER_SAID (R, whats_up_space_1)
+ || PLAYER_SAID (R, whats_up_space_2)
+ || PLAYER_SAID (R, whats_up_space_3)
+ || PLAYER_SAID (R, whats_up_space_4)
+ || PLAYER_SAID (R, whats_up_space_5))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (ILWRATH_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_SPACE_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_SPACE_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_SPACE_4);
+ break;
+ case 4:
+ NPCPhrase (GENERAL_INFO_SPACE_5);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ILWRATH_INFO, NumVisits);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, bye))
+ NPCPhrase (GOODBYE_AND_DIE);
+ else if (PLAYER_SAID (R, where_you_come_from))
+ NPCPhrase (CAME_FROM);
+ if (PLAYER_SAID (R, it_will_be_a_pleasure))
+ NPCPhrase (WHO_BLASTS_WHO);
+ if (PLAYER_SAID (R, surrender))
+ NPCPhrase (NO_SURRENDER);
+ if (PLAYER_SAID (R, be_reasonable))
+ NPCPhrase (NOT_REASONABLE);
+ }
+}
+
+static void IlwrathHome (RESPONSE_REF R);
+
+static void
+IlwrathGods (RESPONSE_REF R)
+{
+ BYTE GodsLeft;
+
+ GodsLeft = FALSE;
+ if (PLAYER_SAID (R, want_info_on_gods))
+ NPCPhrase (SO_MUCH_TO_KNOW);
+ else if (PLAYER_SAID (R, when_start_worship))
+ {
+ NPCPhrase (LONG_AGO);
+
+ DISABLE_PHRASE (when_start_worship);
+ }
+ else if (PLAYER_SAID (R, any_good_gods))
+ {
+ NPCPhrase (KILLED_GOOD_GODS);
+
+ DISABLE_PHRASE (any_good_gods);
+ }
+ else if (PLAYER_SAID (R, how_talk_with_gods))
+ {
+ NPCPhrase (CHANNEL_44);
+
+ DISABLE_PHRASE (how_talk_with_gods);
+ }
+ else if (PLAYER_SAID (R, why_44))
+ {
+ NPCPhrase (BECAUSE_44);
+
+ DISABLE_PHRASE (why_44);
+ }
+
+ if (PHRASE_ENABLED (when_start_worship))
+ {
+ Response (when_start_worship, IlwrathGods);
+ GodsLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (any_good_gods))
+ {
+ Response (any_good_gods, IlwrathGods);
+ GodsLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (how_talk_with_gods))
+ {
+ Response (how_talk_with_gods, IlwrathGods);
+ GodsLeft = TRUE;
+ }
+ else if (PHRASE_ENABLED (why_44))
+ {
+ Response (why_44, IlwrathGods);
+ GodsLeft = TRUE;
+ }
+ Response (enough_gods, IlwrathHome);
+
+ if (!GodsLeft)
+ DISABLE_PHRASE (want_info_on_gods);
+}
+
+static void
+IlwrathInfo (RESPONSE_REF R)
+{
+ BYTE InfoLeft;
+
+ InfoLeft = FALSE;
+ if (PLAYER_SAID (R, want_info_on_ilwrath))
+ NPCPhrase (WHAT_ABOUT_ILWRATH);
+ else if (PLAYER_SAID (R, what_about_physio))
+ {
+ NPCPhrase (ABOUT_PHYSIO);
+
+ DISABLE_PHRASE (what_about_physio);
+ }
+ else if (PLAYER_SAID (R, what_about_history))
+ {
+ NPCPhrase (ABOUT_HISTORY);
+
+ DISABLE_PHRASE (what_about_history);
+ }
+ else if (PLAYER_SAID (R, what_about_culture))
+ {
+ NPCPhrase (ABOUT_CULTURE);
+
+ DISABLE_PHRASE (what_about_culture);
+ }
+ else if (PLAYER_SAID (R, what_about_urquan))
+ {
+ NPCPhrase (URQUAN_TOO_NICE);
+
+ DISABLE_PHRASE (what_about_urquan);
+ }
+ else if (PLAYER_SAID (R, are_you_evil))
+ {
+ NPCPhrase (OF_COURSE_WERE_EVIL);
+
+ DISABLE_PHRASE (are_you_evil);
+ }
+
+ if (PHRASE_ENABLED (what_about_physio))
+ {
+ Response (what_about_physio, IlwrathInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_history))
+ {
+ Response (what_about_history, IlwrathInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_culture))
+ {
+ Response (what_about_culture, IlwrathInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_urquan))
+ {
+ Response (what_about_urquan, IlwrathInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (are_you_evil))
+ {
+ Response (are_you_evil, IlwrathInfo);
+ InfoLeft = TRUE;
+ }
+ else
+ {
+ Response (but_evil_is_defined, CombatIsInevitable);
+ InfoLeft = TRUE;
+ }
+ Response (enough_ilwrath, IlwrathHome);
+
+ if (!InfoLeft)
+ DISABLE_PHRASE (want_info_on_ilwrath);
+}
+
+static void
+IlwrathHome (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (ILWRATH_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_HOME_HELLO);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_HOME_HELLO);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ILWRATH_HOME_VISITS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, enough_gods))
+ NPCPhrase (OK_ENOUGH_GODS);
+ else if (PLAYER_SAID (R, enough_ilwrath))
+ NPCPhrase (OK_ENOUGH_ILWRATH);
+
+ if (PHRASE_ENABLED (want_info_on_gods))
+ {
+ Response (want_info_on_gods, IlwrathGods);
+ }
+ if (PHRASE_ENABLED (want_info_on_ilwrath))
+ {
+ Response (want_info_on_ilwrath, IlwrathInfo);
+ }
+ Response (want_peace, CombatIsInevitable);
+ Response (want_alliance, CombatIsInevitable);
+ Response (bye_homeworld, CombatIsInevitable);
+}
+
+static void GodsSpeak (RESPONSE_REF R);
+
+static void
+GodsOrder (RESPONSE_REF R)
+{
+ BYTE OrdersLeft;
+
+ OrdersLeft = FALSE;
+ if (PLAYER_SAID (R, other_divine_orders))
+ NPCPhrase (WHAT_ORDERS);
+ else if (PLAYER_SAID (R, say_warship))
+ {
+ NPCPhrase (OK_WARSHIP);
+
+ DISABLE_PHRASE (say_warship);
+ }
+ else if (PLAYER_SAID (R, say_dwe))
+ {
+ NPCPhrase (OK_DWE);
+
+ DISABLE_PHRASE (say_dwe);
+ }
+ else if (PLAYER_SAID (R, say_youboo))
+ {
+ NPCPhrase (OK_YOUBOO);
+
+ DISABLE_PHRASE (say_youboo);
+ }
+ else if (PLAYER_SAID (R, say_dillrat))
+ {
+ NPCPhrase (OK_DILRAT);
+
+ DISABLE_PHRASE (say_dillrat);
+ }
+
+ if (PHRASE_ENABLED (say_warship))
+ {
+ Response (say_warship, GodsOrder);
+ OrdersLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (say_dwe))
+ {
+ Response (say_dwe, GodsOrder);
+ OrdersLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (say_youboo))
+ {
+ Response (say_youboo, GodsOrder);
+ OrdersLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (say_dillrat))
+ {
+ Response (say_dillrat, GodsOrder);
+ OrdersLeft = TRUE;
+ }
+ Response (enough_orders, GodsSpeak);
+
+ if (!OrdersLeft)
+ DISABLE_PHRASE (other_divine_orders);
+}
+
+static void
+GodsSpeak (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (R == 0)
+ {
+ if (GET_GAME_STATE (ILWRATH_FIGHT_THRADDASH))
+ NPCPhrase (GLORIOUS_WORSHIP);
+ else if (GET_GAME_STATE (ILWRATH_DECEIVED))
+ NPCPhrase (ON_WAY);
+ else
+ {
+ NumVisits = GET_GAME_STATE (ILWRATH_GODS_SPOKEN);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ILWRATH_BELIEVE);
+ break;
+ case 1:
+ NPCPhrase (GODS_RETURN_1);
+ break;
+ case 2:
+ NPCPhrase (GODS_RETURN_2);
+ break;
+ case 3:
+ NPCPhrase (GODS_RETURN_3);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ILWRATH_GODS_SPOKEN, NumVisits);
+ }
+ }
+ else if (PLAYER_SAID (R, go_kill_thraddash))
+ {
+ NPCPhrase (OK_KILL_THRADDASH);
+
+ SET_GAME_STATE (ILWRATH_DECEIVED, 1);
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, ADVANCE_ILWRATH_MISSION);
+ }
+ else if (PLAYER_SAID (R, worship_us))
+ {
+ NumVisits = GET_GAME_STATE (ILWRATH_WORSHIP);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (WE_WORSHIP_1);
+ break;
+ case 1:
+ NPCPhrase (WE_WORSHIP_2);
+ break;
+ case 2:
+ NPCPhrase (WE_WORSHIP_3);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ILWRATH_WORSHIP, NumVisits);
+
+ DISABLE_PHRASE (worship_us);
+ }
+ else if (PLAYER_SAID (R, enough_orders))
+ NPCPhrase (NEVER_ENOUGH);
+
+ if (!GET_GAME_STATE (ILWRATH_DECEIVED))
+ Response (go_kill_thraddash, GodsSpeak);
+ if (PHRASE_ENABLED (worship_us))
+ Response (worship_us, GodsSpeak);
+ if (PHRASE_ENABLED (other_divine_orders))
+ {
+ Response (other_divine_orders, GodsOrder);
+ }
+ Response (bye_gods, CombatIsInevitable);
+}
+
+static void
+IlwrathSpace (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (R == 0)
+ {
+ NumVisits = GET_GAME_STATE (ILWRATH_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_HELLO_SPACE);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_HELLO_SPACE_1);
+ break;
+ case 2:
+ NPCPhrase (SUBSEQUENT_HELLO_SPACE_2);
+ break;
+ case 3:
+ NPCPhrase (SUBSEQUENT_HELLO_SPACE_3);
+ break;
+ case 4:
+ NPCPhrase (SUBSEQUENT_HELLO_SPACE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ILWRATH_VISITS, NumVisits);
+ }
+
+ NumVisits = GET_GAME_STATE (ILWRATH_INFO);
+ switch (NumVisits)
+ {
+ case 0:
+ Response (whats_up_space_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (whats_up_space_2, CombatIsInevitable);
+ break;
+ case 2:
+ Response (whats_up_space_3, CombatIsInevitable);
+ break;
+ case 3:
+ Response (whats_up_space_4, CombatIsInevitable);
+ break;
+ case 4:
+ Response (whats_up_space_5, CombatIsInevitable);
+ break;
+ }
+ Response (you_are_weak, CombatIsInevitable);
+ Response (slay_by_thousands, CombatIsInevitable);
+ Response (ease_up, CombatIsInevitable);
+ Response (bye_space, CombatIsInevitable);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (GET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER))
+ {
+ NPCPhrase (SEND_MESSAGE);
+
+ Response (where_you_come_from, CombatIsInevitable);
+ Response (it_will_be_a_pleasure, CombatIsInevitable);
+ Response (surrender, CombatIsInevitable);
+ Response (be_reasonable, CombatIsInevitable);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ IlwrathHome ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ NumVisits = GET_GAME_STATE (ILWRATH_CHMMR_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_CHMMR_HELLO);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_CHMMR_HELLO);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ILWRATH_CHMMR_VISITS, NumVisits);
+
+ Response (whats_up, CombatIsInevitable);
+ Response (bye, CombatIsInevitable);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 5))
+ {
+ // Communicating with an Ilwrath ship using a HyperWave Broadcaster.
+ if (GET_GAME_STATE (ILWRATH_FIGHT_THRADDASH))
+ NPCPhrase (BIG_FUN);
+ else if (GET_GAME_STATE (ILWRATH_DECEIVED))
+ NPCPhrase (FAST_AS_CAN);
+ else
+ NPCPhrase (JUST_GRUNTS);
+
+ setSegue (Segue_peace);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 4))
+ {
+ // Communicating with the Ilwrath homeworld using a
+ // Hyperwave Broadcaster.
+ GodsSpeak ((RESPONSE_REF)0);
+ }
+ else
+ {
+ setSegue (Segue_peace);
+
+ if (GET_GAME_STATE (ILWRATH_FIGHT_THRADDASH))
+ NPCPhrase (HAPPY_FIGHTING_THRADDASH);
+ else if (GET_GAME_STATE (ILWRATH_DECEIVED))
+ NPCPhrase (ON_WAY_TO_THRADDASH);
+ else
+ IlwrathSpace ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_ilwrath (void)
+{
+ return (0);
+}
+
+static void
+post_ilwrath_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_ilwrath_comm (void)
+{
+ LOCDATA *retval;
+
+ ilwrath_desc.init_encounter_func = Intro;
+ ilwrath_desc.post_encounter_func = post_ilwrath_enc;
+ ilwrath_desc.uninit_encounter_func = uninit_ilwrath;
+
+ ilwrath_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ ilwrath_desc.AlienTextBaseline.y = 70;
+ ilwrath_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (GET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER)
+ || (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA)
+ & ((1 << 4) | (1 << 5)))
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &ilwrath_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/ilwrath/resinst.h b/src/uqm/comm/ilwrath/resinst.h
new file mode 100644
index 0000000..7063e39
--- /dev/null
+++ b/src/uqm/comm/ilwrath/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ILWRATH_COLOR_MAP "comm.ilwrath.colortable"
+#define ILWRATH_CONVERSATION_PHRASES "comm.ilwrath.dialogue"
+#define ILWRATH_FONT "comm.ilwrath.font"
+#define ILWRATH_MUSIC "comm.ilwrath.music"
+#define ILWRATH_PMAP_ANIM "comm.ilwrath.graphics"
diff --git a/src/uqm/comm/ilwrath/strings.h b/src/uqm/comm/ilwrath/strings.h
new file mode 100644
index 0000000..9cd4d26
--- /dev/null
+++ b/src/uqm/comm/ilwrath/strings.h
@@ -0,0 +1,135 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ILWRATH_STRINGS_H
+#define ILWRATH_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ NEVER_ENOUGH,
+ OK_WARSHIP,
+ OK_DWE,
+ OK_YOUBOO,
+ OK_DILRAT,
+ BIG_FUN,
+ FAST_AS_CAN,
+ GLORIOUS_WORSHIP,
+ ON_WAY,
+ GODS_RETURN_1,
+ GODS_RETURN_2,
+ GODS_RETURN_3,
+ JUST_GRUNTS,
+ GRUNTS_AGAIN,
+ WHAT_ORDERS,
+ WE_WORSHIP_1,
+ WE_WORSHIP_2,
+ WE_WORSHIP_3,
+ SUBSEQUENT_CHMMR_HELLO,
+ INIT_CHMMR_HELLO,
+ OK_ENOUGH_ILWRATH,
+ OK_ENOUGH_GODS,
+ SEND_MESSAGE,
+ CAME_FROM,
+ WHO_BLASTS_WHO,
+ NO_SURRENDER,
+ NOT_REASONABLE,
+ SUBSEQUENT_HOME_HELLO,
+ GENERAL_INFO,
+ GOODBYE_AND_DIE,
+ DECEIVERS,
+ NO_PEACE,
+ NO_ALLIANCE,
+ ILWRATH_BELIEVE,
+ OK_KILL_THRADDASH,
+ GOODBYE_GODS,
+ INIT_HELLO_SPACE,
+ SUBSEQUENT_HELLO_SPACE_1,
+ SUBSEQUENT_HELLO_SPACE_2,
+ SUBSEQUENT_HELLO_SPACE_3,
+ SUBSEQUENT_HELLO_SPACE_4,
+ GENERAL_INFO_SPACE_1,
+ GENERAL_INFO_SPACE_2,
+ GENERAL_INFO_SPACE_3,
+ GENERAL_INFO_SPACE_4,
+ GENERAL_INFO_SPACE_5,
+ STRENGTH_NOT_ALL,
+ NO_SLAY_BY_THOUSANDS,
+ NO_EASE_UP,
+ GOODBYE_AND_DIE_SPACE,
+ INIT_HOME_HELLO,
+ GOODBYE_AND_DIE_HOMEWORLD,
+ SO_MUCH_TO_KNOW,
+ LONG_AGO,
+ KILLED_GOOD_GODS,
+ CHANNEL_44,
+ BECAUSE_44,
+ WHAT_ABOUT_ILWRATH,
+ ABOUT_PHYSIO,
+ ABOUT_HISTORY,
+ ABOUT_CULTURE,
+ ABOUT_URQUAN,
+ URQUAN_TOO_NICE,
+ OF_COURSE_WERE_EVIL,
+ DONT_CONFUSE_US,
+ ON_WAY_TO_THRADDASH,
+ HAPPY_FIGHTING_THRADDASH,
+
+ say_warship,
+ say_dwe,
+ say_youboo,
+ say_dillrat,
+ enough_orders,
+ other_divine_orders,
+ worship_us,
+ bye_gods,
+ enough_ilwrath,
+ enough_gods,
+ where_you_come_from,
+ it_will_be_a_pleasure,
+ be_reasonable,
+ surrender,
+ whats_up,
+ bye,
+ want_peace,
+ want_alliance,
+ go_kill_thraddash,
+ whats_up_space_1,
+ whats_up_space_2,
+ whats_up_space_3,
+ whats_up_space_4,
+ whats_up_space_5,
+ you_are_weak,
+ slay_by_thousands,
+ ease_up,
+ bye_space,
+ bye_homeworld,
+ want_info_on_gods,
+ when_start_worship,
+ any_good_gods,
+ how_talk_with_gods,
+ why_44,
+ want_info_on_ilwrath,
+ what_about_physio,
+ what_about_history,
+ what_about_culture,
+ what_about_urquan,
+ are_you_evil,
+ but_evil_is_defined,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/melnorm/Makeinfo b/src/uqm/comm/melnorm/Makeinfo
new file mode 100644
index 0000000..5e3a214
--- /dev/null
+++ b/src/uqm/comm/melnorm/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="melnorm.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/melnorm/melnorm.c b/src/uqm/comm/melnorm/melnorm.c
new file mode 100644
index 0000000..00b6a07
--- /dev/null
+++ b/src/uqm/comm/melnorm/melnorm.c
@@ -0,0 +1,1855 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/gameev.h"
+#include "uqm/shipcont.h"
+#include "libs/inplib.h"
+#include "libs/mathlib.h"
+
+#include "uqm/hyper.h"
+ // for SOL_X/SOL_Y
+#include "uqm/planets/planets.h"
+ // for xxx_DISASTER
+#include "uqm/sis.h"
+
+
+static const NUMBER_SPEECH_DESC melnorme_numbers_english;
+
+static LOCDATA melnorme_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ MELNORME_PMAP_ANIM, /* AlienFrame */
+ MELNORME_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ MELNORME_COLOR_MAP, /* AlienColorMap */
+ MELNORME_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ MELNORME_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 4, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 6, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND * 4, ONE_SECOND * 4,/* RestartRate */
+ (1 << 1), /* BlockMask */
+ },
+ {
+ 11, /* StartIndex */
+ 9, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND * 4, ONE_SECOND * 4,/* RestartRate */
+ (1 << 0), /* BlockMask */
+ },
+ {
+ 20, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 5, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ &melnorme_numbers_english, /* AlienNumberSpeech - default */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static COUNT melnorme_digit_names[] =
+{
+ ENUMERATE_ZERO,
+ ENUMERATE_ONE,
+ ENUMERATE_TWO,
+ ENUMERATE_THREE,
+ ENUMERATE_FOUR,
+ ENUMERATE_FIVE,
+ ENUMERATE_SIX,
+ ENUMERATE_SEVEN,
+ ENUMERATE_EIGHT,
+ ENUMERATE_NINE
+};
+
+static COUNT melnorme_teen_names[] =
+{
+ ENUMERATE_TEN,
+ ENUMERATE_ELEVEN,
+ ENUMERATE_TWELVE,
+ ENUMERATE_THIRTEEN,
+ ENUMERATE_FOURTEEN,
+ ENUMERATE_FIFTEEN,
+ ENUMERATE_SIXTEEN,
+ ENUMERATE_SEVENTEEN,
+ ENUMERATE_EIGHTEEN,
+ ENUMERATE_NINETEEN
+};
+
+static COUNT melnorme_tens_names[] =
+{
+ 0, /* invalid */
+ 0, /* skip digit */
+ ENUMERATE_TWENTY,
+ ENUMERATE_THIRTY,
+ ENUMERATE_FOURTY,
+ ENUMERATE_FIFTY,
+ ENUMERATE_SIXTY,
+ ENUMERATE_SEVENTY,
+ ENUMERATE_EIGHTY,
+ ENUMERATE_NINETY
+};
+
+static const NUMBER_SPEECH_DESC melnorme_numbers_english =
+{
+ 5, /* NumDigits */
+ {
+ { /* 1000-999999 */
+ 1000, /* Divider */
+ 0, /* Subtrahend */
+ NULL, /* StrDigits - recurse */
+ NULL, /* Names - not used */
+ ENUMERATE_THOUSAND /* CommonIndex */
+ },
+ { /* 100-999 */
+ 100, /* Divider */
+ 0, /* Subtrahend */
+ melnorme_digit_names, /* StrDigits */
+ NULL, /* Names - not used */
+ ENUMERATE_HUNDRED /* CommonIndex */
+ },
+ { /* 20-99 */
+ 10, /* Divider */
+ 0, /* Subtrahend */
+ melnorme_tens_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ },
+ { /* 10-19 */
+ 1, /* Divider */
+ 10, /* Subtrahend */
+ melnorme_teen_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ },
+ { /* 0-9 */
+ 1, /* Divider */
+ 0, /* Subtrahend */
+ melnorme_digit_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ }
+ }
+};
+
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof (*array))
+
+
+//////////////Technology System///////////////////////
+// This section deals with enabling and checking for
+// various technologies. It should probably be
+// migrated to its own file.
+
+// Identifiers for the various technologies
+typedef enum
+{
+ TECH_MODULE_BLASTER,
+ TECH_LANDER_SPEED,
+ TECH_MODULE_ANTIMISSILE,
+ TECH_LANDER_SHIELD_BIO,
+ TECH_LANDER_CARGO,
+ TECH_MODULE_BIGFUELTANK,
+ TECH_LANDER_RAPIDFIRE,
+ TECH_LANDER_SHIELD_QUAKE,
+ TECH_MODULE_TRACKING,
+ TECH_LANDER_SHIELD_LIGHTNING,
+ TECH_LANDER_SHIELD_HEAT,
+ TECH_MODULE_CANNON,
+ TECH_MODULE_FURNACE,
+} TechId_t;
+
+// Group the technologies into three subtypes
+typedef enum
+{
+ TECH_TYPE_MODULE, // Flagship modules
+ // subtype = moduleId, info = cost
+ // Cost will be scaled by MODULE_COST_SCALE.
+ TECH_TYPE_LANDER_SHIELD, // Lander shield enhancements
+ // subtype = disaster type, info = unused
+ TECH_TYPE_STATE // Other game state changes
+ // subtype = stateId, info = state value
+} TechType_t;
+
+
+// Define the information specifying a particular technology
+typedef struct
+{
+ TechId_t id; // ID of the technology
+ TechType_t type; // Type of the technology
+ int subtype; // Subtype of the technology
+ int info; // Supplemental information
+} TechData;
+
+
+// A table of the available technologies.
+// This should really be an associative map of TechIds to tech data records,
+// but implementing that would be excessive.
+static const TechData tech_data_table[] =
+{
+ // Tech ID Tech Type, Supplemental info
+ { TECH_MODULE_BLASTER, TECH_TYPE_MODULE, BLASTER_WEAPON, 4000 },
+ { TECH_LANDER_SPEED, TECH_TYPE_STATE, IMPROVED_LANDER_SPEED, 1 },
+ { TECH_MODULE_ANTIMISSILE, TECH_TYPE_MODULE, ANTIMISSILE_DEFENSE, 4000 },
+ { TECH_LANDER_SHIELD_BIO, TECH_TYPE_LANDER_SHIELD, BIOLOGICAL_DISASTER, -1 },
+ { TECH_LANDER_CARGO, TECH_TYPE_STATE, IMPROVED_LANDER_CARGO, 1 },
+ { TECH_MODULE_BIGFUELTANK, TECH_TYPE_MODULE, HIGHEFF_FUELSYS, 1000 },
+ { TECH_LANDER_RAPIDFIRE, TECH_TYPE_STATE, IMPROVED_LANDER_SHOT, 1 },
+ { TECH_LANDER_SHIELD_QUAKE, TECH_TYPE_LANDER_SHIELD, EARTHQUAKE_DISASTER, -1 },
+ { TECH_MODULE_TRACKING, TECH_TYPE_MODULE, TRACKING_SYSTEM, 5000 },
+ { TECH_LANDER_SHIELD_LIGHTNING, TECH_TYPE_LANDER_SHIELD, LIGHTNING_DISASTER, -1 },
+ { TECH_LANDER_SHIELD_HEAT, TECH_TYPE_LANDER_SHIELD, LAVASPOT_DISASTER, -1 },
+ { TECH_MODULE_CANNON, TECH_TYPE_MODULE, CANNON_WEAPON, 6000 },
+ { TECH_MODULE_FURNACE, TECH_TYPE_MODULE, SHIVA_FURNACE, 4000 },
+};
+const size_t NUM_TECHNOLOGIES = ARRAY_SIZE (tech_data_table);
+
+// Lookup function to get the data for a particular tech
+static const TechData*
+GetTechData (TechId_t techId)
+{
+ size_t i = 0;
+ for (i = 0; i < NUM_TECHNOLOGIES; ++i)
+ {
+ if (tech_data_table[i].id == techId)
+ return &tech_data_table[i];
+ }
+ return NULL;
+}
+
+
+// We have to explicitly switch on the state ID because the xxx_GAME_STATE
+// macros use preprocessor stringizing.
+static bool
+HasStateTech (int stateId)
+{
+ switch (stateId)
+ {
+ case IMPROVED_LANDER_SPEED:
+ return GET_GAME_STATE (IMPROVED_LANDER_SPEED);
+ case IMPROVED_LANDER_CARGO:
+ return GET_GAME_STATE (IMPROVED_LANDER_CARGO);
+ case IMPROVED_LANDER_SHOT:
+ return GET_GAME_STATE (IMPROVED_LANDER_SHOT);
+ }
+ return false;
+}
+
+static void
+GrantStateTech (int stateId, BYTE value)
+{
+ switch (stateId)
+ {
+ case IMPROVED_LANDER_SPEED:
+ SET_GAME_STATE (IMPROVED_LANDER_SPEED, value);
+ return;
+ case IMPROVED_LANDER_CARGO:
+ SET_GAME_STATE (IMPROVED_LANDER_CARGO, value);
+ return;
+ case IMPROVED_LANDER_SHOT:
+ SET_GAME_STATE (IMPROVED_LANDER_SHOT, value);
+ return;
+ }
+}
+
+static bool
+HasTech (TechId_t techId)
+{
+ const TechData* techData = GetTechData (techId);
+ if (!techData)
+ return false;
+
+ switch (techData->type)
+ {
+ case TECH_TYPE_MODULE:
+ return GLOBAL (ModuleCost[techData->subtype]) != 0;
+ case TECH_TYPE_LANDER_SHIELD:
+ return (GET_GAME_STATE (LANDER_SHIELDS) & (1 << techData->subtype)) != 0;
+ case TECH_TYPE_STATE:
+ return HasStateTech (techData->subtype);
+ }
+ return false;
+}
+
+static void
+GrantTech (TechId_t techId)
+{
+ const TechData* techData = GetTechData (techId);
+ if (!techData)
+ return;
+
+ switch (techData->type)
+ {
+ case TECH_TYPE_MODULE:
+ GLOBAL (ModuleCost[techData->subtype]) = techData->info / MODULE_COST_SCALE;
+ return;
+ case TECH_TYPE_LANDER_SHIELD:
+ {
+ COUNT state = GET_GAME_STATE (LANDER_SHIELDS) | (1 << techData->subtype);
+ SET_GAME_STATE (LANDER_SHIELDS, state);
+ return;
+ }
+ case TECH_TYPE_STATE:
+ GrantStateTech (techData->subtype, techData->info);
+ return;
+ }
+}
+
+
+////////////Melnorme Sales System///////////
+// This section contains code related to Melnorme sales
+
+// Many of the conversation lines in strings.h fall into groups
+// of sequential responses. These structures allow those
+// responses to be interated through.
+static const int ok_buy_event_lines[] =
+{
+ OK_BUY_EVENT_1, OK_BUY_EVENT_2, OK_BUY_EVENT_3, OK_BUY_EVENT_4,
+ OK_BUY_EVENT_5, OK_BUY_EVENT_6, OK_BUY_EVENT_7, OK_BUY_EVENT_8
+};
+const size_t NUM_EVENT_ITEMS = ARRAY_SIZE (ok_buy_event_lines);
+
+static const int ok_buy_alien_race_lines[] =
+{
+ OK_BUY_ALIEN_RACE_1, OK_BUY_ALIEN_RACE_2, OK_BUY_ALIEN_RACE_3,
+ OK_BUY_ALIEN_RACE_4, OK_BUY_ALIEN_RACE_5, OK_BUY_ALIEN_RACE_6,
+ OK_BUY_ALIEN_RACE_7, OK_BUY_ALIEN_RACE_8, OK_BUY_ALIEN_RACE_9,
+ OK_BUY_ALIEN_RACE_10, OK_BUY_ALIEN_RACE_11, OK_BUY_ALIEN_RACE_12,
+ OK_BUY_ALIEN_RACE_13, OK_BUY_ALIEN_RACE_14, OK_BUY_ALIEN_RACE_15,
+ OK_BUY_ALIEN_RACE_16
+};
+const size_t NUM_ALIEN_RACE_ITEMS = ARRAY_SIZE (ok_buy_alien_race_lines);
+
+static const int ok_buy_history_lines[] =
+{
+ OK_BUY_HISTORY_1, OK_BUY_HISTORY_2, OK_BUY_HISTORY_3,
+ OK_BUY_HISTORY_4, OK_BUY_HISTORY_5, OK_BUY_HISTORY_6,
+ OK_BUY_HISTORY_7, OK_BUY_HISTORY_8, OK_BUY_HISTORY_9
+};
+const size_t NUM_HISTORY_ITEMS = ARRAY_SIZE (ok_buy_history_lines);
+
+static const int hello_and_down_to_business_lines[] =
+{
+ HELLO_AND_DOWN_TO_BUSINESS_1, HELLO_AND_DOWN_TO_BUSINESS_2,
+ HELLO_AND_DOWN_TO_BUSINESS_3, HELLO_AND_DOWN_TO_BUSINESS_4,
+ HELLO_AND_DOWN_TO_BUSINESS_5, HELLO_AND_DOWN_TO_BUSINESS_6,
+ HELLO_AND_DOWN_TO_BUSINESS_7, HELLO_AND_DOWN_TO_BUSINESS_8,
+ HELLO_AND_DOWN_TO_BUSINESS_9, HELLO_AND_DOWN_TO_BUSINESS_10
+};
+const size_t NUM_HELLO_LINES = ARRAY_SIZE (hello_and_down_to_business_lines);
+
+static const int rescue_lines[] =
+{
+ RESCUE_EXPLANATION, RESCUE_AGAIN_1, RESCUE_AGAIN_2,
+ RESCUE_AGAIN_3, RESCUE_AGAIN_4, RESCUE_AGAIN_5
+};
+const size_t NUM_RESCUE_LINES = ARRAY_SIZE (rescue_lines);
+
+// How many lines are available in the given array?
+static size_t
+GetNumLines (const int array[])
+{
+ if (array == ok_buy_event_lines)
+ return NUM_EVENT_ITEMS;
+ else if (array == ok_buy_alien_race_lines)
+ return NUM_ALIEN_RACE_ITEMS;
+ else if (array == ok_buy_history_lines)
+ return NUM_HISTORY_ITEMS;
+ else if (array == hello_and_down_to_business_lines)
+ return NUM_HELLO_LINES;
+ else if (array == rescue_lines)
+ return NUM_RESCUE_LINES;
+ return 0;
+}
+
+// Get the line, with range checking.
+// Returns the last line if the desired one is out of range.
+static int
+GetLineSafe (const int array[], size_t linenum)
+{
+ const size_t array_size = GetNumLines (array);
+ assert (array_size > 0);
+ if (linenum >= array_size)
+ linenum = array_size - 1;
+ return array[linenum];
+}
+
+// Data structure to hold the Melnorme's info on a technology
+typedef struct
+{
+ TechId_t techId; // ID of technology
+ int price; // Melnorme's price to sell
+ int sale_line; // Sales pitch line ID
+ int sold_line; // Post-sale line ID
+} TechSaleData;
+
+// Right now, all techs have the same price.
+#define TECHPRICE (75 * BIO_CREDIT_VALUE)
+
+static const TechSaleData tech_sale_catalog[] =
+{
+ { TECH_MODULE_BLASTER, TECHPRICE, NEW_TECH_1, OK_BUY_NEW_TECH_1 },
+ { TECH_LANDER_SPEED, TECHPRICE, NEW_TECH_2, OK_BUY_NEW_TECH_2 },
+ { TECH_MODULE_ANTIMISSILE, TECHPRICE, NEW_TECH_3, OK_BUY_NEW_TECH_3 },
+ { TECH_LANDER_SHIELD_BIO, TECHPRICE, NEW_TECH_4, OK_BUY_NEW_TECH_4 },
+ { TECH_LANDER_CARGO, TECHPRICE, NEW_TECH_5, OK_BUY_NEW_TECH_5 },
+ { TECH_MODULE_BIGFUELTANK, TECHPRICE, NEW_TECH_6, OK_BUY_NEW_TECH_6 },
+ { TECH_LANDER_RAPIDFIRE, TECHPRICE, NEW_TECH_7, OK_BUY_NEW_TECH_7 },
+ { TECH_LANDER_SHIELD_QUAKE, TECHPRICE, NEW_TECH_8, OK_BUY_NEW_TECH_8 },
+ { TECH_MODULE_TRACKING, TECHPRICE, NEW_TECH_9, OK_BUY_NEW_TECH_9 },
+ { TECH_LANDER_SHIELD_LIGHTNING, TECHPRICE, NEW_TECH_10, OK_BUY_NEW_TECH_10 },
+ { TECH_LANDER_SHIELD_HEAT, TECHPRICE, NEW_TECH_11, OK_BUY_NEW_TECH_11 },
+ { TECH_MODULE_CANNON, TECHPRICE, NEW_TECH_12, OK_BUY_NEW_TECH_12 },
+ { TECH_MODULE_FURNACE, TECHPRICE, NEW_TECH_13, OK_BUY_NEW_TECH_13 },
+};
+const size_t NUM_TECH_ITEMS = ARRAY_SIZE (tech_sale_catalog);
+
+// Return the next tech for sale that the player doesn't already have.
+// Returns NULL if the player has all the techs.
+static const TechSaleData*
+GetNextTechForSale (void)
+{
+ size_t i = 0;
+ for (i = 0; i < NUM_TECH_ITEMS; ++i)
+ {
+ if (!HasTech (tech_sale_catalog[i].techId))
+ return &tech_sale_catalog[i];
+ }
+
+ return NULL;
+}
+
+///////////End Melnorme Sales Section//////////////////
+
+static StatMsgMode prevMsgMode;
+
+static void DoFirstMeeting (RESPONSE_REF R);
+
+static COUNT
+ShipWorth (void)
+{
+ BYTE i;
+ SBYTE crew_pods;
+ COUNT worth;
+
+ worth = GLOBAL_SIS (NumLanders)
+ * GLOBAL (ModuleCost[PLANET_LANDER]);
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (DriveSlots[i]) < EMPTY_SLOT)
+ worth += GLOBAL (ModuleCost[FUSION_THRUSTER]);
+ }
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (JetSlots[i]) < EMPTY_SLOT)
+ worth += GLOBAL (ModuleCost[TURNING_JETS]);
+ }
+
+ crew_pods = -(SBYTE)(
+ (GLOBAL_SIS (CrewEnlisted) + CREW_POD_CAPACITY - 1)
+ / CREW_POD_CAPACITY
+ );
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ BYTE which_module;
+
+ which_module = GLOBAL_SIS (ModuleSlots[i]);
+ if (which_module < BOMB_MODULE_0
+ && (which_module != CREW_POD || ++crew_pods > 0))
+ {
+ worth += GLOBAL (ModuleCost[which_module]);
+ }
+ }
+
+ return (worth);
+}
+
+static COUNT rescue_fuel;
+static SIS_STATE SIS_copy;
+
+// Extract method to return the response string index
+// for stripping a given module.
+static int
+GetStripModuleRef (int moduleID)
+{
+ switch (moduleID)
+ {
+ case PLANET_LANDER: return LANDERS;
+ case FUSION_THRUSTER: return THRUSTERS;
+ case TURNING_JETS: return JETS;
+ case CREW_POD: return PODS;
+ case STORAGE_BAY: return BAYS;
+ case DYNAMO_UNIT: return DYNAMOS;
+ case SHIVA_FURNACE: return FURNACES;
+ case GUN_WEAPON: return GUNS;
+ case BLASTER_WEAPON: return BLASTERS;
+ case CANNON_WEAPON: return CANNONS;
+ case TRACKING_SYSTEM: return TRACKERS;
+ case ANTIMISSILE_DEFENSE: return DEFENSES;
+ // If a modder has added new modules, should it really
+ // be a fatal error if the Melnorme don't know about
+ // them?
+ default:
+ assert (0 && "Unknown module");
+ }
+ return 0;
+}
+
+static DWORD
+getStripRandomSeed (void)
+{
+ DWORD x, y;
+ // We truncate the location because encounters move the ship slightly in
+ // HSpace, and throw some other relatively immutable values in the mix to
+ // vary the deal when stuck at the same general location again.
+ // It is still possible but unlikely for encounters to move the ship into
+ // another truncation sector so the player could choose from 2 deals.
+ x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)) / 100;
+ y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y)) / 100;
+ // prime numbers help randomness
+ return y * 1013 + x + GLOBAL_SIS (NumLanders)
+ + GLOBAL_SIS (ModuleSlots[1]) + GLOBAL_SIS (ModuleSlots[4])
+ + GLOBAL_SIS (ModuleSlots[7]) + GLOBAL_SIS (ModuleSlots[10]);
+}
+
+static BOOLEAN
+StripShip (COUNT fuel_required)
+{
+ BYTE i, which_module;
+ SBYTE crew_pods;
+
+ SET_GAME_STATE (MELNORME_RESCUE_REFUSED, 0);
+
+ crew_pods = -(SBYTE)(
+ (GLOBAL_SIS (CrewEnlisted) + CREW_POD_CAPACITY - 1)
+ / CREW_POD_CAPACITY
+ );
+ if (fuel_required == 0)
+ {
+ GlobData.SIS_state = SIS_copy;
+ DeltaSISGauges (UNDEFINED_DELTA, rescue_fuel, UNDEFINED_DELTA);
+ }
+ else if (fuel_required == (COUNT)~0)
+ {
+ GLOBAL_SIS (NumLanders) = 0;
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ GLOBAL_SIS (DriveSlots[i]) = EMPTY_SLOT + 0;
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ GLOBAL_SIS (JetSlots[i]) = EMPTY_SLOT + 1;
+ if (GLOBAL_SIS (FuelOnBoard) > FUEL_RESERVE)
+ GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE;
+ GLOBAL_SIS (TotalBioMass) = 0;
+ GLOBAL_SIS (TotalElementMass) = 0;
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i)
+ GLOBAL_SIS (ElementAmounts[i]) = 0;
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ which_module = GLOBAL_SIS (ModuleSlots[i]);
+ if (which_module < BOMB_MODULE_0
+ && (which_module != CREW_POD
+ || ++crew_pods > 0))
+ GLOBAL_SIS (ModuleSlots[i]) = EMPTY_SLOT + 2;
+ }
+
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+ }
+ else if (fuel_required)
+ {
+ SBYTE bays;
+ BYTE num_searches, beg_mod, end_mod;
+ COUNT worth, total;
+ BYTE module_count[BOMB_MODULE_0];
+ BYTE slot;
+ DWORD capacity;
+ RandomContext *rc;
+
+ // Bug #567
+ // In order to offer the same deal each time if it is refused, we seed
+ // the random number generator with our location, thus making the deal
+ // a repeatable pseudo-random function of where we got stuck and what,
+ // exactly, is on our ship.
+ rc = RandomContext_New();
+ RandomContext_SeedRandom (rc, getStripRandomSeed ());
+
+ SIS_copy = GlobData.SIS_state;
+ for (i = PLANET_LANDER; i < BOMB_MODULE_0; ++i)
+ module_count[i] = 0;
+
+ capacity = FUEL_RESERVE;
+ slot = NUM_MODULE_SLOTS - 1;
+ do
+ {
+ if (SIS_copy.ModuleSlots[slot] == FUEL_TANK
+ || SIS_copy.ModuleSlots[slot] == HIGHEFF_FUELSYS)
+ {
+ COUNT volume;
+
+ volume = SIS_copy.ModuleSlots[slot] == FUEL_TANK
+ ? FUEL_TANK_CAPACITY : HEFUEL_TANK_CAPACITY;
+ capacity += volume;
+ }
+ } while (slot--);
+ if (fuel_required > capacity)
+ fuel_required = capacity;
+
+ bays = -(SBYTE)(
+ (SIS_copy.TotalElementMass + STORAGE_BAY_CAPACITY - 1)
+ / STORAGE_BAY_CAPACITY
+ );
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ which_module = SIS_copy.ModuleSlots[i];
+ if (which_module == CREW_POD)
+ ++crew_pods;
+ else if (which_module == STORAGE_BAY)
+ ++bays;
+ }
+
+ worth = fuel_required / FUEL_TANK_SCALE;
+ total = 0;
+ num_searches = 0;
+ beg_mod = end_mod = (BYTE)~0;
+ while (total < worth && ShipWorth () && ++num_searches)
+ {
+ DWORD rand_val;
+
+ rand_val = RandomContext_Random (rc);
+ switch (which_module = LOBYTE (LOWORD (rand_val)) % (CREW_POD + 1))
+ {
+ case PLANET_LANDER:
+ if (SIS_copy.NumLanders == 0)
+ continue;
+ --SIS_copy.NumLanders;
+ break;
+ case FUSION_THRUSTER:
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ if (SIS_copy.DriveSlots[i] < EMPTY_SLOT)
+ break;
+ }
+ if (i == NUM_DRIVE_SLOTS)
+ continue;
+ SIS_copy.DriveSlots[i] = EMPTY_SLOT + 0;
+ break;
+ case TURNING_JETS:
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ if (SIS_copy.JetSlots[i] < EMPTY_SLOT)
+ break;
+ }
+ if (i == NUM_JET_SLOTS)
+ continue;
+ SIS_copy.JetSlots[i] = EMPTY_SLOT + 1;
+ break;
+ case CREW_POD:
+ i = HIBYTE (LOWORD (rand_val)) % NUM_MODULE_SLOTS;
+ which_module = SIS_copy.ModuleSlots[i];
+ if (which_module >= BOMB_MODULE_0
+ || which_module == FUEL_TANK
+ || which_module == HIGHEFF_FUELSYS
+ || (which_module == STORAGE_BAY
+ && module_count[STORAGE_BAY] >= bays)
+ || (which_module == CREW_POD
+ && module_count[CREW_POD] >= crew_pods))
+ continue;
+ SIS_copy.ModuleSlots[i] = EMPTY_SLOT + 2;
+ break;
+ }
+
+ if (beg_mod == (BYTE)~0)
+ beg_mod = end_mod = which_module;
+ else if (which_module > end_mod)
+ end_mod = which_module;
+ ++module_count[which_module];
+ total += GLOBAL (ModuleCost[which_module]);
+ }
+ RandomContext_Delete (rc);
+
+ if (total == 0)
+ {
+ NPCPhrase (CHARITY);
+ DeltaSISGauges (0, fuel_required, 0);
+ return (FALSE);
+ }
+ else
+ {
+ NPCPhrase (RESCUE_OFFER);
+ rescue_fuel = fuel_required;
+ if (rescue_fuel == capacity)
+ NPCPhrase (RESCUE_TANKS);
+ else
+ NPCPhrase (RESCUE_HOME);
+ for (i = PLANET_LANDER; i < BOMB_MODULE_0; ++i)
+ {
+ if (module_count[i])
+ {
+ if (i == end_mod && i != beg_mod)
+ NPCPhrase (END_LIST_WITH_AND);
+ NPCPhrase (ENUMERATE_ONE + (module_count[i] - 1));
+ NPCPhrase (GetStripModuleRef (i));
+ }
+ }
+ }
+ }
+
+ return (TRUE);
+}
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, no_trade_now))
+ NPCPhrase (OK_NO_TRADE_NOW_BYE);
+ else if (PLAYER_SAID (R, youre_on))
+ {
+ NPCPhrase (YOU_GIVE_US_NO_CHOICE);
+
+ SET_GAME_STATE (MELNORME_ANGER, 1);
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, so_we_can_attack))
+ {
+ NPCPhrase (DECEITFUL_HUMAN);
+
+ SET_GAME_STATE (MELNORME_ANGER, 2);
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, bye_melnorme_slightly_angry))
+ NPCPhrase (MELNORME_SLIGHTLY_ANGRY_GOODBYE);
+ else if (PLAYER_SAID (R, ok_strip_me))
+ {
+ if (ShipWorth () < 4000 / MODULE_COST_SCALE)
+ /* is ship worth stripping */
+ NPCPhrase (NOT_WORTH_STRIPPING);
+ else
+ {
+ SET_GAME_STATE (MELNORME_ANGER, 0);
+
+ StripShip ((COUNT)~0);
+ NPCPhrase (FAIR_JUSTICE);
+ }
+ }
+ else if (PLAYER_SAID (R, fight_some_more))
+ {
+ NPCPhrase (OK_FIGHT_SOME_MORE);
+
+ SET_GAME_STATE (MELNORME_ANGER, 3);
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, bye_melnorme_pissed_off))
+ NPCPhrase (MELNORME_PISSED_OFF_GOODBYE);
+ else if (PLAYER_SAID (R, well_if_thats_the_way_you_feel))
+ {
+ NPCPhrase (WE_FIGHT_AGAIN);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, you_hate_us_so_we_go_away))
+ NPCPhrase (HATE_YOU_GOODBYE);
+ else if (PLAYER_SAID (R, take_it))
+ {
+ StripShip (0);
+ NPCPhrase (HAPPY_TO_HAVE_RESCUED);
+ }
+ else if (PLAYER_SAID (R, leave_it))
+ {
+ SET_GAME_STATE (MELNORME_RESCUE_REFUSED, 1);
+ NPCPhrase (MAYBE_SEE_YOU_LATER);
+ }
+ else if (PLAYER_SAID (R, no_help))
+ {
+ SET_GAME_STATE (MELNORME_RESCUE_REFUSED, 1);
+ NPCPhrase (GOODBYE_AND_GOODLUCK);
+ }
+ else if (PLAYER_SAID (R, no_changed_mind))
+ {
+ NPCPhrase (GOODBYE_AND_GOODLUCK_AGAIN);
+ }
+ else if (PLAYER_SAID (R, be_leaving_now)
+ || PLAYER_SAID (R, goodbye))
+ {
+ NPCPhrase (FRIENDLY_GOODBYE);
+ }
+}
+
+static void
+DoRescue (RESPONSE_REF R)
+{
+ SIZE dx, dy;
+ COUNT fuel_required;
+
+ (void) R; // ignored
+ dx = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x))
+ - SOL_X;
+ dy = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y))
+ - SOL_Y;
+ fuel_required = square_root (
+ (DWORD)((long)dx * dx + (long)dy * dy)
+ ) + (2 * FUEL_TANK_SCALE);
+
+ if (StripShip (fuel_required))
+ {
+ Response (take_it, ExitConversation);
+ Response (leave_it, ExitConversation);
+ }
+}
+
+// Extract method for getting the player's current credits.
+static COUNT
+GetAvailableCredits (void)
+{
+ return MAKE_WORD (GET_GAME_STATE (MELNORME_CREDIT0),
+ GET_GAME_STATE (MELNORME_CREDIT1));
+}
+
+// Extract method for setting the player's current credits.
+static void
+SetAvailableCredits (COUNT credits)
+{
+ SET_GAME_STATE (MELNORME_CREDIT0, LOBYTE (credits));
+ SET_GAME_STATE (MELNORME_CREDIT1, HIBYTE (credits));
+}
+
+// Now returns whether the purchase succeeded instead of the remaining
+// credit balance. Use GetAvailableCredits() to get the latter.
+static bool
+DeltaCredit (SIZE delta_credit)
+{
+ COUNT Credit = GetAvailableCredits ();
+
+ // Can they afford it?
+ if ((int)delta_credit >= 0 || ((int)(-delta_credit) <= (int)(Credit)))
+ {
+ Credit += delta_credit;
+ SetAvailableCredits (Credit);
+ DrawStatusMessage (NULL);
+ return true;
+ }
+
+ // Fail
+ NPCPhrase (NEED_MORE_CREDIT0);
+ NPCNumber (-delta_credit - Credit, NULL);
+ NPCPhrase (NEED_MORE_CREDIT1);
+
+ return false;
+}
+
+
+// Extract methods to process the giving of various bits of information to the
+// player. Ideally, we'd want to merge these three into a single parameterized
+// function, but the nature of the XXX_GAME_STATE() code makes that tricky.
+static void
+CurrentEvents (void)
+{
+ BYTE stack = GET_GAME_STATE (MELNORME_EVENTS_INFO_STACK);
+ const int phraseId = GetLineSafe (ok_buy_event_lines, stack);
+ NPCPhrase (phraseId);
+ SET_GAME_STATE (MELNORME_EVENTS_INFO_STACK, stack + 1);
+}
+
+static void
+AlienRaces (void)
+{
+ BYTE stack = GET_GAME_STATE (MELNORME_ALIEN_INFO_STACK);
+ const int phraseId = GetLineSafe (ok_buy_alien_race_lines, stack);
+ // Two pieces of alien knowledge trigger state changes.
+ switch (phraseId)
+ {
+ case OK_BUY_ALIEN_RACE_14:
+ if (!GET_GAME_STATE (FOUND_PLUTO_SPATHI))
+ {
+ SET_GAME_STATE (KNOW_SPATHI_PASSWORD, 1);
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 7);
+ }
+ break;
+ case OK_BUY_ALIEN_RACE_15:
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) < 2)
+ {
+ SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 2);
+ }
+ SET_GAME_STATE (KNOW_SYREEN_WORLD_SHATTERED, 1);
+ break;
+ }
+ NPCPhrase (phraseId);
+ SET_GAME_STATE (MELNORME_ALIEN_INFO_STACK, stack + 1);
+}
+
+static void
+History (void)
+{
+ BYTE stack = GET_GAME_STATE (MELNORME_HISTORY_INFO_STACK);
+ const int phraseId = GetLineSafe (ok_buy_history_lines, stack);
+ NPCPhrase (phraseId);
+ SET_GAME_STATE (MELNORME_HISTORY_INFO_STACK, stack + 1);
+}
+
+// extract method to tell if we have any information left to sell to the player.
+static bool AnyInfoLeftToSell (void)
+{
+ return GET_GAME_STATE (MELNORME_EVENTS_INFO_STACK) < NUM_EVENT_ITEMS
+ || GET_GAME_STATE (MELNORME_ALIEN_INFO_STACK) < NUM_ALIEN_RACE_ITEMS
+ || GET_GAME_STATE (MELNORME_HISTORY_INFO_STACK) < NUM_HISTORY_ITEMS;
+}
+
+static void NatureOfConversation (RESPONSE_REF R);
+
+static BYTE AskedToBuy;
+
+
+static void
+DoBuy (RESPONSE_REF R)
+{
+ COUNT credit;
+ SIZE needed_credit;
+ BYTE slot;
+ DWORD capacity;
+
+ credit = GetAvailableCredits ();
+
+ capacity = FUEL_RESERVE;
+ slot = NUM_MODULE_SLOTS - 1;
+ do
+ {
+ if (GLOBAL_SIS (ModuleSlots[slot]) == FUEL_TANK
+ || GLOBAL_SIS (ModuleSlots[slot]) == HIGHEFF_FUELSYS)
+ {
+ COUNT volume;
+
+ volume = GLOBAL_SIS (ModuleSlots[slot]) == FUEL_TANK
+ ? FUEL_TANK_CAPACITY : HEFUEL_TANK_CAPACITY;
+ capacity += volume;
+ }
+ } while (slot--);
+
+ // If they're out of credits, educate them on how commerce works.
+ if (credit == 0)
+ {
+ AskedToBuy = TRUE;
+ NPCPhrase (NEED_CREDIT);
+
+ NatureOfConversation (R);
+ }
+ else if (PLAYER_SAID (R, buy_fuel)
+ || PLAYER_SAID (R, buy_1_fuel)
+ || PLAYER_SAID (R, buy_5_fuel)
+ || PLAYER_SAID (R, buy_10_fuel)
+ || PLAYER_SAID (R, buy_25_fuel)
+ || PLAYER_SAID (R, fill_me_up))
+ {
+ needed_credit = 0;
+ if (PLAYER_SAID (R, buy_1_fuel))
+ needed_credit = 1;
+ else if (PLAYER_SAID (R, buy_5_fuel))
+ needed_credit = 5;
+ else if (PLAYER_SAID (R, buy_10_fuel))
+ needed_credit = 10;
+ else if (PLAYER_SAID (R, buy_25_fuel))
+ needed_credit = 25;
+ else if (PLAYER_SAID (R, fill_me_up))
+ needed_credit = (capacity - GLOBAL_SIS (FuelOnBoard)
+ + FUEL_TANK_SCALE - 1)
+ / FUEL_TANK_SCALE;
+
+ if (needed_credit == 0)
+ {
+ if (!GET_GAME_STATE (MELNORME_FUEL_PROCEDURE))
+ {
+ NPCPhrase (BUY_FUEL_INTRO);
+ SET_GAME_STATE (MELNORME_FUEL_PROCEDURE, 1);
+ }
+ }
+ else
+ {
+ if (GLOBAL_SIS (FuelOnBoard) / FUEL_TANK_SCALE
+ + needed_credit > capacity / FUEL_TANK_SCALE)
+ {
+ NPCPhrase (NO_ROOM_FOR_FUEL);
+ goto TryFuelAgain;
+ }
+
+ if ((int)(needed_credit * (BIO_CREDIT_VALUE / 2)) <= (int)credit)
+ {
+ DWORD f;
+
+ NPCPhrase (GOT_FUEL);
+
+ f = (DWORD)needed_credit * FUEL_TANK_SCALE;
+ while (f > 0x3FFFL)
+ {
+ DeltaSISGauges (0, 0x3FFF, 0);
+ f -= 0x3FFF;
+ }
+ DeltaSISGauges (0, (SIZE)f, 0);
+ }
+ needed_credit *= (BIO_CREDIT_VALUE / 2);
+ }
+ if (needed_credit)
+ {
+ DeltaCredit (-needed_credit);
+ if (GLOBAL_SIS (FuelOnBoard) >= capacity)
+ goto BuyBuyBuy;
+ }
+TryFuelAgain:
+ NPCPhrase (HOW_MUCH_FUEL);
+
+ Response (buy_1_fuel, DoBuy);
+ Response (buy_5_fuel, DoBuy);
+ Response (buy_10_fuel, DoBuy);
+ Response (buy_25_fuel, DoBuy);
+ Response (fill_me_up, DoBuy);
+ Response (done_buying_fuel, DoBuy);
+ }
+ else if (PLAYER_SAID (R, buy_technology)
+ || PLAYER_SAID (R, buy_new_tech))
+ {
+ // Note that this code no longer uses the MELNORME_TECH_STACK state
+ // buts, as they're not needed; we can tell what technologies the
+ // player has by using the technology API above. This opens the
+ // possibility of the player acquiring tech from someplace other than
+ // the Melnorme.
+ const TechSaleData* nextTech;
+
+ // If it's our first time, give an introduction.
+ if (!GET_GAME_STATE (MELNORME_TECH_PROCEDURE))
+ {
+ NPCPhrase (BUY_NEW_TECH_INTRO);
+ SET_GAME_STATE (MELNORME_TECH_PROCEDURE, 1);
+ }
+
+ // Did the player just attempt to buy a tech?
+ if (PLAYER_SAID (R, buy_new_tech))
+ {
+ nextTech = GetNextTechForSale ();
+ if (!nextTech)
+ goto BuyBuyBuy; // No tech left to buy
+
+ if (!DeltaCredit (-nextTech->price))
+ goto BuyBuyBuy; // Can't afford it
+
+ // Make the sale
+ GrantTech (nextTech->techId);
+ NPCPhrase (nextTech->sold_line);
+ }
+
+ nextTech = GetNextTechForSale ();
+ if (!nextTech)
+ {
+ NPCPhrase (NEW_TECH_ALL_GONE);
+ goto BuyBuyBuy; // No tech left to buy
+ }
+
+ NPCPhrase (nextTech->sale_line);
+
+ Response (buy_new_tech, DoBuy);
+ Response (no_buy_new_tech, DoBuy);
+ }
+ else if (PLAYER_SAID (R, buy_info)
+ || PLAYER_SAID (R, buy_current_events)
+ || PLAYER_SAID (R, buy_alien_races)
+ || PLAYER_SAID (R, buy_history))
+ {
+ if (!GET_GAME_STATE (MELNORME_INFO_PROCEDURE))
+ {
+ NPCPhrase (BUY_INFO_INTRO);
+ SET_GAME_STATE (MELNORME_INFO_PROCEDURE, 1);
+ }
+ else if (PLAYER_SAID (R, buy_info))
+ {
+ NPCPhrase (OK_BUY_INFO);
+ }
+ else
+ {
+#define INFO_COST 75
+ if (!DeltaCredit (-INFO_COST))
+ goto BuyBuyBuy;
+
+ if (PLAYER_SAID (R, buy_current_events))
+ CurrentEvents ();
+ else if (PLAYER_SAID (R, buy_alien_races))
+ AlienRaces ();
+ else if (PLAYER_SAID (R, buy_history))
+ History ();
+ }
+
+ if (!AnyInfoLeftToSell ())
+ {
+ NPCPhrase (INFO_ALL_GONE);
+ goto BuyBuyBuy;
+ }
+
+ if (GET_GAME_STATE (MELNORME_EVENTS_INFO_STACK) < NUM_EVENT_ITEMS)
+ Response (buy_current_events, DoBuy);
+ if (GET_GAME_STATE (MELNORME_ALIEN_INFO_STACK) < NUM_ALIEN_RACE_ITEMS)
+ Response (buy_alien_races, DoBuy);
+ if (GET_GAME_STATE (MELNORME_HISTORY_INFO_STACK) < NUM_HISTORY_ITEMS)
+ Response (buy_history, DoBuy);
+ Response (done_buying_info, DoBuy);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, done_buying_fuel))
+ NPCPhrase (OK_DONE_BUYING_FUEL);
+ else if (PLAYER_SAID (R, no_buy_new_tech))
+ NPCPhrase (OK_NO_BUY_NEW_TECH);
+ else if (PLAYER_SAID (R, done_buying_info))
+ NPCPhrase (OK_DONE_BUYING_INFO);
+ else
+ NPCPhrase (WHAT_TO_BUY);
+
+BuyBuyBuy:
+ if (GLOBAL_SIS (FuelOnBoard) < capacity)
+ Response (buy_fuel, DoBuy);
+ if (GetNextTechForSale ())
+ Response (buy_technology, DoBuy);
+ if (AnyInfoLeftToSell ())
+ Response (buy_info, DoBuy);
+
+ Response (done_buying, NatureOfConversation);
+ Response (be_leaving_now, ExitConversation);
+ }
+}
+
+static void
+DoSell (RESPONSE_REF R)
+{
+ BYTE num_new_rainbows;
+ UWORD rainbow_mask;
+ SIZE added_credit;
+ int what_to_sell_queued = 0;
+
+ rainbow_mask = MAKE_WORD (
+ GET_GAME_STATE (RAINBOW_WORLD0),
+ GET_GAME_STATE (RAINBOW_WORLD1)
+ );
+ num_new_rainbows = (BYTE)(-GET_GAME_STATE (MELNORME_RAINBOW_COUNT));
+ while (rainbow_mask)
+ {
+ if (rainbow_mask & 1)
+ ++num_new_rainbows;
+
+ rainbow_mask >>= 1;
+ }
+
+ if (!PLAYER_SAID (R, sell))
+ {
+ if (PLAYER_SAID (R, sell_life_data))
+ {
+ DWORD TimeIn;
+
+ added_credit = GLOBAL_SIS (TotalBioMass) * BIO_CREDIT_VALUE;
+
+ NPCPhrase (SOLD_LIFE_DATA1);
+ NPCNumber (GLOBAL_SIS (TotalBioMass), NULL);
+ NPCPhrase (SOLD_LIFE_DATA2);
+ NPCNumber (added_credit, NULL);
+ NPCPhrase (SOLD_LIFE_DATA3);
+ // queue WHAT_TO_SELL before talk-segue
+ if (num_new_rainbows)
+ {
+ NPCPhrase (WHAT_TO_SELL);
+ what_to_sell_queued = 1;
+ }
+ AlienTalkSegue (1);
+
+ DrawCargoStrings ((BYTE)~0, (BYTE)~0);
+ SleepThread (ONE_SECOND / 2);
+ TimeIn = GetTimeCounter ();
+ DrawCargoStrings (
+ (BYTE)NUM_ELEMENT_CATEGORIES,
+ (BYTE)NUM_ELEMENT_CATEGORIES
+ );
+ do
+ {
+ TimeIn = GetTimeCounter ();
+ if (AnyButtonPress (TRUE))
+ {
+ DeltaCredit (GLOBAL_SIS (TotalBioMass) * BIO_CREDIT_VALUE);
+ GLOBAL_SIS (TotalBioMass) = 0;
+ }
+ else
+ {
+ --GLOBAL_SIS (TotalBioMass);
+ DeltaCredit (BIO_CREDIT_VALUE);
+ }
+ DrawCargoStrings (
+ (BYTE)NUM_ELEMENT_CATEGORIES,
+ (BYTE)NUM_ELEMENT_CATEGORIES
+ );
+ } while (GLOBAL_SIS (TotalBioMass));
+ SleepThread (ONE_SECOND / 2);
+
+ ClearSISRect (DRAW_SIS_DISPLAY);
+ }
+ else /* if (R == sell_rainbow_locations) */
+ {
+ added_credit = num_new_rainbows * (250 * BIO_CREDIT_VALUE);
+
+ NPCPhrase (SOLD_RAINBOW_LOCATIONS1);
+ NPCNumber (num_new_rainbows, NULL);
+ NPCPhrase (SOLD_RAINBOW_LOCATIONS2);
+ NPCNumber (added_credit, NULL);
+ NPCPhrase (SOLD_RAINBOW_LOCATIONS3);
+
+ num_new_rainbows += GET_GAME_STATE (MELNORME_RAINBOW_COUNT);
+ SET_GAME_STATE (MELNORME_RAINBOW_COUNT, num_new_rainbows);
+ num_new_rainbows = 0;
+
+ DeltaCredit (added_credit);
+ }
+
+ AskedToBuy = FALSE;
+ }
+
+ if (GLOBAL_SIS (TotalBioMass) || num_new_rainbows)
+ {
+ if (!what_to_sell_queued)
+ NPCPhrase (WHAT_TO_SELL);
+
+ if (GLOBAL_SIS (TotalBioMass))
+ Response (sell_life_data, DoSell);
+ if (num_new_rainbows)
+ Response (sell_rainbow_locations, DoSell);
+ Response (done_selling, NatureOfConversation);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, sell))
+ NPCPhrase (NOTHING_TO_SELL);
+ DISABLE_PHRASE (sell);
+
+ NatureOfConversation (R);
+ }
+}
+
+
+static void
+NatureOfConversation (RESPONSE_REF R)
+{
+ BYTE num_new_rainbows;
+ UWORD rainbow_mask;
+ COUNT Credit;
+
+ if (PLAYER_SAID (R, get_on_with_business))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 5);
+ R = 0;
+ }
+
+ // Draw credits display
+ DeltaCredit (0);
+ Credit = GetAvailableCredits ();
+ if (R == 0)
+ {
+ BYTE stack = GET_GAME_STATE (MELNORME_YACK_STACK2) - 5;
+ NPCPhrase (GetLineSafe (hello_and_down_to_business_lines, stack));
+ if (stack < (NUM_HELLO_LINES - 1))
+ ++stack;
+ SET_GAME_STATE (MELNORME_YACK_STACK2, stack + 5);
+ }
+
+ rainbow_mask = MAKE_WORD (
+ GET_GAME_STATE (RAINBOW_WORLD0),
+ GET_GAME_STATE (RAINBOW_WORLD1)
+ );
+ num_new_rainbows = (BYTE)(-GET_GAME_STATE (MELNORME_RAINBOW_COUNT));
+ while (rainbow_mask)
+ {
+ if (rainbow_mask & 1)
+ ++num_new_rainbows;
+
+ rainbow_mask >>= 1;
+ }
+
+ if (GLOBAL_SIS (FuelOnBoard) > 0
+ || GLOBAL_SIS (TotalBioMass)
+ || Credit
+ || num_new_rainbows)
+ {
+ if (!GET_GAME_STATE (TRADED_WITH_MELNORME))
+ {
+ SET_GAME_STATE (TRADED_WITH_MELNORME, 1);
+
+ NPCPhrase (TRADING_INFO);
+ }
+
+ if (R == 0)
+ {
+ /* Melnorme reports any news and turns purple */
+ NPCPhrase (BUY_OR_SELL);
+ AlienTalkSegue (1);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1)
+ ), ONE_SECOND / 2);
+ AlienTalkSegue ((COUNT)~0);
+ }
+ else if (PLAYER_SAID (R, why_turned_purple))
+ {
+ SET_GAME_STATE (WHY_MELNORME_PURPLE, 1);
+
+ NPCPhrase (TURNED_PURPLE_BECAUSE);
+ }
+ else if (PLAYER_SAID (R, done_selling))
+ {
+ NPCPhrase (OK_DONE_SELLING);
+ }
+ else if (PLAYER_SAID (R, done_buying))
+ {
+ NPCPhrase (OK_DONE_BUYING);
+ }
+
+ if (!GET_GAME_STATE (WHY_MELNORME_PURPLE))
+ {
+ Response (why_turned_purple, NatureOfConversation);
+ }
+ if (!AskedToBuy)
+ Response (buy, DoBuy);
+ if (PHRASE_ENABLED (sell))
+ Response (sell, DoSell);
+ Response (goodbye, ExitConversation);
+ }
+ else /* needs to be rescued */
+ {
+ if (GET_GAME_STATE (MELNORME_RESCUE_REFUSED))
+ {
+ NPCPhrase (CHANGED_MIND);
+
+ Response (yes_changed_mind, DoRescue);
+ Response (no_changed_mind, ExitConversation);
+ }
+ else
+ {
+ BYTE num_rescues = GET_GAME_STATE (MELNORME_RESCUE_COUNT);
+ NPCPhrase (GetLineSafe (rescue_lines, num_rescues));
+
+ if (num_rescues < NUM_RESCUE_LINES - 1)
+ {
+ ++num_rescues;
+ SET_GAME_STATE (MELNORME_RESCUE_COUNT, num_rescues);
+ }
+
+ NPCPhrase (SHOULD_WE_HELP_YOU);
+
+ Response (yes_help, DoRescue);
+ Response (no_help, ExitConversation);
+ }
+ }
+}
+
+static BYTE local_stack0, local_stack1;
+
+static void
+DoBluster (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, trade_is_for_the_weak))
+ {
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 2)
+ ), ONE_SECOND / 2);
+
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 4);
+ NPCPhrase (WERE_NOT_AFRAID);
+ }
+ else if (PLAYER_SAID (R, why_blue_light))
+ {
+ SET_GAME_STATE (WHY_MELNORME_BLUE, 1);
+
+ NPCPhrase (BLUE_IS_MAD);
+ }
+ else if (PLAYER_SAID (R, we_strong_1))
+ {
+ local_stack0 = 1;
+ NPCPhrase (YOU_NOT_STRONG_1);
+ }
+ else if (PLAYER_SAID (R, we_strong_2))
+ {
+ local_stack0 = 2;
+ NPCPhrase (YOU_NOT_STRONG_2);
+ }
+ else if (PLAYER_SAID (R, we_strong_3))
+ {
+ local_stack0 = 3;
+ NPCPhrase (YOU_NOT_STRONG_3);
+ }
+ else if (PLAYER_SAID (R, just_testing))
+ {
+ local_stack1 = 1;
+ NPCPhrase (REALLY_TESTING);
+ }
+
+ if (!GET_GAME_STATE (WHY_MELNORME_BLUE))
+ Response (why_blue_light, DoBluster);
+ switch (local_stack0)
+ {
+ case 0:
+ Response (we_strong_1, DoBluster);
+ break;
+ case 1:
+ Response (we_strong_2, DoBluster);
+ break;
+ case 2:
+ Response (we_strong_3, DoBluster);
+ break;
+ }
+ switch (local_stack1)
+ {
+ case 0:
+ Response (just_testing, DoBluster);
+ break;
+ case 1:
+ {
+ Response (yes_really_testing, DoFirstMeeting);
+ break;
+ }
+ }
+ Response (youre_on, ExitConversation);
+}
+
+static void
+yack0_respond (void)
+{
+
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK0))
+ {
+ case 0:
+ {
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ we_are_from_alliance0,
+ buf,
+ (RESPONSE_REF)-1);
+ DoResponsePhrase (we_are_from_alliance0, DoFirstMeeting, shared_phrase_buf);
+ break;
+ }
+ case 1:
+ Response (how_know, DoFirstMeeting);
+ break;
+ }
+}
+
+static void
+yack1_respond (void)
+{
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK1))
+ {
+ case 0:
+ Response (what_about_yourselves, DoFirstMeeting);
+ break;
+ case 1:
+ Response (what_factors, DoFirstMeeting);
+ case 2:
+ Response (get_on_with_business, NatureOfConversation);
+ break;
+ }
+}
+
+static void
+yack2_respond (void)
+{
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK2))
+ {
+ case 0:
+ Response (what_about_universe, DoFirstMeeting);
+ break;
+ case 1:
+ Response (giving_is_good_1, DoFirstMeeting);
+ break;
+ case 2:
+ Response (giving_is_good_2, DoFirstMeeting);
+ break;
+ case 3:
+ Response (trade_is_for_the_weak, DoBluster);
+ break;
+ }
+}
+
+static void
+DoFirstMeeting (RESPONSE_REF R)
+{
+ BYTE last_stack = 0;
+ PVOIDFUNC temp_func, stack_func[] =
+ {
+ yack0_respond,
+ yack1_respond,
+ yack2_respond,
+ };
+
+ if (R == 0)
+ {
+ BYTE business_count;
+
+ business_count = GET_GAME_STATE (MELNORME_BUSINESS_COUNT);
+ switch (business_count++)
+ {
+ case 0:
+ NPCPhrase (HELLO_NOW_DOWN_TO_BUSINESS_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_NOW_DOWN_TO_BUSINESS_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_NOW_DOWN_TO_BUSINESS_3);
+ --business_count;
+ break;
+ }
+ SET_GAME_STATE (MELNORME_BUSINESS_COUNT, business_count);
+ }
+ else if (PLAYER_SAID (R, we_are_from_alliance0))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK0, 1);
+ NPCPhrase (KNOW_OF_YOU);
+ }
+ else if (PLAYER_SAID (R, how_know))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK0, 2);
+ NPCPhrase (KNOW_BECAUSE);
+ }
+ else if (PLAYER_SAID (R, what_about_yourselves))
+ {
+ last_stack = 1;
+ SET_GAME_STATE (MELNORME_YACK_STACK1, 1);
+ NPCPhrase (NO_TALK_ABOUT_OURSELVES);
+ }
+ else if (PLAYER_SAID (R, what_factors))
+ {
+ last_stack = 1;
+ SET_GAME_STATE (MELNORME_YACK_STACK1, 2);
+ NPCPhrase (FACTORS_ARE);
+ }
+ else if (PLAYER_SAID (R, what_about_universe))
+ {
+ last_stack = 2;
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 1);
+ NPCPhrase (NO_FREE_LUNCH);
+ }
+ else if (PLAYER_SAID (R, giving_is_good_1))
+ {
+ last_stack = 2;
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 2);
+ NPCPhrase (GIVING_IS_BAD_1);
+ }
+ else if (PLAYER_SAID (R, giving_is_good_2))
+ {
+ last_stack = 2;
+ SET_GAME_STATE (MELNORME_YACK_STACK2, 3);
+ NPCPhrase (GIVING_IS_BAD_2);
+ }
+ else if (PLAYER_SAID (R, yes_really_testing))
+ {
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 0)
+ ), ONE_SECOND / 2);
+
+ NPCPhrase (TEST_RESULTS);
+ }
+ else if (PLAYER_SAID (R, we_apologize))
+ {
+ SET_GAME_STATE (MELNORME_ANGER, 0);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 0)
+ ), ONE_SECOND / 2);
+
+ NPCPhrase (APOLOGY_ACCEPTED);
+ }
+
+ temp_func = stack_func[0];
+ stack_func[0] = stack_func[last_stack];
+ stack_func[last_stack] = temp_func;
+ (*stack_func[0]) ();
+ (*stack_func[1]) ();
+ (*stack_func[2]) ();
+ Response (no_trade_now, ExitConversation);
+}
+
+static void
+DoMelnormeMiffed (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE miffed_count;
+
+ miffed_count = GET_GAME_STATE (MELNORME_MIFFED_COUNT);
+ switch (miffed_count++)
+ {
+ case 0:
+ NPCPhrase (HELLO_SLIGHTLY_ANGRY_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_SLIGHTLY_ANGRY_2);
+ break;
+ default:
+ --miffed_count;
+ NPCPhrase (HELLO_SLIGHTLY_ANGRY_3);
+ break;
+ }
+ SET_GAME_STATE (MELNORME_MIFFED_COUNT, miffed_count);
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 2)
+ ), ONE_SECOND / 2);
+ }
+ else if (PLAYER_SAID (R, explore_relationship))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK3, 1);
+
+ NPCPhrase (EXAMPLE_OF_RELATIONSHIP);
+ }
+ else if (PLAYER_SAID (R, excuse_1))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK3, 2);
+
+ NPCPhrase (NO_EXCUSE_1);
+ }
+ else if (PLAYER_SAID (R, excuse_2))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK3, 3);
+
+ NPCPhrase (NO_EXCUSE_2);
+ }
+ else if (PLAYER_SAID (R, excuse_3))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK3, 4);
+
+ NPCPhrase (NO_EXCUSE_3);
+ }
+
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK3))
+ {
+ case 0:
+ Response (explore_relationship, DoMelnormeMiffed);
+ break;
+ case 1:
+ Response (excuse_1, DoMelnormeMiffed);
+ break;
+ case 2:
+ Response (excuse_2, DoMelnormeMiffed);
+ break;
+ case 3:
+ Response (excuse_3, DoMelnormeMiffed);
+ break;
+ }
+ Response (we_apologize, DoFirstMeeting);
+ Response (so_we_can_attack, ExitConversation);
+ Response (bye_melnorme_slightly_angry, ExitConversation);
+}
+
+static void
+DoMelnormePissed (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE pissed_count;
+
+ pissed_count = GET_GAME_STATE (MELNORME_PISSED_COUNT);
+ switch (pissed_count++)
+ {
+ case 0:
+ NPCPhrase (HELLO_PISSED_OFF_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_PISSED_OFF_2);
+ break;
+ default:
+ --pissed_count;
+ NPCPhrase (HELLO_PISSED_OFF_3);
+ break;
+ }
+ SET_GAME_STATE (MELNORME_PISSED_COUNT, pissed_count);
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 2)
+ ), ONE_SECOND / 2);
+ }
+ else if (PLAYER_SAID (R, beg_forgiveness))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK4, 1);
+
+ NPCPhrase (LOTS_TO_MAKE_UP_FOR);
+ }
+ else if (PLAYER_SAID (R, you_are_so_right))
+ {
+ SET_GAME_STATE (MELNORME_YACK_STACK4, 2);
+
+ NPCPhrase (ONE_LAST_CHANCE);
+ }
+
+ switch (GET_GAME_STATE (MELNORME_YACK_STACK4))
+ {
+ case 0:
+ Response (beg_forgiveness, DoMelnormePissed);
+ break;
+ case 1:
+ Response (you_are_so_right, DoMelnormePissed);
+ break;
+ case 2:
+ Response (ok_strip_me, ExitConversation);
+ break;
+ }
+ Response (fight_some_more, ExitConversation);
+ Response (bye_melnorme_pissed_off, ExitConversation);
+}
+
+static void
+DoMelnormeHate (RESPONSE_REF R)
+{
+ BYTE hate_count;
+
+ (void) R; // ignored
+ hate_count = GET_GAME_STATE (MELNORME_HATE_COUNT);
+ switch (hate_count++)
+ {
+ case 0:
+ NPCPhrase (HELLO_HATE_YOU_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_HATE_YOU_2);
+ break;
+ default:
+ --hate_count;
+ NPCPhrase (HELLO_HATE_YOU_3);
+ break;
+ }
+ SET_GAME_STATE (MELNORME_HATE_COUNT, hate_count);
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 2)
+ ), ONE_SECOND / 2);
+
+ Response (well_if_thats_the_way_you_feel, ExitConversation);
+ Response (you_hate_us_so_we_go_away, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ prevMsgMode = SetStatusMessageMode (SMM_CREDITS);
+
+ if (GET_GAME_STATE (MET_MELNORME) == 0)
+ {
+ SET_GAME_STATE (MET_MELNORME, 1);
+ DoFirstMeeting (0);
+ }
+ else
+ {
+ switch (GET_GAME_STATE (MELNORME_ANGER))
+ {
+ case 0:
+ if (GET_GAME_STATE (MELNORME_YACK_STACK2) <= 5)
+ DoFirstMeeting (0);
+ else
+ NatureOfConversation (0);
+ break;
+ case 1:
+ DoMelnormeMiffed (0);
+ break;
+ case 2:
+ DoMelnormePissed (0);
+ break;
+ default:
+ DoMelnormeHate (0);
+ break;
+ }
+ }
+}
+
+static COUNT
+uninit_melnorme (void)
+{
+ return 0;
+}
+
+static void
+post_melnorme_enc (void)
+{
+ if (prevMsgMode != SMM_UNDEFINED)
+ SetStatusMessageMode (prevMsgMode);
+ DrawStatusMessage (NULL);
+}
+
+LOCDATA*
+init_melnorme_comm (void)
+{
+ LOCDATA *retval;
+
+ melnorme_desc.init_encounter_func = Intro;
+ melnorme_desc.post_encounter_func = post_melnorme_enc;
+ melnorme_desc.uninit_encounter_func = uninit_melnorme;
+
+ melnorme_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ melnorme_desc.AlienTextBaseline.y = 0;
+ melnorme_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ local_stack0 = 0;
+ local_stack1 = 0;
+
+ prevMsgMode = SMM_UNDEFINED;
+
+ setSegue (Segue_peace);
+ AskedToBuy = FALSE;
+ retval = &melnorme_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/melnorm/resinst.h b/src/uqm/comm/melnorm/resinst.h
new file mode 100644
index 0000000..cda8d5a
--- /dev/null
+++ b/src/uqm/comm/melnorm/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define MELNORME_COLOR_MAP "comm.melnorme.colortable"
+#define MELNORME_CONVERSATION_PHRASES "comm.melnorme.dialogue"
+#define MELNORME_FONT "comm.melnorme.font"
+#define MELNORME_MUSIC "comm.melnorme.music"
+#define MELNORME_PMAP_ANIM "comm.melnorme.graphics"
diff --git a/src/uqm/comm/melnorm/strings.h b/src/uqm/comm/melnorm/strings.h
new file mode 100644
index 0000000..14ad322
--- /dev/null
+++ b/src/uqm/comm/melnorm/strings.h
@@ -0,0 +1,309 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MELNORME_STRINGS_H
+#define MELNORME_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ HELLO_NOW_DOWN_TO_BUSINESS_1,
+ HELLO_NOW_DOWN_TO_BUSINESS_2,
+ HELLO_NOW_DOWN_TO_BUSINESS_3,
+ KNOW_OF_YOU,
+ how_know,
+ KNOW_BECAUSE,
+ what_about_yourselves,
+ NO_TALK_ABOUT_OURSELVES,
+ what_factors,
+ FACTORS_ARE,
+ what_about_universe,
+ NO_FREE_LUNCH,
+ giving_is_good_1,
+ GIVING_IS_BAD_1,
+ giving_is_good_2,
+ GIVING_IS_BAD_2,
+ get_on_with_business,
+ trade_is_for_the_weak,
+ WERE_NOT_AFRAID,
+ no_trade_now,
+ OK_NO_TRADE_NOW_BYE,
+ HELLO_AND_DOWN_TO_BUSINESS_1,
+ HELLO_AND_DOWN_TO_BUSINESS_2,
+ HELLO_AND_DOWN_TO_BUSINESS_3,
+ HELLO_AND_DOWN_TO_BUSINESS_4,
+ HELLO_AND_DOWN_TO_BUSINESS_5,
+ HELLO_AND_DOWN_TO_BUSINESS_6,
+ HELLO_AND_DOWN_TO_BUSINESS_7,
+ HELLO_AND_DOWN_TO_BUSINESS_8,
+ HELLO_AND_DOWN_TO_BUSINESS_9,
+ HELLO_AND_DOWN_TO_BUSINESS_10,
+ whats_my_credit,
+ HELLO_SLIGHTLY_ANGRY_1,
+ HELLO_SLIGHTLY_ANGRY_2,
+ HELLO_SLIGHTLY_ANGRY_3,
+ explore_relationship,
+ EXAMPLE_OF_RELATIONSHIP,
+ excuse_1,
+ NO_EXCUSE_1,
+ excuse_2,
+ NO_EXCUSE_2,
+ excuse_3,
+ NO_EXCUSE_3,
+ we_apologize,
+ APOLOGY_ACCEPTED,
+ so_we_can_attack,
+ DECEITFUL_HUMAN,
+ bye_melnorme_slightly_angry,
+ MELNORME_SLIGHTLY_ANGRY_GOODBYE,
+ HELLO_HATE_YOU_1,
+ HELLO_HATE_YOU_2,
+ HELLO_HATE_YOU_3,
+ well_if_thats_the_way_you_feel,
+ you_hate_us_so_we_go_away,
+ HATE_YOU_GOODBYE,
+ WE_FIGHT_AGAIN,
+ RESCUE_EXPLANATION,
+ RESCUE_AGAIN_1,
+ RESCUE_AGAIN_2,
+ RESCUE_AGAIN_3,
+ RESCUE_AGAIN_4,
+ RESCUE_AGAIN_5,
+ CHANGED_MIND,
+ no_changed_mind,
+ yes_changed_mind,
+ SHOULD_WE_HELP_YOU,
+ yes_help,
+ no_help,
+ RESCUE_OFFER,
+ RESCUE_TANKS,
+ RESCUE_HOME,
+ take_it,
+ leave_it,
+ HAPPY_TO_HAVE_RESCUED,
+ MAYBE_SEE_YOU_LATER,
+ GOODBYE_AND_GOODLUCK,
+ GOODBYE_AND_GOODLUCK_AGAIN,
+ HELLO_PISSED_OFF_1,
+ HELLO_PISSED_OFF_2,
+ HELLO_PISSED_OFF_3,
+ beg_forgiveness,
+ LOTS_TO_MAKE_UP_FOR,
+ you_are_so_right,
+ ONE_LAST_CHANCE,
+ ok_strip_me,
+ no_strip_now,
+ NOT_WORTH_STRIPPING,
+ FAIR_JUSTICE,
+ bye_melnorme_pissed_off,
+ MELNORME_PISSED_OFF_GOODBYE,
+ fight_some_more,
+ OK_FIGHT_SOME_MORE,
+ why_blue_light,
+ BLUE_IS_MAD,
+ we_strong_1,
+ YOU_NOT_STRONG_1,
+ we_strong_2,
+ YOU_NOT_STRONG_2,
+ we_strong_3,
+ YOU_NOT_STRONG_3,
+ just_testing,
+ REALLY_TESTING,
+ yes_really_testing,
+ TEST_RESULTS,
+ youre_on,
+ YOU_GIVE_US_NO_CHOICE,
+ TRADING_INFO,
+ BUY_OR_SELL,
+ goodbye,
+ why_turned_purple,
+ buy,
+ sell,
+ TURNED_PURPLE_BECAUSE,
+ NOTHING_TO_SELL,
+ WHAT_TO_SELL,
+ OK_DONE_SELLING,
+ sell_life_data,
+ SOLD_LIFE_DATA1,
+ SOLD_LIFE_DATA2,
+ SOLD_LIFE_DATA3,
+ sell_rainbow_locations,
+ SOLD_RAINBOW_LOCATIONS1,
+ SOLD_RAINBOW_LOCATIONS2,
+ SOLD_RAINBOW_LOCATIONS3,
+ sell_precursor_find,
+ SOLD_PRECURSOR_FIND,
+ changed_mind_no_sell,
+ done_selling,
+ NEED_CREDIT,
+ WHAT_TO_BUY,
+ WHAT_MORE_TO_BUY,
+ OK_DONE_BUYING,
+ buy_fuel,
+ done_buying,
+ be_leaving_now,
+ HOW_MUCH_FUEL,
+ buy_1_fuel,
+ GOT_FUEL,
+ buy_5_fuel,
+ buy_10_fuel,
+ buy_25_fuel,
+ done_buying_fuel,
+ FRIENDLY_GOODBYE,
+ CREDIT_IS0,
+ CREDIT_IS1,
+ NEED_MORE_CREDIT0,
+ NEED_MORE_CREDIT1,
+ BUY_FUEL_INTRO,
+ NO_ROOM_FOR_FUEL,
+ buy_info,
+ buy_technology,
+ buy_current_events,
+ buy_alien_races,
+ buy_history,
+ done_buying_info,
+ no_buy_info,
+ BUY_INFO_INTRO,
+ OK_BUY_INFO,
+ OK_NO_BUY_INFO,
+ OK_DONE_BUYING_INFO,
+ OK_BUY_EVENT_1,
+ OK_BUY_EVENT_2,
+ OK_BUY_EVENT_3,
+ OK_BUY_EVENT_4,
+ OK_BUY_EVENT_5,
+ OK_BUY_EVENT_6,
+ OK_BUY_EVENT_7,
+ OK_BUY_EVENT_8,
+ OK_BUY_ALIEN_RACE_1,
+ OK_BUY_ALIEN_RACE_2,
+ OK_BUY_ALIEN_RACE_3,
+ OK_BUY_ALIEN_RACE_4,
+ OK_BUY_ALIEN_RACE_5,
+ OK_BUY_ALIEN_RACE_6,
+ OK_BUY_ALIEN_RACE_7,
+ OK_BUY_ALIEN_RACE_8,
+ OK_BUY_ALIEN_RACE_9,
+ OK_BUY_ALIEN_RACE_10,
+ OK_BUY_ALIEN_RACE_11,
+ OK_BUY_ALIEN_RACE_12,
+ OK_BUY_ALIEN_RACE_13,
+ OK_BUY_ALIEN_RACE_14,
+ OK_BUY_ALIEN_RACE_15,
+ OK_BUY_ALIEN_RACE_16,
+ OK_BUY_HISTORY_1,
+ OK_BUY_HISTORY_2,
+ OK_BUY_HISTORY_3,
+ OK_BUY_HISTORY_4,
+ OK_BUY_HISTORY_5,
+ OK_BUY_HISTORY_6,
+ OK_BUY_HISTORY_7,
+ OK_BUY_HISTORY_8,
+ OK_BUY_HISTORY_9,
+ INFO_ALL_GONE,
+ buy_new_tech,
+ no_buy_new_tech,
+ done_buying_new_tech,
+ fill_me_up,
+ OK_FILL_YOU_UP,
+ BUY_NEW_TECH_INTRO,
+ OK_BUY_NEW_TECH,
+ OK_NO_BUY_NEW_TECH,
+ OK_DONE_BUYING_NEW_TECH,
+ OK_DONE_BUYING_FUEL,
+ NEW_TECH_1,
+ NEW_TECH_2,
+ NEW_TECH_3,
+ NEW_TECH_4,
+ NEW_TECH_5,
+ NEW_TECH_6,
+ NEW_TECH_7,
+ NEW_TECH_8,
+ NEW_TECH_9,
+ NEW_TECH_10,
+ NEW_TECH_11,
+ NEW_TECH_12,
+ NEW_TECH_13,
+ OK_BUY_NEW_TECH_1,
+ OK_BUY_NEW_TECH_2,
+ OK_BUY_NEW_TECH_3,
+ OK_BUY_NEW_TECH_4,
+ OK_BUY_NEW_TECH_5,
+ OK_BUY_NEW_TECH_6,
+ OK_BUY_NEW_TECH_7,
+ OK_BUY_NEW_TECH_8,
+ OK_BUY_NEW_TECH_9,
+ OK_BUY_NEW_TECH_10,
+ OK_BUY_NEW_TECH_11,
+ OK_BUY_NEW_TECH_12,
+ OK_BUY_NEW_TECH_13,
+ CHARITY,
+ NEW_TECH_ALL_GONE,
+ we_are_from_alliance0,
+ STRIP_HEAD,
+ LANDERS,
+ THRUSTERS,
+ JETS,
+ PODS,
+ BAYS,
+ DYNAMOS,
+ FURNACES,
+ GUNS,
+ BLASTERS,
+ CANNONS,
+ TRACKERS,
+ DEFENSES,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ ENUMERATE_ONE,
+ ENUMERATE_TWO,
+ ENUMERATE_THREE,
+ ENUMERATE_FOUR,
+ ENUMERATE_FIVE,
+ ENUMERATE_SIX,
+ ENUMERATE_SEVEN,
+ ENUMERATE_EIGHT,
+ ENUMERATE_NINE,
+ ENUMERATE_TEN,
+ ENUMERATE_ELEVEN,
+ ENUMERATE_TWELVE,
+ ENUMERATE_THIRTEEN,
+ ENUMERATE_FOURTEEN,
+ ENUMERATE_FIFTEEN,
+ ENUMERATE_SIXTEEN,
+ END_LIST_WITH_AND,
+ ENUMERATE_ZERO,
+ ENUMERATE_SEVENTEEN,
+ ENUMERATE_EIGHTEEN,
+ ENUMERATE_NINETEEN,
+ ENUMERATE_TWENTY,
+ ENUMERATE_THIRTY,
+ ENUMERATE_FOURTY,
+ ENUMERATE_FIFTY,
+ ENUMERATE_SIXTY,
+ ENUMERATE_SEVENTY,
+ ENUMERATE_EIGHTY,
+ ENUMERATE_NINETY,
+ ENUMERATE_HUNDRED,
+ ENUMERATE_THOUSAND
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/mycon/Makeinfo b/src/uqm/comm/mycon/Makeinfo
new file mode 100644
index 0000000..df0ef72
--- /dev/null
+++ b/src/uqm/comm/mycon/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="myconc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/mycon/myconc.c b/src/uqm/comm/mycon/myconc.c
new file mode 100644
index 0000000..6490904
--- /dev/null
+++ b/src/uqm/comm/mycon/myconc.c
@@ -0,0 +1,643 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/gameev.h"
+#include "libs/mathlib.h"
+
+
+static LOCDATA mycon_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ MYCON_PMAP_ANIM, /* AlienFrame */
+ MYCON_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ MYCON_COLOR_MAP, /* AlienColorMap */
+ MYCON_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ MYCON_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 5, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 12, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 40, 0, /* FrameRate */
+ ONE_SECOND * 3 / 40, 0, /* RestartRate */
+ (1 << 1), /* BlockMask */
+ },
+ {
+ 18, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 40, 0, /* FrameRate */
+ ONE_SECOND * 3 / 40, 0, /* RestartRate */
+ (1 << 0), /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 40, 0, /* FrameRate */
+ ONE_SECOND * 3 / 40, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 28, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 40, 0, /* FrameRate */
+ ONE_SECOND * 3 / 40, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 33, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND * 3 / 40, 0, /* FrameRate */
+ ONE_SECOND * 3 / 40, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 11, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static BYTE MadeChoice;
+
+static void
+DoRamble (RESPONSE_REF R)
+{
+ BYTE Counter;
+
+ Counter = GET_GAME_STATE (MYCON_RAMBLE);
+ switch (Counter++)
+ {
+ case 0:
+ NPCPhrase (RAMBLE_1);
+ break;
+ case 1:
+ NPCPhrase (RAMBLE_2);
+ break;
+ case 2:
+ NPCPhrase (RAMBLE_3);
+ break;
+ case 3:
+ NPCPhrase (RAMBLE_4);
+ break;
+ case 4:
+ NPCPhrase (RAMBLE_5);
+ break;
+ case 5:
+ NPCPhrase (RAMBLE_6);
+ break;
+ case 6:
+ NPCPhrase (RAMBLE_7);
+ break;
+ case 7:
+ NPCPhrase (RAMBLE_8);
+ break;
+ case 8:
+ NPCPhrase (RAMBLE_9);
+ break;
+ case 9:
+ NPCPhrase (RAMBLE_10);
+ break;
+ case 10:
+ NPCPhrase (RAMBLE_11);
+ break;
+ case 11:
+ NPCPhrase (RAMBLE_12);
+ break;
+ case 12:
+ NPCPhrase (RAMBLE_13);
+ break;
+ case 13:
+ NPCPhrase (RAMBLE_14);
+ break;
+ case 14:
+ NPCPhrase (RAMBLE_15);
+ break;
+ case 15:
+ NPCPhrase (RAMBLE_16);
+ break;
+ case 16:
+ NPCPhrase (RAMBLE_17);
+ break;
+ case 17:
+ NPCPhrase (RAMBLE_18);
+ break;
+ case 18:
+ NPCPhrase (RAMBLE_19);
+ break;
+ case 19:
+ NPCPhrase (RAMBLE_20);
+ break;
+ case 20:
+ NPCPhrase (RAMBLE_21);
+ break;
+ case 21:
+ NPCPhrase (RAMBLE_22);
+ break;
+ case 22:
+ NPCPhrase (RAMBLE_23);
+ break;
+ case 23:
+ NPCPhrase (RAMBLE_24);
+ break;
+ case 24:
+ NPCPhrase (RAMBLE_25);
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) < 2)
+ {
+ SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 2);
+ }
+ break;
+ case 25:
+ NPCPhrase (RAMBLE_26);
+ break;
+ case 26:
+ NPCPhrase (RAMBLE_27);
+ break;
+ case 27:
+ NPCPhrase (RAMBLE_28);
+ break;
+ case 28:
+ NPCPhrase (RAMBLE_29);
+ break;
+ case 29:
+ NPCPhrase (RAMBLE_30);
+ break;
+ case 30:
+ NPCPhrase (RAMBLE_31);
+ break;
+ case 31:
+ NPCPhrase (RAMBLE_32);
+ Counter = 0;
+ break;
+ }
+ SET_GAME_STATE (MYCON_RAMBLE, Counter);
+
+ if (!(GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7)))
+ {
+ if (!PLAYER_SAID (R, come_in_peace)
+ && !PLAYER_SAID (R, gonna_die))
+ {
+ Counter = (GET_GAME_STATE (MYCON_INSULTS) + 1) & 7;
+ SET_GAME_STATE (MYCON_INSULTS, Counter);
+ MadeChoice = 1;
+ }
+ }
+ else if (!PLAYER_SAID (R, lets_be_friends)
+ && !PLAYER_SAID (R, came_to_homeworld)
+ && !PLAYER_SAID (R, submit_to_us))
+ {
+ Counter = (GET_GAME_STATE (MYCON_INFO) + 1) & 15;
+ SET_GAME_STATE (MYCON_INFO, Counter);
+ MadeChoice = 1;
+ }
+}
+
+static void
+CombatIsInevitable (RESPONSE_REF R)
+{
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, bye_space))
+ NPCPhrase (BYE_AND_DIE_SPACE);
+ else if (PLAYER_SAID (R, bye_homeworld))
+ NPCPhrase (BYE_AND_DIE_HOMEWORLD);
+ else if (PLAYER_SAID (R, like_to_land))
+ NPCPhrase (NEVER_LET_LAND);
+ else if (PLAYER_SAID (R, bye_sun_device))
+ {
+ NPCPhrase (GOODBYE_SUN_DEVICE);
+
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ DoRamble (R);
+ if (!(GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7)))
+ NPCPhrase (BYE_AND_DIE_SPACE);
+ else
+ NPCPhrase (BYE_AND_DIE_HOMEWORLD);
+ }
+ MadeChoice = 0;
+}
+
+static void
+SunDevice (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_up_sun_device))
+ {
+ NPCPhrase (GENERAL_INFO_SUN_DEVICE);
+
+ DISABLE_PHRASE (whats_up_sun_device);
+ }
+ else if (PLAYER_SAID (R, how_goes_implanting))
+ {
+ NPCPhrase (UNFORSEEN_DELAYS);
+
+ DISABLE_PHRASE (how_goes_implanting);
+ }
+ else if (PLAYER_SAID (R, i_have_a_cunning_plan))
+ {
+ NPCPhrase (WONT_FALL_FOR_TRICK);
+
+ SET_GAME_STATE (NO_TRICK_AT_SUN, 1);
+ }
+
+ if (PHRASE_ENABLED (whats_up_sun_device))
+ Response (whats_up_sun_device, SunDevice);
+ if (GET_GAME_STATE (MYCON_FELL_FOR_AMBUSH))
+ {
+ if (PHRASE_ENABLED (how_goes_implanting) && GET_GAME_STATE (MYCON_FELL_FOR_AMBUSH))
+ Response (how_goes_implanting, SunDevice);
+ Response (like_to_land, CombatIsInevitable);
+ }
+ else if (GET_GAME_STATE (MYCON_AMBUSH)
+ && !GET_GAME_STATE (NO_TRICK_AT_SUN))
+ Response (i_have_a_cunning_plan, SunDevice);
+ Response (bye_sun_device, CombatIsInevitable);
+}
+
+static void
+TrickMycon (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, i_have_a_cunning_plan))
+ {
+ NPCPhrase (TELL_US_ABOUT_WORLD);
+
+ DISABLE_PHRASE (i_have_a_cunning_plan);
+ }
+ else if (PLAYER_SAID (R, clue_1))
+ {
+ NPCPhrase (RESPONSE_1);
+
+ DISABLE_PHRASE (clue_1);
+ }
+ else if (PLAYER_SAID (R, clue_2))
+ {
+ NPCPhrase (RESPONSE_2);
+
+ DISABLE_PHRASE (clue_2);
+ }
+ else if (PLAYER_SAID (R, clue_3))
+ {
+ NPCPhrase (RESPONSE_3);
+
+ DISABLE_PHRASE (clue_3);
+ }
+
+ if (PHRASE_ENABLED (clue_1) == 0
+ && PHRASE_ENABLED (clue_2) == 0
+ && PHRASE_ENABLED (clue_3) == 0)
+ {
+ NPCPhrase (WE_GO_TO_IMPLANT);
+
+ setSegue (Segue_peace);
+ SET_GAME_STATE (MYCON_FELL_FOR_AMBUSH, 1);
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, ADVANCE_MYCON_MISSION);
+ }
+ else
+ NPCPhrase (AMBUSH_TAIL);
+
+ if (PHRASE_ENABLED (clue_1))
+ Response (clue_1, TrickMycon);
+ if (PHRASE_ENABLED (clue_2))
+ Response (clue_2, TrickMycon);
+ if (PHRASE_ENABLED (clue_3))
+ Response (clue_3, TrickMycon);
+}
+
+static void
+NormalMycon (RESPONSE_REF R)
+{
+ RESPONSE_FUNC RespFunc;
+
+ if (PLAYER_SAID (R, what_about_shattered))
+ {
+ NPCPhrase (ABOUT_SHATTERED);
+
+ SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 2);
+ }
+ else if (R)
+ {
+ DoRamble (R);
+ NPCPhrase (RAMBLE_TAIL);
+
+ DISABLE_PHRASE (R);
+ }
+
+ if ((BYTE)TFB_Random () < 256 * 30 / 100)
+ RespFunc = (RESPONSE_FUNC)CombatIsInevitable;
+ else
+ RespFunc = (RESPONSE_FUNC)NormalMycon;
+ if (!(GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7)))
+ {
+ if (PHRASE_ENABLED (come_in_peace))
+ Response (come_in_peace, RespFunc);
+ if (PHRASE_ENABLED (gonna_die))
+ Response (gonna_die, RespFunc);
+ if (!MadeChoice) switch (GET_GAME_STATE (MYCON_INSULTS))
+ {
+ case 0:
+ Response (insult_1, RespFunc);
+ break;
+ case 1:
+ Response (insult_2, RespFunc);
+ break;
+ case 2:
+ Response (insult_3, RespFunc);
+ break;
+ case 3:
+ Response (insult_4, RespFunc);
+ break;
+ case 4:
+ Response (insult_5, RespFunc);
+ break;
+ case 5:
+ Response (insult_6, RespFunc);
+ break;
+ case 6:
+ Response (insult_7, RespFunc);
+ break;
+ case 7:
+ Response (insult_8, RespFunc);
+ break;
+ }
+ Response (bye_space, CombatIsInevitable);
+ }
+ else
+ {
+ if (!MadeChoice) switch (GET_GAME_STATE (MYCON_INFO))
+ {
+ case 0:
+ Response (question_1, RespFunc);
+ break;
+ case 1:
+ Response (question_2, RespFunc);
+ break;
+ case 2:
+ Response (question_3, RespFunc);
+ break;
+ case 3:
+ Response (question_4, RespFunc);
+ break;
+ case 4:
+ Response (question_5, RespFunc);
+ break;
+ case 5:
+ Response (question_6, RespFunc);
+ break;
+ case 6:
+ Response (question_7, RespFunc);
+ break;
+ case 7:
+ Response (question_8, RespFunc);
+ break;
+ case 8:
+ Response (question_9, RespFunc);
+ break;
+ case 9:
+ Response (question_10, RespFunc);
+ break;
+ case 10:
+ Response (question_11, RespFunc);
+ break;
+ case 11:
+ Response (question_12, RespFunc);
+ break;
+ case 12:
+ Response (question_13, RespFunc);
+ break;
+ case 13:
+ Response (question_14, RespFunc);
+ break;
+ case 14:
+ Response (question_15, RespFunc);
+ break;
+ case 15:
+ Response (question_16, RespFunc);
+ break;
+ }
+ if (PHRASE_ENABLED (lets_be_friends))
+ Response (lets_be_friends, RespFunc);
+ if (PHRASE_ENABLED (came_to_homeworld))
+ Response (came_to_homeworld, RespFunc);
+ if (PHRASE_ENABLED (submit_to_us))
+ Response (submit_to_us, RespFunc);
+ if (!GET_GAME_STATE (MYCON_FELL_FOR_AMBUSH))
+ {
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) == 1)
+ Response (what_about_shattered, NormalMycon);
+ if (GET_GAME_STATE (MYCON_AMBUSH))
+ {
+ Response (i_have_a_cunning_plan, TrickMycon);
+ }
+ }
+ Response (bye_homeworld, CombatIsInevitable);
+ }
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (GET_GAME_STATE (SUN_DEVICE))
+ {
+ NumVisits = GET_GAME_STATE (MYCON_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (DIE_THIEF);
+ break;
+ case 1:
+ NPCPhrase (DIE_THIEF_AGAIN);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (MYCON_VISITS, NumVisits);
+
+ setSegue (Segue_hostile);
+ }
+ else if (GET_GAME_STATE (MYCON_KNOW_AMBUSH))
+ {
+ NPCPhrase (DIE_LIAR);
+
+ setSegue (Segue_hostile);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ NumVisits = GET_GAME_STATE (MYCON_SUN_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_SUN_DEVICE_WORLD_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_SUN_DEVICE_WORLD_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (MYCON_SUN_VISITS, NumVisits);
+
+ SunDevice ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (MYCON_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_HOMEWORLD_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_HOMEWORLD_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_HOMEWORLD_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_HOMEWORLD_4);
+ break;
+ case 4:
+ NPCPhrase (HELLO_HOMEWORLD_5);
+ break;
+ case 5:
+ NPCPhrase (HELLO_HOMEWORLD_6);
+ break;
+ case 6:
+ NPCPhrase (HELLO_HOMEWORLD_7);
+ break;
+ case 7:
+ NPCPhrase (HELLO_HOMEWORLD_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (MYCON_HOME_VISITS, NumVisits);
+
+ NormalMycon ((RESPONSE_REF)0);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (MYCON_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_SPACE_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_SPACE_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_SPACE_4);
+ break;
+ case 4:
+ NPCPhrase (HELLO_SPACE_5);
+ break;
+ case 5:
+ NPCPhrase (HELLO_SPACE_6);
+ break;
+ case 6:
+ NPCPhrase (HELLO_SPACE_7);
+ break;
+ case 7:
+ NPCPhrase (HELLO_SPACE_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (MYCON_VISITS, NumVisits);
+
+ NormalMycon ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_mycon (void)
+{
+ return (0);
+}
+
+static void
+post_mycon_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_mycon_comm (void)
+{
+ LOCDATA *retval;
+
+ mycon_desc.init_encounter_func = Intro;
+ mycon_desc.post_encounter_func = post_mycon_enc;
+ mycon_desc.uninit_encounter_func = uninit_mycon;
+
+ mycon_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ mycon_desc.AlienTextBaseline.y = 0;
+ mycon_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ MadeChoice = 0;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
+ {
+ setSegue (Segue_hostile);
+ }
+ else
+ {
+ setSegue (Segue_peace);
+ }
+ retval = &mycon_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/mycon/resinst.h b/src/uqm/comm/mycon/resinst.h
new file mode 100644
index 0000000..65a6a33
--- /dev/null
+++ b/src/uqm/comm/mycon/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define MYCON_COLOR_MAP "comm.mycon.colortable"
+#define MYCON_CONVERSATION_PHRASES "comm.mycon.dialogue"
+#define MYCON_FONT "comm.mycon.font"
+#define MYCON_MUSIC "comm.mycon.music"
+#define MYCON_PMAP_ANIM "comm.mycon.graphics"
diff --git a/src/uqm/comm/mycon/strings.h b/src/uqm/comm/mycon/strings.h
new file mode 100644
index 0000000..8b71dfb
--- /dev/null
+++ b/src/uqm/comm/mycon/strings.h
@@ -0,0 +1,136 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MYCON_STRINGS_H
+#define MYCON_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ TELL_US_ABOUT_WORLD,
+ BYE_AND_DIE_HOMEWORLD,
+ RAMBLE_1,
+ RAMBLE_2,
+ RAMBLE_3,
+ RAMBLE_4,
+ RAMBLE_5,
+ RAMBLE_6,
+ RAMBLE_7,
+ RAMBLE_8,
+ RAMBLE_9,
+ RAMBLE_10,
+ RAMBLE_11,
+ RAMBLE_12,
+ RAMBLE_13,
+ RAMBLE_14,
+ RAMBLE_15,
+ RAMBLE_16,
+ RAMBLE_17,
+ RAMBLE_18,
+ RAMBLE_19,
+ RAMBLE_20,
+ RAMBLE_21,
+ RAMBLE_22,
+ RAMBLE_23,
+ RAMBLE_24,
+ RAMBLE_25,
+ RAMBLE_26,
+ RAMBLE_27,
+ RAMBLE_28,
+ RAMBLE_29,
+ RAMBLE_30,
+ RAMBLE_31,
+ RAMBLE_32,
+ question_1,
+ question_2,
+ question_3,
+ question_4,
+ question_5,
+ question_6,
+ question_7,
+ question_8,
+ question_9,
+ question_10,
+ question_11,
+ question_12,
+ question_13,
+ question_14,
+ question_15,
+ question_16,
+ bye_space,
+ BYE_AND_DIE_SPACE,
+ gonna_die,
+ insult_1,
+ insult_2,
+ insult_3,
+ insult_4,
+ insult_5,
+ insult_6,
+ insult_7,
+ insult_8,
+ come_in_peace,
+ HELLO_HOMEWORLD_1,
+ HELLO_HOMEWORLD_2,
+ HELLO_HOMEWORLD_3,
+ HELLO_HOMEWORLD_4,
+ HELLO_HOMEWORLD_5,
+ HELLO_HOMEWORLD_6,
+ HELLO_HOMEWORLD_7,
+ HELLO_HOMEWORLD_8,
+ HELLO_SPACE_1,
+ HELLO_SPACE_2,
+ HELLO_SPACE_3,
+ HELLO_SPACE_4,
+ HELLO_SPACE_5,
+ HELLO_SPACE_6,
+ HELLO_SPACE_7,
+ HELLO_SPACE_8,
+ lets_be_friends,
+ came_to_homeworld,
+ submit_to_us,
+ bye_sun_device,
+ GOODBYE_SUN_DEVICE,
+ RESPONSE_1,
+ RESPONSE_2,
+ RESPONSE_3,
+ clue_1,
+ clue_2,
+ clue_3,
+ what_about_shattered,
+ ABOUT_SHATTERED,
+ HELLO_SUN_DEVICE_WORLD_1,
+ HELLO_SUN_DEVICE_WORLD_2,
+ whats_up_sun_device,
+ GENERAL_INFO_SUN_DEVICE,
+ like_to_land,
+ NEVER_LET_LAND,
+ bye_homeworld,
+ i_have_a_cunning_plan,
+ DIE_LIAR,
+ how_goes_implanting,
+ UNFORSEEN_DELAYS,
+ DIE_THIEF,
+ DIE_THIEF_AGAIN,
+ GOODBYE_AND_DIE,
+ AMBUSH_TAIL,
+ RAMBLE_TAIL,
+ WE_GO_TO_IMPLANT,
+ WONT_FALL_FOR_TRICK,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/orz/Makeinfo b/src/uqm/comm/orz/Makeinfo
new file mode 100644
index 0000000..b5c56d2
--- /dev/null
+++ b/src/uqm/comm/orz/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="orzc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/orz/orzc.c b/src/uqm/comm/orz/orzc.c
new file mode 100644
index 0000000..72076a6
--- /dev/null
+++ b/src/uqm/comm/orz/orzc.c
@@ -0,0 +1,898 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+
+
+static LOCDATA orz_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ ORZ_PMAP_ANIM, /* AlienFrame */
+ ORZ_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ ORZ_COLOR_MAP, /* AlienColorMap */
+ ORZ_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ ORZ_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 12 /* 13 */, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 4, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 10, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 15, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 10, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 17, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 10, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 20, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND / 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 7), /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND / 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 6), /* BlockMask */
+ },
+ {
+ 30, /* StartIndex */
+ 3, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND / 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5), /* BlockMask */
+ },
+ {
+ 33, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND / 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4), /* BlockMask */
+ },
+ {
+ 36, /* StartIndex */
+ 25, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 60, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 61, /* StartIndex */
+ 15, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 60, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 76, /* StartIndex */
+ 17, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 60, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 12), /* BlockMask */
+ },
+ {
+ 93, /* StartIndex */
+ 25, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 60, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 118, /* StartIndex */
+ 11, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 60, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 10), /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 12, ONE_SECOND * 3 / 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (R, bye_ally))
+ NPCPhrase (GOODBYE_ALLY);
+ else if (PLAYER_SAID (R, bye_neutral))
+ NPCPhrase (GOODBYE_NEUTRAL);
+ else if (PLAYER_SAID (R, bye_angry))
+ NPCPhrase (GOODBYE_ANGRY);
+ else if (PLAYER_SAID (R, bye_taalo))
+ {
+ if (GET_GAME_STATE (ORZ_MANNER) == 1)
+ NPCPhrase (ANGRY_TAALO_GOODBYE);
+ else
+ NPCPhrase (FRIENDLY_TAALO_GOODBYE);
+ }
+ else if (PLAYER_SAID (R, hostile_2))
+ {
+ NPCPhrase (HOSTILITY_IS_BAD_2);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, may_we_land))
+ {
+ NPCPhrase (SURE_LAND);
+
+ SET_GAME_STATE (TAALO_UNPROTECTED, 1);
+ }
+ else if (PLAYER_SAID (R, yes_alliance)
+ || PLAYER_SAID (R, were_sorry))
+ {
+ if (PLAYER_SAID (R, yes_alliance))
+ NPCPhrase (GREAT);
+ else
+ NPCPhrase (APOLOGY_ACCEPTED);
+
+ SET_GAME_STATE (ORZ_ANDRO_STATE, 0);
+ SET_GAME_STATE (ORZ_GENERAL_INFO, 0);
+ SET_GAME_STATE (ORZ_PERSONAL_INFO, 0);
+ SET_GAME_STATE (ORZ_MANNER, 3);
+ SetRaceAllied (ORZ_SHIP, TRUE);
+ }
+ else if (PLAYER_SAID (R, demand_to_land))
+ {
+ NPCPhrase (NO_DEMAND);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, about_andro_3)
+ || PLAYER_SAID (R, must_know_about_androsyn))
+ {
+ if (PLAYER_SAID (R, about_andro_3))
+ NPCPhrase (BLEW_IT);
+ else
+ NPCPhrase (KNOW_TOO_MUCH);
+
+ SET_GAME_STATE (ORZ_VISITS, 0);
+ SET_GAME_STATE (ORZ_MANNER, 2);
+ setSegue (Segue_hostile);
+ if (PLAYER_SAID (R, about_andro_3))
+ {
+ SetRaceAllied (ORZ_SHIP, FALSE);
+ RemoveEscortShips (ORZ_SHIP);
+ }
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1)
+ ), ONE_SECOND / 2);
+ }
+ else /* insults */
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (ORZ_PERSONAL_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INSULTED_1);
+ break;
+ case 1:
+ NPCPhrase (INSULTED_2);
+ break;
+ case 2:
+ NPCPhrase (INSULTED_3);
+ setSegue (Segue_hostile);
+ break;
+ case 7:
+ --NumVisits;
+ default:
+ NPCPhrase (INSULTED_4);
+ setSegue (Segue_hostile);
+ break;
+ }
+ SET_GAME_STATE (ORZ_PERSONAL_INFO, NumVisits);
+ }
+}
+
+static void
+TaaloWorld (RESPONSE_REF R)
+{
+ // We can only get here when ORZ_MANNER != HOSTILE (2)
+ BYTE Manner;
+
+ Manner = GET_GAME_STATE (ORZ_MANNER);
+ if (PLAYER_SAID (R, demand_to_land))
+ {
+ NPCPhrase (ASK_NICELY);
+
+ DISABLE_PHRASE (demand_to_land);
+ }
+ else if (PLAYER_SAID (R, why_you_here))
+ {
+ if (Manner != 1)
+ NPCPhrase (FRIENDLY_EXPLANATION);
+ else
+ NPCPhrase (ANGRY_EXPLANATION);
+
+ DISABLE_PHRASE (why_you_here);
+ }
+ else if (PLAYER_SAID (R, what_is_this_place))
+ {
+ if (Manner != 1)
+ NPCPhrase (FRIENDLY_PLACE);
+ else
+ NPCPhrase (ANGRY_PLACE);
+
+ DISABLE_PHRASE (what_is_this_place);
+ }
+ else if (PLAYER_SAID (R, may_we_land))
+ {
+ NPCPhrase (ALLIES_CAN_VISIT);
+
+ DISABLE_PHRASE (may_we_land);
+ }
+ else if (PLAYER_SAID (R, make_alliance))
+ {
+ NPCPhrase (CANT_ALLY_HERE);
+
+ DISABLE_PHRASE (make_alliance);
+ }
+ else if (PLAYER_SAID (R, why_busy))
+ {
+ NPCPhrase (BUSY_BECAUSE);
+
+ DISABLE_PHRASE (why_busy);
+ }
+
+ if (PHRASE_ENABLED (may_we_land))
+ {
+ if (Manner == 3 && CheckAlliance (ORZ_SHIP) == GOOD_GUY)
+ Response (may_we_land, ExitConversation);
+ else
+ Response (may_we_land, TaaloWorld);
+ }
+ else if (PHRASE_ENABLED (make_alliance))
+ Response (make_alliance, TaaloWorld);
+ else if (PHRASE_ENABLED (why_busy))
+ Response (why_busy, TaaloWorld);
+ if (PHRASE_ENABLED (demand_to_land))
+ {
+ if (Manner == 1)
+ Response (demand_to_land, ExitConversation);
+ else
+ Response (demand_to_land, TaaloWorld);
+ }
+ if (PHRASE_ENABLED (why_you_here))
+ Response (why_you_here, TaaloWorld);
+ if (PHRASE_ENABLED (what_is_this_place))
+ Response (what_is_this_place, TaaloWorld);
+ Response (bye_taalo, ExitConversation);
+}
+
+static void
+OrzAllied (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, whats_up_ally))
+ {
+ NumVisits = GET_GAME_STATE (ORZ_GENERAL_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_ALLY_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_ALLY_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_ALLY_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_ALLY_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_GENERAL_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_ally);
+ }
+ else if (PLAYER_SAID (R, more_about_you))
+ {
+ NumVisits = GET_GAME_STATE (ORZ_PERSONAL_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ABOUT_US_1);
+ break;
+ case 1:
+ NPCPhrase (ABOUT_US_2);
+ break;
+ case 2:
+ NPCPhrase (ABOUT_US_3);
+ break;
+ case 3:
+ NPCPhrase (ABOUT_US_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_PERSONAL_INFO, NumVisits);
+
+ DISABLE_PHRASE (more_about_you);
+ }
+ else if (PLAYER_SAID (R, about_andro_1))
+ {
+ NPCPhrase (FORGET_ANDRO_1);
+
+ SET_GAME_STATE (ORZ_ANDRO_STATE, 1);
+ }
+ else if (PLAYER_SAID (R, about_andro_2))
+ {
+ NPCPhrase (FORGET_ANDRO_2);
+
+ SET_GAME_STATE (ORZ_ANDRO_STATE, 2);
+ }
+
+ if (GET_GAME_STATE (ORZ_ANDRO_STATE) == 0)
+ Response (about_andro_1, OrzAllied);
+ else if (GET_GAME_STATE (ORZ_ANDRO_STATE) == 1)
+ Response (about_andro_2, OrzAllied);
+ else
+ {
+ Response (about_andro_3, ExitConversation);
+ }
+ if (PHRASE_ENABLED (whats_up_ally))
+ Response (whats_up_ally, OrzAllied);
+ if (PHRASE_ENABLED (more_about_you))
+ Response (more_about_you, OrzAllied);
+ Response (bye_ally, ExitConversation);
+}
+
+static void OrzNeutral (RESPONSE_REF R);
+
+static void
+WhereAndrosyn (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ NPCPhrase (DISEMBLE_ABOUT_ANDROSYN);
+ DISABLE_PHRASE (where_androsyn);
+
+ Response (must_know_about_androsyn, ExitConversation);
+ Response (dont_really_care, OrzNeutral);
+}
+
+static void
+OfferAlliance (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, seem_like_nice_guys))
+ NPCPhrase (ARE_NICE_WANT_ALLY);
+ else if (PLAYER_SAID (R, talk_about_alliance))
+ NPCPhrase (OK_TALK_ALLIANCE);
+ else if (PLAYER_SAID (R, why_so_trusting))
+ {
+ NPCPhrase (TRUSTING_BECAUSE);
+
+ SET_GAME_STATE (ORZ_STACK1, 1);
+ }
+
+ Response (no_alliance, OrzNeutral);
+ Response (decide_later, OrzNeutral);
+ if (GET_GAME_STATE (ORZ_STACK1) == 0)
+ {
+ Response (why_so_trusting, OfferAlliance);
+ }
+ Response (yes_alliance, ExitConversation);
+}
+
+static void
+OrzNeutral (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[3];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = 0;
+ if (PLAYER_SAID (R, hostile_1))
+ {
+ NPCPhrase (HOSTILITY_IS_BAD_1);
+
+ DISABLE_PHRASE (hostile_1);
+ LastStack = 2;
+ }
+ else if (PLAYER_SAID (R, we_are_vindicator0))
+ {
+ NPCPhrase (NICE_TO_MEET_YOU);
+
+ SET_GAME_STATE (ORZ_STACK0, 1);
+ LastStack = 1;
+ }
+ else if (PLAYER_SAID (R, who_you))
+ {
+ NPCPhrase (WE_ARE_ORZ);
+
+ SET_GAME_STATE (ORZ_ANDRO_STATE, 1);
+ }
+ else if (PLAYER_SAID (R, why_here))
+ {
+ NPCPhrase (HERE_BECAUSE);
+
+ SET_GAME_STATE (ORZ_ANDRO_STATE, 2);
+ }
+ else if (PLAYER_SAID (R, no_alliance))
+ {
+ NPCPhrase (MAYBE_LATER);
+
+ DISABLE_PHRASE (talk_about_alliance);
+ SET_GAME_STATE (REFUSED_ORZ_ALLIANCE, 1);
+ }
+ else if (PLAYER_SAID (R, decide_later))
+ {
+ NPCPhrase (OK_LATER);
+
+ DISABLE_PHRASE (talk_about_alliance);
+ SET_GAME_STATE (REFUSED_ORZ_ALLIANCE, 1);
+ }
+ else if (PLAYER_SAID (R, dont_really_care))
+ NPCPhrase (YOU_ARE_OUR_FRIENDS);
+ else if (PLAYER_SAID (R, where_androsyn))
+ {
+ WhereAndrosyn (R);
+ return;
+ }
+ else if (PLAYER_SAID (R, talk_about_alliance)
+ || PLAYER_SAID (R, seem_like_nice_guys))
+ {
+ OfferAlliance (R);
+ return;
+ }
+ else if (PLAYER_SAID (R, hostile_2))
+ {
+ ExitConversation (R);
+ return;
+ }
+
+ if (GET_GAME_STATE (ORZ_ANDRO_STATE) == 0)
+ pStr[0] = who_you;
+ else if (GET_GAME_STATE (ORZ_ANDRO_STATE) == 1)
+ pStr[0] = why_here;
+ else if (PHRASE_ENABLED (where_androsyn) && GET_GAME_STATE (ORZ_ANDRO_STATE) == 2)
+ pStr[0] = where_androsyn;
+ if (GET_GAME_STATE (REFUSED_ORZ_ALLIANCE))
+ {
+ if (PHRASE_ENABLED (talk_about_alliance))
+ pStr[1] = talk_about_alliance;
+ }
+ else if (GET_GAME_STATE (ORZ_STACK0) == 0)
+ {
+ construct_response (shared_phrase_buf,
+ we_are_vindicator0,
+ GLOBAL_SIS (CommanderName),
+ we_are_vindicator1,
+ GLOBAL_SIS (ShipName),
+ we_are_vindicator2,
+ (UNICODE*)NULL);
+ pStr[1] = we_are_vindicator0;
+ }
+ else
+ pStr[1] = seem_like_nice_guys;
+ if (PHRASE_ENABLED (hostile_1))
+ pStr[2] = hostile_1;
+ else
+ pStr[2] = hostile_2;
+
+ if (pStr[LastStack])
+ {
+ if (pStr[LastStack] != we_are_vindicator0)
+ Response (pStr[LastStack], OrzNeutral);
+ else
+ DoResponsePhrase (pStr[LastStack], OrzNeutral, shared_phrase_buf);
+ }
+ for (i = 0; i < 3; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ {
+ if (pStr[i] != we_are_vindicator0)
+ Response (pStr[i], OrzNeutral);
+ else
+ DoResponsePhrase (pStr[i], OrzNeutral, shared_phrase_buf);
+ }
+ }
+ Response (bye_neutral, ExitConversation);
+}
+
+static void
+OrzAngry (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_up_angry))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (ORZ_GENERAL_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_ANGRY_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_ANGRY_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_GENERAL_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_angry);
+ }
+
+ if (PHRASE_ENABLED (whats_up_angry))
+ {
+ Response (whats_up_angry, OrzAngry);
+ }
+ Response (were_sorry, ExitConversation);
+ switch (GET_GAME_STATE (ORZ_PERSONAL_INFO))
+ {
+ case 0:
+ Response (insult_1, ExitConversation);
+ break;
+ case 1:
+ Response (insult_2, ExitConversation);
+ break;
+ case 2:
+ Response (insult_3, ExitConversation);
+ break;
+ case 3:
+ Response (insult_4, ExitConversation);
+ break;
+ case 4:
+ Response (insult_5, ExitConversation);
+ break;
+ case 5:
+ Response (insult_6, ExitConversation);
+ break;
+ case 6:
+ Response (insult_7, ExitConversation);
+ break;
+ case 7:
+ Response (insult_8, ExitConversation);
+ break;
+ }
+ Response (bye_angry, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits, Manner;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (!GET_GAME_STATE (MET_ORZ_BEFORE))
+ NPCPhrase (INIT_HELLO);
+
+ Manner = GET_GAME_STATE (ORZ_MANNER);
+ if (Manner == 2)
+ {
+ CommData.AlienColorMap =
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1);
+
+ NumVisits = GET_GAME_STATE (ORZ_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_VISITS, NumVisits);
+
+ setSegue (Segue_hostile);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ NumVisits = GET_GAME_STATE (TAALO_VISITS);
+ if (Manner != 1)
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (FRIENDLY_ALLIED_TAALO_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (FRIENDLY_ALLIED_TAALO_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ANGRY_TAALO_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ANGRY_TAALO_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ }
+ SET_GAME_STATE (TAALO_VISITS, NumVisits);
+
+ TaaloWorld ((RESPONSE_REF)0);
+ }
+ else if (Manner == 3 && CheckAlliance (ORZ_SHIP) == GOOD_GUY)
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (ORZ_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (ORZ_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ALLIED_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ALLIED_SPACE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (ALLIED_SPACE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (ALLIED_SPACE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_VISITS, NumVisits);
+ }
+
+ OrzAllied ((RESPONSE_REF)0);
+ }
+ else if (Manner != 1)
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (ORZ_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (ORZ_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_VISITS, NumVisits);
+ }
+
+ OrzNeutral ((RESPONSE_REF)0);
+ }
+ else
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (ORZ_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ANGRY_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ANGRY_HOMEWORLD_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (ORZ_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ANGRY_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ANGRY_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ORZ_VISITS, NumVisits);
+ }
+
+ OrzAngry ((RESPONSE_REF)0);
+ }
+
+ if (!GET_GAME_STATE (MET_ORZ_BEFORE))
+ {
+ SET_GAME_STATE (MET_ORZ_BEFORE, 1);
+
+ // Disable talking anim and run the computer report
+ EnableTalkingAnim (FALSE);
+ AlienTalkSegue (1);
+ // Run whatever is left with talking anim
+ EnableTalkingAnim (TRUE);
+ }
+}
+
+static COUNT
+uninit_orz (void)
+{
+ return (0);
+}
+
+static void
+post_orz_enc (void)
+{
+ BYTE Manner;
+
+ if (getSegue () == Segue_hostile
+ && (Manner = GET_GAME_STATE (ORZ_MANNER)) != 2)
+ {
+ SET_GAME_STATE (ORZ_MANNER, 1);
+ if (Manner != 1)
+ {
+ SET_GAME_STATE (ORZ_VISITS, 0);
+ SET_GAME_STATE (ORZ_HOME_VISITS, 0);
+ SET_GAME_STATE (TAALO_VISITS, 0);
+ }
+ }
+}
+
+LOCDATA*
+init_orz_comm (void)
+{
+ LOCDATA *retval;
+
+ orz_desc.init_encounter_func = Intro;
+ orz_desc.post_encounter_func = post_orz_enc;
+ orz_desc.uninit_encounter_func = uninit_orz;
+
+ orz_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ orz_desc.AlienTextBaseline.y = 0;
+ orz_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (GET_GAME_STATE (ORZ_MANNER) == 3
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &orz_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/orz/resinst.h b/src/uqm/comm/orz/resinst.h
new file mode 100644
index 0000000..a526cef
--- /dev/null
+++ b/src/uqm/comm/orz/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ORZ_COLOR_MAP "comm.orz.colortable"
+#define ORZ_CONVERSATION_PHRASES "comm.orz.dialogue"
+#define ORZ_FONT "comm.orz.font"
+#define ORZ_MUSIC "comm.orz.music"
+#define ORZ_PMAP_ANIM "comm.orz.graphics"
diff --git a/src/uqm/comm/orz/strings.h b/src/uqm/comm/orz/strings.h
new file mode 100644
index 0000000..7eabebe
--- /dev/null
+++ b/src/uqm/comm/orz/strings.h
@@ -0,0 +1,143 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ORZ_STRINGS_H
+#define ORZ_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ INIT_HELLO,
+ who_you,
+ WE_ARE_ORZ,
+ why_here,
+ HERE_BECAUSE,
+ ALLIED_HOMEWORLD_HELLO_1,
+ ALLIED_HOMEWORLD_HELLO_2,
+ ALLIED_HOMEWORLD_HELLO_3,
+ ALLIED_HOMEWORLD_HELLO_4,
+ ALLIED_SPACE_HELLO_1,
+ ALLIED_SPACE_HELLO_2,
+ ALLIED_SPACE_HELLO_3,
+ ALLIED_SPACE_HELLO_4,
+ whats_up_ally,
+ GENERAL_INFO_ALLY_1,
+ GENERAL_INFO_ALLY_2,
+ GENERAL_INFO_ALLY_3,
+ GENERAL_INFO_ALLY_4,
+ more_about_you,
+ ABOUT_US_1,
+ ABOUT_US_2,
+ ABOUT_US_3,
+ ABOUT_US_4,
+ where_androsyn,
+ DISEMBLE_ABOUT_ANDROSYN,
+ must_know_about_androsyn,
+ KNOW_TOO_MUCH,
+ dont_really_care,
+ YOU_ARE_OUR_FRIENDS,
+ about_andro_1,
+ FORGET_ANDRO_1,
+ about_andro_2,
+ FORGET_ANDRO_2,
+ about_andro_3,
+ BLEW_IT,
+ NEUTRAL_HOMEWORLD_HELLO_1,
+ NEUTRAL_HOMEWORLD_HELLO_2,
+ NEUTRAL_HOMEWORLD_HELLO_3,
+ NEUTRAL_HOMEWORLD_HELLO_4,
+ NEUTRAL_SPACE_HELLO_1,
+ NEUTRAL_SPACE_HELLO_2,
+ NEUTRAL_SPACE_HELLO_3,
+ NEUTRAL_SPACE_HELLO_4,
+ hostile_1,
+ HOSTILITY_IS_BAD_1,
+ hostile_2,
+ HOSTILITY_IS_BAD_2,
+ we_are_vindicator0,
+ we_are_vindicator1,
+ we_are_vindicator2,
+ NICE_TO_MEET_YOU,
+ seem_like_nice_guys,
+ ARE_NICE_WANT_ALLY,
+ talk_about_alliance,
+ OK_TALK_ALLIANCE,
+ yes_alliance,
+ GREAT,
+ no_alliance,
+ MAYBE_LATER,
+ decide_later,
+ OK_LATER,
+ why_so_trusting,
+ TRUSTING_BECAUSE,
+ bye_neutral,
+ GOODBYE_NEUTRAL,
+ ANGRY_SPACE_HELLO_1,
+ ANGRY_SPACE_HELLO_2,
+ ANGRY_HOMEWORLD_HELLO_1,
+ ANGRY_HOMEWORLD_HELLO_2,
+ whats_up_angry,
+ GENERAL_INFO_ANGRY_1,
+ GENERAL_INFO_ANGRY_2,
+ were_sorry,
+ APOLOGY_ACCEPTED,
+ insult_1,
+ insult_2,
+ insult_3,
+ insult_4,
+ insult_5,
+ insult_6,
+ insult_7,
+ insult_8,
+ INSULTED_1,
+ INSULTED_2,
+ INSULTED_3,
+ INSULTED_4,
+ bye_angry,
+ GOODBYE_ANGRY,
+ ANGRY_TAALO_HELLO_1,
+ ANGRY_TAALO_HELLO_2,
+ FRIENDLY_ALLIED_TAALO_HELLO_1,
+ FRIENDLY_ALLIED_TAALO_HELLO_2,
+ demand_to_land,
+ NO_DEMAND,
+ ASK_NICELY,
+ why_you_here,
+ ANGRY_EXPLANATION,
+ FRIENDLY_EXPLANATION,
+ what_is_this_place,
+ FRIENDLY_PLACE,
+ ANGRY_PLACE,
+ may_we_land,
+ SURE_LAND,
+ ALLIES_CAN_VISIT,
+ make_alliance,
+ CANT_ALLY_HERE,
+ why_busy,
+ BUSY_BECAUSE,
+ bye_taalo,
+ bye_ally,
+ GOODBYE_ALLY,
+ FRIENDLY_TAALO_GOODBYE,
+ ANGRY_TAALO_GOODBYE,
+ HOSTILE_HELLO_1,
+ HOSTILE_HELLO_2,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/pkunk/Makeinfo b/src/uqm/comm/pkunk/Makeinfo
new file mode 100644
index 0000000..67dc511
--- /dev/null
+++ b/src/uqm/comm/pkunk/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="pkunkc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/pkunk/pkunkc.c b/src/uqm/comm/pkunk/pkunkc.c
new file mode 100644
index 0000000..5db575d
--- /dev/null
+++ b/src/uqm/comm/pkunk/pkunkc.c
@@ -0,0 +1,1148 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/gameev.h"
+
+
+static LOCDATA pkunk_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ PKUNK_PMAP_ANIM, /* AlienFrame */
+ PKUNK_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ PKUNK_COLOR_MAP, /* AlienColorMap */
+ PKUNK_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ PKUNK_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 3, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 3, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 7, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 2), /* BlockMask */
+ },
+ {
+ 11, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 1), /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 2, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 6, /* FrameRate */
+ ONE_SECOND / 12, ONE_SECOND / 2, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static BOOLEAN
+ShipsReady (void)
+{
+ SIZE i;
+
+ return (GET_GAME_STATE (PKUNK_MANNER) == 3
+ && !((i = (GLOBAL (GameClock.year_index) - START_YEAR) - GET_GAME_STATE (PKUNK_SHIP_YEAR)) < 0
+ || ((i == 0 && (i = GLOBAL (GameClock.month_index) - GET_GAME_STATE (PKUNK_SHIP_MONTH)) < 0)
+ || (i == 0 && GLOBAL (GameClock.day_index) < GET_GAME_STATE (PKUNK_SHIP_DAY)))));
+}
+
+static void
+PrepareShip (void)
+{
+#define MAX_PKUNK_SHIPS 4
+ if (AddEscortShips (PKUNK_SHIP, MAX_PKUNK_SHIPS))
+ {
+ BYTE mi, di, yi;
+
+ mi = GLOBAL (GameClock.month_index);
+ SET_GAME_STATE (PKUNK_SHIP_MONTH, mi);
+ if ((di = GLOBAL (GameClock.day_index)) > 28)
+ di = 28;
+ SET_GAME_STATE (PKUNK_SHIP_DAY, di);
+ yi = (BYTE)(GLOBAL (GameClock.year_index) - START_YEAR) + 1;
+ SET_GAME_STATE (PKUNK_SHIP_YEAR, yi);
+ }
+}
+
+#define GOOD_REASON_1 (1 << 0)
+#define GOOD_REASON_2 (1 << 1)
+#define BAD_REASON_1 (1 << 2)
+#define BAD_REASON_2 (1 << 3)
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (R, friendly_bye_space))
+ NPCPhrase (FRIENDLY_GOODBYE_SPACE);
+ else if (PLAYER_SAID (R, neutral_bye_space))
+ NPCPhrase (NEUTRAL_GOODBYE_SPACE);
+ else if (PLAYER_SAID (R, bye_angry))
+ NPCPhrase (GOODBYE_ANGRY);
+ else if (PLAYER_SAID (R, bye_friendly))
+ NPCPhrase (GOODBYE_FRIENDLY);
+ else if (PLAYER_SAID (R, we_here_to_help)
+ || PLAYER_SAID (R, we_need_help))
+ {
+ if (PLAYER_SAID (R, we_here_to_help))
+ NPCPhrase (NEED_HELP);
+ else
+ NPCPhrase (GIVE_HELP);
+ NPCPhrase (ALMOST_ALLIANCE);
+
+ SET_GAME_STATE (PKUNK_MANNER, 3);
+ SET_GAME_STATE (PKUNK_VISITS, 0);
+ SET_GAME_STATE (PKUNK_HOME_VISITS, 0);
+ SET_GAME_STATE (PKUNK_INFO, 0);
+
+ AddEvent (RELATIVE_EVENT, 6, 0, 0, ADVANCE_PKUNK_MISSION);
+ if (EscortFeasibilityStudy (PKUNK_SHIP) == 0)
+ NPCPhrase (INIT_NO_ROOM);
+ else
+ {
+ NPCPhrase (INIT_SHIP_GIFT);
+ AlienTalkSegue ((COUNT)~0);
+ PrepareShip ();
+ }
+ }
+ else if (PLAYER_SAID (R, try_to_be_nicer))
+ {
+ NPCPhrase (CANT_ASK_FOR_MORE);
+ NPCPhrase (VISIT_OUR_HOMEWORLD);
+
+ SET_GAME_STATE (PKUNK_MANNER, 3);
+ SET_GAME_STATE (PKUNK_VISITS, 0);
+ SET_GAME_STATE (PKUNK_HOME_VISITS, 0);
+ SET_GAME_STATE (PKUNK_INFO, 0);
+ }
+ else if (PLAYER_SAID (R, must_conquer)
+ || PLAYER_SAID (R, obey))
+ {
+ if (PLAYER_SAID (R, obey))
+ NPCPhrase (NO_OBEY);
+ else
+ {
+ NPCPhrase (BAD_IDEA);
+
+ SET_GAME_STATE (PKUNK_MANNER, 2);
+ }
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, die_idiot_fools))
+ {
+ NPCPhrase (VERY_WELL);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, suit_yourself))
+ NPCPhrase (GOODBYE_MIGRATION);
+ else
+ {
+ BYTE ReasonMask;
+
+ ReasonMask = GET_GAME_STATE (PKUNK_REASONS);
+ if (PLAYER_SAID (R, good_reason_1))
+ {
+ NPCPhrase (WE_GO_HOME_1);
+ ReasonMask |= GOOD_REASON_1;
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, ADVANCE_PKUNK_MISSION);
+ }
+ else if (PLAYER_SAID (R, good_reason_2))
+ {
+ NPCPhrase (WE_GO_HOME_2);
+ ReasonMask |= GOOD_REASON_2;
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, ADVANCE_PKUNK_MISSION);
+ }
+ else if (PLAYER_SAID (R, bad_reason_1))
+ {
+ NPCPhrase (NO_GO_HOME_1);
+ ReasonMask |= BAD_REASON_1;
+ }
+ else if (PLAYER_SAID (R, bad_reason_2))
+ {
+ NPCPhrase (NO_GO_HOME_2);
+ ReasonMask |= BAD_REASON_2;
+ }
+ SET_GAME_STATE (PKUNK_REASONS, ReasonMask);
+ }
+}
+
+static void PkunkHome (RESPONSE_REF R);
+
+static void
+PkunkAngry (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, why_insults))
+ {
+ NPCPhrase (RELEASE_TENSION);
+
+ DISABLE_PHRASE (why_insults);
+ }
+ else if (PLAYER_SAID (R, what_about_you))
+ {
+ NPCPhrase (ABOUT_US);
+
+ DISABLE_PHRASE (what_about_you);
+ }
+ else if (PLAYER_SAID (R, should_be_friends))
+ {
+ NPCPhrase (YES_FRIENDS);
+
+ DISABLE_PHRASE (should_be_friends);
+ }
+
+ if (PHRASE_ENABLED (should_be_friends))
+ {
+ Response (should_be_friends, PkunkAngry);
+ }
+ else
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ Response (try_to_be_nicer, PkunkHome);
+ else
+ Response (try_to_be_nicer, ExitConversation);
+ }
+ Response (die_idiot_fools, ExitConversation);
+ if (PHRASE_ENABLED (why_insults))
+ Response (why_insults, PkunkAngry);
+ if (PHRASE_ENABLED (what_about_you))
+ Response (what_about_you, PkunkAngry);
+ Response (bye_angry, ExitConversation);
+}
+
+static void
+DiscussConquer (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, we_conquer))
+ {
+ NPCPhrase (WHY_CONQUER);
+
+ DISABLE_PHRASE (we_conquer);
+ }
+ else if (PLAYER_SAID (R, conquer_because_1))
+ {
+#if 0
+ NPCPhrase (NOT_CONQUER_10);
+ NPCPhrase (GLOBAL_ALLIANCE_NAME + name_1);
+ NPCPhrase (NOT_CONQUER_11);
+ NPCPhrase (GLOBAL_ALLIANCE_NAME + name_1);
+ NPCPhrase (NOT_CONQUER_12);
+#endif
+ NPCPhrase (NOT_CONQUER_1);
+
+ DISABLE_PHRASE (conquer_because_1);
+ }
+ else if (PLAYER_SAID (R, conquer_because_2))
+ {
+ NPCPhrase (NOT_CONQUER_2);
+
+ DISABLE_PHRASE (conquer_because_2);
+ }
+
+ if (PHRASE_ENABLED (conquer_because_1))
+ {
+#if 0
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ conquer_because_1,
+ buf,
+ (RESPONSE_REF)-1);
+ DoResponsePhrase (conquer_because_1, DiscussConquer, shared_phrase_buf);
+#endif
+ Response(conquer_because_1, DiscussConquer);
+ }
+ if (PHRASE_ENABLED (conquer_because_2))
+ Response (conquer_because_2, DiscussConquer);
+ Response (must_conquer, ExitConversation);
+ Response (no_conquest, PkunkHome);
+}
+
+static void
+OfferAlliance (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, we_are_vindicator0))
+ NPCPhrase (WHY_YOU_HERE);
+ else if (PLAYER_SAID (R, exploring_universe))
+ {
+ NPCPhrase (SENSE_DEEPER_CONFLICT);
+
+ DISABLE_PHRASE (exploring_universe);
+ }
+ else if (PLAYER_SAID (R, fun_cruise))
+ {
+ NPCPhrase (REPRESS);
+
+ DISABLE_PHRASE (fun_cruise);
+ }
+
+ Response (we_here_to_help, ExitConversation);
+ Response (we_need_help, ExitConversation);
+ if (PHRASE_ENABLED (exploring_universe))
+ Response (exploring_universe, OfferAlliance);
+ if (PHRASE_ENABLED (fun_cruise))
+ Response (fun_cruise, OfferAlliance);
+}
+
+static void
+AboutPkunk (RESPONSE_REF R)
+{
+ BYTE InfoLeft;
+
+ InfoLeft = FALSE;
+ if (PLAYER_SAID (R, what_about_you))
+ NPCPhrase (ABOUT_US);
+ else if (PLAYER_SAID (R, what_about_history))
+ {
+ NPCPhrase (ABOUT_HISTORY);
+
+ DISABLE_PHRASE (what_about_history);
+ }
+ else if (PLAYER_SAID (R, what_about_yehat))
+ {
+ NPCPhrase (ABOUT_YEHAT);
+
+ DISABLE_PHRASE (what_about_yehat);
+ }
+ else if (PLAYER_SAID (R, what_about_culture))
+ {
+ NPCPhrase (ABOUT_CULTURE);
+
+ DISABLE_PHRASE (what_about_culture);
+ }
+ else if (PLAYER_SAID (R, elaborate_culture))
+ {
+ NPCPhrase (OK_ELABORATE_CULTURE);
+
+ DISABLE_PHRASE (elaborate_culture);
+ }
+ else if (PLAYER_SAID (R, what_about_future))
+ {
+ NPCPhrase (ABOUT_FUTURE);
+
+ DISABLE_PHRASE (what_about_future);
+ }
+
+ if (PHRASE_ENABLED (what_about_history))
+ {
+ Response (what_about_history, AboutPkunk);
+ InfoLeft = TRUE;
+ }
+ else if (PHRASE_ENABLED (what_about_yehat))
+ {
+ Response (what_about_yehat, AboutPkunk);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_culture))
+ {
+ Response (what_about_culture, AboutPkunk);
+ InfoLeft = TRUE;
+ }
+ else if (PHRASE_ENABLED (elaborate_culture))
+ {
+ Response (elaborate_culture, AboutPkunk);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_future))
+ {
+ Response (what_about_future, AboutPkunk);
+ InfoLeft = TRUE;
+ }
+ Response (enough_about_you, PkunkHome);
+
+ if (!InfoLeft)
+ {
+ DISABLE_PHRASE (what_about_you);
+ }
+}
+
+static void
+AboutIlwrath (RESPONSE_REF R)
+{
+ BYTE InfoLeft;
+
+ InfoLeft = FALSE;
+ if (PLAYER_SAID (R, what_about_ilwrath))
+ NPCPhrase (ABOUT_ILWRATH);
+ else if (PLAYER_SAID (R, why_ilwrath_fight))
+ {
+ NPCPhrase (ILWRATH_FIGHT_BECAUSE);
+
+ DISABLE_PHRASE (why_ilwrath_fight);
+ }
+ else if (PLAYER_SAID (R, when_fight_start))
+ {
+ NPCPhrase (FIGHT_START_WHEN);
+
+ DISABLE_PHRASE (when_fight_start);
+ }
+ else if (PLAYER_SAID (R, how_goes_fight))
+ {
+ NPCPhrase (FIGHT_GOES);
+
+ DISABLE_PHRASE (how_goes_fight);
+ }
+ else if (PLAYER_SAID (R, how_stop_fight))
+ {
+ NPCPhrase (STOP_FIGHT_LIKE_SO);
+
+ DISABLE_PHRASE (how_stop_fight);
+ }
+
+ if (PHRASE_ENABLED (why_ilwrath_fight))
+ {
+ Response (why_ilwrath_fight, AboutIlwrath);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (when_fight_start))
+ {
+ Response (when_fight_start, AboutIlwrath);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (how_goes_fight))
+ {
+ Response (how_goes_fight, AboutIlwrath);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (how_stop_fight))
+ {
+ Response (how_stop_fight, AboutIlwrath);
+ InfoLeft = TRUE;
+ }
+ Response (enough_ilwrath, PkunkHome);
+
+ if (!InfoLeft)
+ {
+ DISABLE_PHRASE (what_about_ilwrath);
+ }
+}
+
+static void
+PkunkHome (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, no_conquest))
+ NPCPhrase (GOOD_IDEA);
+ else if (PLAYER_SAID (R, enough_ilwrath))
+ NPCPhrase (OK_ENOUGH_ILWRATH);
+ else if (PLAYER_SAID (R, enough_about_you))
+ NPCPhrase (OK_ENOUGH_ABOUT_US);
+ else if (PLAYER_SAID (R, where_fleet_1)
+ || PLAYER_SAID (R, where_fleet_2)
+ || PLAYER_SAID (R, where_fleet_3))
+ {
+ SET_GAME_STATE (PKUNK_SWITCH, 1);
+ if (!(GET_GAME_STATE (PKUNK_MISSION) & 1))
+ {
+ NumVisits = GET_GAME_STATE (PKUNK_RETURN);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (RETURNING_FROM_YEHAT_1);
+ break;
+ case 1:
+ NPCPhrase (RETURNING_FROM_YEHAT_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_RETURN, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (PKUNK_MIGRATE);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (MIGRATING_HOMEWORLD_1);
+ break;
+ case 1:
+ NPCPhrase (MIGRATING_HOMEWORLD_2);
+ break;
+ case 2:
+ NPCPhrase (MIGRATING_HOMEWORLD_3);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_MIGRATE, NumVisits);
+ }
+
+ NumVisits = GET_GAME_STATE (PKUNK_FLEET) + 1;
+ SET_GAME_STATE (PKUNK_FLEET, NumVisits);
+
+ DISABLE_PHRASE (where_fleet_1);
+ }
+ else if (PLAYER_SAID (R, am_worried_1)
+ || PLAYER_SAID (R, am_worried_2)
+ || PLAYER_SAID (R, am_worried_3))
+ {
+ NumVisits = GET_GAME_STATE (PKUNK_WORRY);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (DONT_WORRY_1);
+ break;
+ case 1:
+ NPCPhrase (DONT_WORRY_2);
+ break;
+ case 2:
+ NPCPhrase (DONT_WORRY_3);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_WORRY, NumVisits);
+
+ DISABLE_PHRASE (am_worried_1);
+ }
+ else if (PLAYER_SAID (R, try_to_be_nicer))
+ {
+ NPCPhrase (CANT_ASK_FOR_MORE);
+ if (!GET_GAME_STATE (CLEAR_SPINDLE))
+ {
+ NPCPhrase (GIVE_SPINDLE);
+
+ SET_GAME_STATE (CLEAR_SPINDLE, 1);
+ SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 1);
+ }
+ NPCPhrase (CAN_BE_FRIENDS);
+
+ SET_GAME_STATE (PKUNK_MANNER, 3);
+ SET_GAME_STATE (PKUNK_VISITS, 0);
+ SET_GAME_STATE (PKUNK_HOME_VISITS, 0);
+ }
+ else if (PLAYER_SAID (R, what_about_ilwrath))
+ {
+ NPCPhrase (ABOUT_ILWRATH /* ILWRATH_GONE */);
+
+ DISABLE_PHRASE (what_about_ilwrath);
+ }
+
+ if (PHRASE_ENABLED (we_conquer) && GET_GAME_STATE (PKUNK_MANNER) == 0)
+ {
+ Response (we_conquer, DiscussConquer);
+ }
+ if (GET_GAME_STATE (PKUNK_ON_THE_MOVE))
+ {
+ if (PHRASE_ENABLED (where_fleet_1) && !GET_GAME_STATE (PKUNK_SWITCH))
+ {
+ switch (GET_GAME_STATE (PKUNK_FLEET))
+ {
+ case 0:
+ Response (where_fleet_1, PkunkHome);
+ break;
+ case 1:
+ Response (where_fleet_2, PkunkHome);
+ break;
+ case 2:
+ Response (where_fleet_3, PkunkHome);
+ break;
+ }
+ }
+ else if (!PHRASE_ENABLED (where_fleet_1)
+ && PHRASE_ENABLED (am_worried_1)
+ && (GET_GAME_STATE (PKUNK_MISSION) & 1))
+ {
+ switch (GET_GAME_STATE (PKUNK_WORRY))
+ {
+ case 0:
+ Response (am_worried_1, PkunkHome);
+ break;
+ case 1:
+ Response (am_worried_2, PkunkHome);
+ break;
+ case 2:
+ Response (am_worried_3, PkunkHome);
+ break;
+ }
+ }
+ }
+ if (!GET_GAME_STATE (PKUNK_SHIP_MONTH))
+ {
+ construct_response (shared_phrase_buf,
+ we_are_vindicator0,
+ GLOBAL_SIS (CommanderName),
+ we_are_vindicator1,
+ GLOBAL_SIS (ShipName),
+ we_are_vindicator2,
+ (UNICODE*)NULL);
+ DoResponsePhrase (we_are_vindicator0, OfferAlliance, shared_phrase_buf);
+ }
+ if (PHRASE_ENABLED (what_about_you))
+ {
+ Response (what_about_you, AboutPkunk);
+ }
+ if (PHRASE_ENABLED (what_about_ilwrath))
+ {
+ if (!GET_GAME_STATE (ILWRATH_DECEIVED))
+ {
+ Response (what_about_ilwrath, AboutIlwrath);
+ }
+ else
+ {
+ Response (what_about_ilwrath, PkunkHome);
+ }
+ }
+ Response (bye_friendly, ExitConversation);
+}
+
+static void
+PkunkFriendlySpace (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, whats_up_space))
+ {
+ if (ShipsReady ())
+ NPCPhrase (SHIPS_AT_HOME);
+ else
+ {
+ NumVisits = GET_GAME_STATE (PKUNK_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_SPACE_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_SPACE_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_SPACE_4);
+ break;
+ case 4:
+ NPCPhrase (GENERAL_INFO_SPACE_5);
+ break;
+ case 5:
+ NPCPhrase (GENERAL_INFO_SPACE_6);
+ break;
+ case 6:
+ NPCPhrase (GENERAL_INFO_SPACE_7);
+ break;
+ case 7:
+ NPCPhrase (GENERAL_INFO_SPACE_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_INFO, NumVisits);
+ }
+
+ DISABLE_PHRASE (whats_up_space);
+ }
+ else if (PLAYER_SAID (R, how_goes_war))
+ {
+ NumVisits = GET_GAME_STATE (PKUNK_WAR);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (WAR_GOES_1);
+ SET_GAME_STATE (KNOW_URQUAN_STORY, 1);
+ SET_GAME_STATE (KNOW_KOHR_AH_STORY, 1);
+ break;
+ case 1:
+ NPCPhrase (WAR_GOES_2);
+ break;
+ case 2:
+ NPCPhrase (WAR_GOES_3);
+ break;
+ case 3:
+ NPCPhrase (WAR_GOES_4);
+ SET_GAME_STATE (PKUNK_DONE_WAR, 1);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_WAR, NumVisits);
+
+ DISABLE_PHRASE (how_goes_war);
+ }
+ else if (PLAYER_SAID (R, tell_my_fortune))
+ {
+ NumVisits = GET_GAME_STATE (PKUNK_FORTUNE);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (FORTUNE_IS_1);
+ break;
+ case 1:
+ NPCPhrase (FORTUNE_IS_2);
+ break;
+ case 2:
+ NPCPhrase (FORTUNE_IS_3);
+ break;
+ case 3:
+ NPCPhrase (FORTUNE_IS_4);
+ break;
+ case 4:
+ NPCPhrase (FORTUNE_IS_5);
+ break;
+ case 5:
+ NPCPhrase (FORTUNE_IS_6);
+ break;
+ case 6:
+ NPCPhrase (FORTUNE_IS_7);
+ break;
+ case 7:
+ NPCPhrase (FORTUNE_IS_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_FORTUNE, NumVisits);
+
+ DISABLE_PHRASE (tell_my_fortune);
+ }
+
+ if (PHRASE_ENABLED (whats_up_space))
+ Response (whats_up_space, PkunkFriendlySpace);
+ if (!GET_GAME_STATE (PKUNK_DONE_WAR) && PHRASE_ENABLED (how_goes_war))
+ Response (how_goes_war, PkunkFriendlySpace);
+ if (PHRASE_ENABLED (tell_my_fortune))
+ Response (tell_my_fortune, PkunkFriendlySpace);
+ Response (friendly_bye_space, ExitConversation);
+}
+
+static void
+PkunkNeutralSpace (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, form_alliance))
+ {
+ NPCPhrase (GO_TO_HOMEWORLD);
+
+ DISABLE_PHRASE (form_alliance);
+ }
+ else if (PLAYER_SAID (R, can_you_help))
+ {
+ NPCPhrase (GO_TO_HOMEWORLD_AGAIN);
+
+ DISABLE_PHRASE (can_you_help);
+ }
+ else if (PLAYER_SAID (R, hostile_greeting))
+ {
+ NPCPhrase (DONT_BE_HOSTILE);
+
+ DISABLE_PHRASE (hostile_greeting);
+ }
+ else if (PLAYER_SAID (R, whats_up_neutral))
+ {
+ NumVisits = GET_GAME_STATE (PKUNK_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_SPACE_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_SPACE_6 /* was 3 */);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_SPACE_7 /* was 4 */);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_neutral);
+ }
+
+ if (PHRASE_ENABLED (form_alliance))
+ Response (form_alliance, PkunkNeutralSpace);
+ else if (PHRASE_ENABLED (can_you_help))
+ Response (can_you_help, PkunkNeutralSpace);
+ if (PHRASE_ENABLED (hostile_greeting))
+ Response (hostile_greeting, PkunkNeutralSpace);
+ else
+ {
+ Response (obey, ExitConversation);
+ }
+ if (PHRASE_ENABLED (whats_up_neutral))
+ Response (whats_up_neutral, PkunkNeutralSpace);
+ Response (neutral_bye_space, ExitConversation);
+}
+
+static void
+PkunkMigrate (RESPONSE_REF R)
+{
+ BYTE ReasonMask;
+ (void) R; // ignored
+
+ ReasonMask = GET_GAME_STATE (PKUNK_REASONS);
+ if (!(ReasonMask & GOOD_REASON_1))
+ Response (good_reason_1, ExitConversation);
+ if (!(ReasonMask & BAD_REASON_1))
+ Response (bad_reason_1, ExitConversation);
+ if (!(ReasonMask & GOOD_REASON_2))
+ Response (good_reason_2, ExitConversation);
+ if (!(ReasonMask & BAD_REASON_2))
+ Response (bad_reason_2, ExitConversation);
+ Response (suit_yourself, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits, Manner;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ Manner = GET_GAME_STATE (PKUNK_MANNER);
+ if (Manner == 2)
+ {
+ // Irreparably Pissed off the Pkunk.
+ NumVisits = GET_GAME_STATE (PKUNK_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HATE_YOU_FOREVER_1);
+ break;
+ case 1:
+ NPCPhrase (HATE_YOU_FOREVER_2);
+ break;
+ case 2:
+ NPCPhrase (HATE_YOU_FOREVER_3);
+ break;
+ case 3:
+ NPCPhrase (HATE_YOU_FOREVER_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_VISITS, NumVisits);
+
+ setSegue (Segue_hostile);
+ }
+ else if (Manner == 1)
+ {
+ // Bad relations with the Pkunk, but not irreparably.
+ NumVisits = GET_GAME_STATE (PKUNK_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (SPIRITUAL_PROBLEMS_1);
+ break;
+ case 1:
+ NPCPhrase (SPIRITUAL_PROBLEMS_2);
+ break;
+ case 2:
+ NPCPhrase (SPIRITUAL_PROBLEMS_3);
+ break;
+ case 3:
+ NPCPhrase (SPIRITUAL_PROBLEMS_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_VISITS, NumVisits);
+
+ PkunkAngry ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ // Encountering the Pkunk at their home world.
+ if (!GET_GAME_STATE (CLEAR_SPINDLE))
+ {
+ NPCPhrase (GIVE_SPINDLE);
+
+ SET_GAME_STATE (CLEAR_SPINDLE, 1);
+ SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 1);
+ }
+ else if (!GET_GAME_STATE (PKUNK_SENSE_VICTOR)
+ && GLOBAL (GameClock.year_index) > START_YEAR
+ && !GET_GAME_STATE (KOHR_AH_FRENZY))
+ {
+ NPCPhrase (SENSE_KOHRAH_VICTORY);
+
+ SET_GAME_STATE (PKUNK_SENSE_VICTOR, 1);
+ }
+
+ NumVisits = GET_GAME_STATE (PKUNK_HOME_VISITS);
+ if (Manner == 0)
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ }
+ else
+ {
+ if (NumVisits && ShipsReady ())
+ {
+ if (EscortFeasibilityStudy (PKUNK_SHIP) == 0)
+ NPCPhrase (NO_ROOM);
+ else
+ {
+ NPCPhrase (SHIP_GIFT);
+ PrepareShip ();
+ }
+ }
+ else switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (FRIENDLY_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (FRIENDLY_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (FRIENDLY_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (FRIENDLY_HOMEWORLD_HELLO_4);
+ break;
+ case 4:
+ NPCPhrase (FRIENDLY_HOMEWORLD_HELLO_5);
+ break;
+ case 5:
+ NPCPhrase (FRIENDLY_HOMEWORLD_HELLO_6);
+ break;
+ case 6:
+ NPCPhrase (FRIENDLY_HOMEWORLD_HELLO_7);
+ break;
+ case 7:
+ NPCPhrase (FRIENDLY_HOMEWORLD_HELLO_8);
+ --NumVisits;
+ break;
+ }
+ }
+ SET_GAME_STATE (PKUNK_HOME_VISITS, NumVisits);
+
+ PkunkHome ((RESPONSE_REF)0);
+ }
+ else if ((NumVisits = GET_GAME_STATE (PKUNK_MISSION)) == 0
+ || !(NumVisits & 1))
+ {
+ // Encountering a Pkunk ship in space, while they are not
+ // migrating.
+ NumVisits = GET_GAME_STATE (PKUNK_VISITS);
+ if (Manner == 3)
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_4);
+ break;
+ case 4:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_5);
+ break;
+ case 5:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_6);
+ break;
+ case 6:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_7);
+ break;
+ case 7:
+ NPCPhrase (FRIENDLY_SPACE_HELLO_8);
+ --NumVisits;
+ break;
+ }
+
+ PkunkFriendlySpace ((RESPONSE_REF)0);
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+
+ PkunkNeutralSpace ((RESPONSE_REF)0);
+ }
+ SET_GAME_STATE (PKUNK_VISITS, NumVisits);
+
+ }
+ else
+ {
+ // Encountering a Pkunk ship in space, while they are
+ // migrating.
+ NumVisits = GET_GAME_STATE (PKUNK_MIGRATE_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (MIGRATING_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (MIGRATING_SPACE_2);
+ break;
+ case 2:
+ NPCPhrase (MIGRATING_SPACE_3);
+ break;
+ case 3:
+ NPCPhrase (MIGRATING_SPACE_4);
+ break;
+ case 4:
+ NPCPhrase (MIGRATING_SPACE_5);
+ break;
+ case 5:
+ NPCPhrase (MIGRATING_SPACE_6);
+ break;
+ case 6:
+ NPCPhrase (MIGRATING_SPACE_7);
+ break;
+ case 7:
+ NPCPhrase (MIGRATING_SPACE_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (PKUNK_MIGRATE_VISITS, NumVisits);
+
+ PkunkMigrate ((RESPONSE_REF)0);
+ }
+}
+
+// Called after combat or communications
+static COUNT
+uninit_pkunk (void)
+{
+ return (0);
+}
+
+static void
+post_pkunk_enc (void)
+{
+ BYTE Manner;
+
+ if (getSegue () == Segue_hostile
+ && (Manner = GET_GAME_STATE (PKUNK_MANNER)) != 2)
+ {
+ SET_GAME_STATE (PKUNK_MANNER, 1);
+ if (Manner != 1)
+ {
+ SET_GAME_STATE (PKUNK_VISITS, 0);
+ SET_GAME_STATE (PKUNK_HOME_VISITS, 0);
+ }
+ }
+}
+
+LOCDATA*
+init_pkunk_comm (void)
+{
+ LOCDATA *retval;
+
+ pkunk_desc.init_encounter_func = Intro;
+ pkunk_desc.post_encounter_func = post_pkunk_enc;
+ pkunk_desc.uninit_encounter_func = uninit_pkunk;
+
+ pkunk_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ pkunk_desc.AlienTextBaseline.y = 0;
+ pkunk_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (GET_GAME_STATE (PKUNK_MANNER) == 3
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ // Enter communications immediately.
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ // Ask the player whether to attack or talk.
+ setSegue (Segue_hostile);
+ }
+ retval = &pkunk_desc;
+
+ return (retval);
+}
+
+
diff --git a/src/uqm/comm/pkunk/resinst.h b/src/uqm/comm/pkunk/resinst.h
new file mode 100644
index 0000000..8f9ab7a
--- /dev/null
+++ b/src/uqm/comm/pkunk/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define PKUNK_COLOR_MAP "comm.pkunk.colortable"
+#define PKUNK_CONVERSATION_PHRASES "comm.pkunk.dialogue"
+#define PKUNK_FONT "comm.pkunk.font"
+#define PKUNK_MUSIC "comm.pkunk.music"
+#define PKUNK_PMAP_ANIM "comm.pkunk.graphics"
diff --git a/src/uqm/comm/pkunk/strings.h b/src/uqm/comm/pkunk/strings.h
new file mode 100644
index 0000000..beb8b86
--- /dev/null
+++ b/src/uqm/comm/pkunk/strings.h
@@ -0,0 +1,214 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PKUNK_STRINGS_H
+#define PKUNK_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ GIVE_SPINDLE,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ NEUTRAL_SPACE_HELLO_1,
+ NEUTRAL_SPACE_HELLO_3,
+ NEUTRAL_SPACE_HELLO_2,
+ NEUTRAL_SPACE_HELLO_4,
+ FRIENDLY_SPACE_HELLO_1,
+ FRIENDLY_SPACE_HELLO_2,
+ FRIENDLY_SPACE_HELLO_3,
+ FRIENDLY_SPACE_HELLO_4,
+ FRIENDLY_SPACE_HELLO_5,
+ FRIENDLY_SPACE_HELLO_6,
+ FRIENDLY_SPACE_HELLO_7,
+ FRIENDLY_SPACE_HELLO_8,
+ NEUTRAL_HOMEWORLD_HELLO_1,
+ NEUTRAL_HOMEWORLD_HELLO_2,
+ NEUTRAL_HOMEWORLD_HELLO_3,
+ NEUTRAL_HOMEWORLD_HELLO_4,
+ FRIENDLY_HOMEWORLD_HELLO_1,
+ FRIENDLY_HOMEWORLD_HELLO_2,
+ FRIENDLY_HOMEWORLD_HELLO_3,
+ FRIENDLY_HOMEWORLD_HELLO_4,
+ FRIENDLY_HOMEWORLD_HELLO_5,
+ FRIENDLY_HOMEWORLD_HELLO_6,
+ FRIENDLY_HOMEWORLD_HELLO_7,
+ FRIENDLY_HOMEWORLD_HELLO_8,
+ whats_up_neutral,
+ GENERAL_INFO_NEUTRAL_1,
+ GENERAL_INFO_NEUTRAL_2,
+ GENERAL_INFO_NEUTRAL_3,
+ GENERAL_INFO_NEUTRAL_4,
+ good_reason_1,
+ WE_GO_HOME_1,
+ good_reason_2,
+ WE_GO_HOME_2,
+ bad_reason_1,
+ NO_GO_HOME_1,
+ bad_reason_2,
+ NO_GO_HOME_2,
+ SENSE_KOHRAH_VICTORY,
+ SPIRITUAL_PROBLEMS_1,
+ SPIRITUAL_PROBLEMS_2,
+ SPIRITUAL_PROBLEMS_3,
+ SPIRITUAL_PROBLEMS_4,
+ HATE_YOU_FOREVER_1,
+ HATE_YOU_FOREVER_2,
+ HATE_YOU_FOREVER_3,
+ HATE_YOU_FOREVER_4,
+ MIGRATING_SPACE_1,
+ MIGRATING_SPACE_2,
+ MIGRATING_SPACE_3,
+ MIGRATING_SPACE_4,
+ MIGRATING_SPACE_5,
+ MIGRATING_SPACE_6,
+ MIGRATING_SPACE_7,
+ MIGRATING_SPACE_8,
+ die_idiot_fools,
+ VERY_WELL,
+ why_insults,
+ RELEASE_TENSION,
+ what_about_you_angry,
+ ABOUT_US_ANGRY,
+ what_about_you,
+ should_be_friends,
+ YES_FRIENDS,
+ try_to_be_nicer,
+ CANT_ASK_FOR_MORE,
+ VISIT_OUR_HOMEWORLD,
+ CAN_BE_FRIENDS,
+ bye_angry,
+ GOODBYE_ANGRY,
+ we_conquer,
+ WHY_CONQUER,
+ conquer_because_1,
+#if 0
+ NOT_CONQUER_10,
+ NOT_CONQUER_11,
+ NOT_CONQUER_12,
+#endif
+ NOT_CONQUER_1,
+ conquer_because_2,
+ NOT_CONQUER_2,
+ must_conquer,
+ BAD_IDEA,
+ no_conquest,
+ GOOD_IDEA,
+ we_are_vindicator0,
+ we_are_vindicator1,
+ we_are_vindicator2,
+ WHY_YOU_HERE,
+ we_here_to_help,
+ NEED_HELP,
+ we_need_help,
+ GIVE_HELP,
+ exploring_universe,
+ SENSE_DEEPER_CONFLICT,
+ fun_cruise,
+ REPRESS,
+ why_ilwrath_fight,
+ ILWRATH_FIGHT_BECAUSE,
+ when_fight_start,
+ FIGHT_START_WHEN,
+ how_goes_fight,
+ FIGHT_GOES,
+ how_goes_war,
+ WAR_GOES_1,
+ WAR_GOES_2,
+ WAR_GOES_3,
+ WAR_GOES_4,
+ how_stop_fight,
+ STOP_FIGHT_LIKE_SO,
+ enough_ilwrath,
+ OK_ENOUGH_ILWRATH,
+ what_about_history,
+ ABOUT_HISTORY,
+ what_about_yehat,
+ ABOUT_YEHAT,
+ what_about_culture,
+ ABOUT_CULTURE,
+ elaborate_culture,
+ OK_ELABORATE_CULTURE,
+ what_about_future,
+ ABOUT_FUTURE,
+ enough_about_you,
+ OK_ENOUGH_ABOUT_US,
+ ABOUT_US,
+ where_fleet_1,
+ where_fleet_2,
+ where_fleet_3,
+ MIGRATING_HOMEWORLD_1,
+ MIGRATING_HOMEWORLD_2,
+ MIGRATING_HOMEWORLD_3,
+ RETURNING_FROM_YEHAT_1,
+ RETURNING_FROM_YEHAT_2,
+ am_worried_1,
+ am_worried_2,
+ am_worried_3,
+ DONT_WORRY_1,
+ DONT_WORRY_2,
+ DONT_WORRY_3,
+ form_alliance,
+ GO_TO_HOMEWORLD,
+ can_you_help,
+ GO_TO_HOMEWORLD_AGAIN,
+ hostile_greeting,
+ DONT_BE_HOSTILE,
+ obey,
+ NO_OBEY,
+ neutral_bye_space,
+ NEUTRAL_GOODBYE_SPACE,
+ SHIP_GIFT,
+ NO_ROOM,
+ friendly_bye_space,
+ FRIENDLY_GOODBYE_SPACE,
+ bye_friendly,
+ GOODBYE_FRIENDLY,
+ ALMOST_ALLIANCE,
+ INIT_NO_ROOM,
+ INIT_SHIP_GIFT,
+ suit_yourself,
+ GOODBYE_MIGRATION,
+ what_about_ilwrath,
+ ABOUT_ILWRATH,
+ whats_up_space,
+ SHIPS_AT_HOME,
+ GENERAL_INFO_SPACE_1,
+ GENERAL_INFO_SPACE_2,
+ GENERAL_INFO_SPACE_3,
+ GENERAL_INFO_SPACE_4,
+ GENERAL_INFO_SPACE_5,
+ GENERAL_INFO_SPACE_6,
+ GENERAL_INFO_SPACE_7,
+ GENERAL_INFO_SPACE_8,
+ tell_my_fortune,
+ FORTUNE_IS_1,
+ FORTUNE_IS_2,
+ FORTUNE_IS_3,
+ FORTUNE_IS_4,
+ FORTUNE_IS_5,
+ FORTUNE_IS_6,
+ FORTUNE_IS_7,
+ FORTUNE_IS_8,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/rebel/Makeinfo b/src/uqm/comm/rebel/Makeinfo
new file mode 100644
index 0000000..4a17467
--- /dev/null
+++ b/src/uqm/comm/rebel/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="rebel.c"
+uqm_HFILES="strings.h"
diff --git a/src/uqm/comm/rebel/rebel.c b/src/uqm/comm/rebel/rebel.c
new file mode 100644
index 0000000..6366f20
--- /dev/null
+++ b/src/uqm/comm/rebel/rebel.c
@@ -0,0 +1,449 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "../yehat/resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+
+
+static LOCDATA yehat_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ YEHAT_PMAP_ANIM, /* AlienFrame */
+ YEHAT_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* (SIS_TEXT_WIDTH - 16) * 2 / 3, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_MIDDLE, /* AlienTextValign */
+ YEHAT_COLOR_MAP, /* AlienColorMap */
+ YEHAT_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ REBEL_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 15, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ { /* right hand-wing tapping keyboard; front guy */
+ 4, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 10, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 6) | (1 << 7),
+ },
+ { /* left hand-wing tapping keyboard; front guy */
+ 7, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 10, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 6) | (1 << 7),
+ },
+ {
+ 10, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4) | (1 << 14),
+ },
+ {
+ 13, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5),
+ },
+ {
+ 16, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3,/* RestartRate */
+ (1 << 2) | (1 << 14),
+ },
+ {
+ 21, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3,/* RestartRate */
+ (1 << 3),
+ },
+ { /* right arm-wing rising; front guy */
+ 26, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3,/* RestartRate */
+ (1 << 0) | (1 << 1),
+ },
+ { /* left arm-wing rising; front guy */
+ 28, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3,/* RestartRate */
+ (1 << 0) | (1 << 1),
+ },
+ {
+ 30, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 33, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 36, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 39, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 42, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 45, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 48, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 2) | (1 << 4),
+ },
+ },
+ { /* AlienTransitionDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+PrepareShip (void)
+{
+ BYTE mi, di, yi;
+
+ mi = (GLOBAL (GameClock.month_index) % 12) + 1;
+ SET_GAME_STATE (YEHAT_SHIP_MONTH, mi);
+ if ((di = GLOBAL (GameClock.day_index)) > 28)
+ di = 28;
+ SET_GAME_STATE (YEHAT_SHIP_DAY, di);
+ yi = (BYTE)(GLOBAL (GameClock.year_index) - START_YEAR);
+ if (mi == 1)
+ ++yi;
+ SET_GAME_STATE (YEHAT_SHIP_YEAR, yi);
+}
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, bye_rebel))
+ NPCPhrase (GOODBYE_REBEL);
+}
+
+static void Rebels (RESPONSE_REF R);
+
+static void
+RebelInfo (RESPONSE_REF R)
+{
+ BYTE InfoLeft;
+
+ InfoLeft = FALSE;
+ if (PLAYER_SAID (R, give_info_rebels))
+ NPCPhrase (WHAT_INFO);
+ else if (PLAYER_SAID (R, what_about_urquan))
+ {
+ NPCPhrase (ABOUT_URQUAN);
+
+ DISABLE_PHRASE (what_about_urquan);
+ }
+ else if (PLAYER_SAID (R, what_about_royalty))
+ {
+ NPCPhrase (ABOUT_ROYALTY);
+
+ DISABLE_PHRASE (what_about_royalty);
+ }
+ else if (PLAYER_SAID (R, what_about_war))
+ {
+ NPCPhrase (ABOUT_WAR);
+
+ DISABLE_PHRASE (what_about_war);
+ }
+ else if (PLAYER_SAID (R, what_about_vux))
+ {
+ NPCPhrase (ABOUT_VUX);
+
+ DISABLE_PHRASE (what_about_vux);
+ }
+ else if (PLAYER_SAID (R, what_about_clue))
+ {
+ NPCPhrase (ABOUT_CLUE);
+
+ DISABLE_PHRASE (what_about_clue);
+ }
+
+ if (PHRASE_ENABLED (what_about_urquan))
+ {
+ Response (what_about_urquan, RebelInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_royalty))
+ {
+ Response (what_about_royalty, RebelInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_war))
+ {
+ Response (what_about_war, RebelInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_vux))
+ {
+ Response (what_about_vux, RebelInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_clue))
+ {
+ Response (what_about_clue, RebelInfo);
+ InfoLeft = TRUE;
+ }
+ Response (enough_info, Rebels);
+
+ if (!InfoLeft)
+ {
+ DISABLE_PHRASE (give_info_rebels);
+ }
+}
+
+static void
+Rebels (RESPONSE_REF R)
+{
+ SBYTE NumVisits;
+
+ if (PLAYER_SAID (R, how_goes_revolution))
+ {
+ NumVisits = GET_GAME_STATE (YEHAT_REBEL_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (REBEL_REVOLUTION_1);
+ break;
+ case 1:
+ NPCPhrase (REBEL_REVOLUTION_2);
+ break;
+ case 2:
+ NPCPhrase (REBEL_REVOLUTION_3);
+ break;
+ case 3:
+ NPCPhrase (REBEL_REVOLUTION_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_REBEL_INFO, NumVisits);
+
+ DISABLE_PHRASE (how_goes_revolution);
+ }
+ else if (PLAYER_SAID (R, any_ships))
+ {
+ if (GET_GAME_STATE (YEHAT_SHIP_MONTH)
+ && ((NumVisits = (GLOBAL (GameClock.year_index) - START_YEAR) - GET_GAME_STATE (YEHAT_SHIP_YEAR)) < 0
+ || ((NumVisits == 0 && (NumVisits = GLOBAL (GameClock.month_index) - GET_GAME_STATE (YEHAT_SHIP_MONTH)) < 0)
+ || (NumVisits == 0 && GLOBAL (GameClock.day_index) < GET_GAME_STATE (YEHAT_SHIP_DAY)))))
+ NPCPhrase (NO_SHIPS_YET);
+ else if ((NumVisits = EscortFeasibilityStudy (YEHAT_SHIP)) == 0)
+ NPCPhrase (NO_ROOM);
+ else
+ {
+#define NUM_YEHAT_SHIPS 4
+ if (NumVisits < NUM_YEHAT_SHIPS)
+ NPCPhrase (HAVE_FEW_SHIPS);
+ else
+ {
+ NumVisits = NUM_YEHAT_SHIPS;
+ NPCPhrase (HAVE_ALL_SHIPS);
+ }
+
+ AlienTalkSegue ((COUNT)~0);
+ AddEscortShips (YEHAT_SHIP, NumVisits);
+ PrepareShip ();
+ }
+
+ DISABLE_PHRASE (any_ships);
+ }
+ else if (PLAYER_SAID (R, what_about_pkunk_rebel))
+ {
+ if (GET_GAME_STATE (YEHAT_ABSORBED_PKUNK))
+ NPCPhrase (PKUNK_ABSORBED_REBEL);
+ else
+ NPCPhrase (HATE_PKUNK_REBEL);
+
+ SET_GAME_STATE (YEHAT_REBEL_TOLD_PKUNK, 1);
+ }
+ else if (PLAYER_SAID (R, enough_info))
+ NPCPhrase (OK_ENOUGH_INFO);
+
+ if (PHRASE_ENABLED (how_goes_revolution))
+ Response (how_goes_revolution, Rebels);
+ if (!GET_GAME_STATE (YEHAT_REBEL_TOLD_PKUNK)
+ && GET_GAME_STATE (PKUNK_VISITS)
+ && GET_GAME_STATE (PKUNK_HOME_VISITS))
+ Response (what_about_pkunk_rebel, Rebels);
+ if (PHRASE_ENABLED (any_ships))
+ Response (any_ships, Rebels);
+ if (PHRASE_ENABLED (give_info_rebels))
+ {
+ Response (give_info_rebels, RebelInfo);
+ }
+ Response (bye_rebel, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ setSegue (Segue_peace);
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ {
+ NPCPhrase (YEHAT_CAVALRY);
+ AlienTalkSegue ((COUNT)~0);
+
+ NumVisits = (BYTE) EscortFeasibilityStudy (YEHAT_REBEL_SHIP);
+ if (NumVisits > 8)
+ NumVisits = 8;
+ AddEscortShips (YEHAT_REBEL_SHIP, NumVisits - (NumVisits >> 1));
+ AddEscortShips (PKUNK_SHIP, NumVisits >> 1);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (YEHAT_REBEL_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (REBEL_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (REBEL_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (REBEL_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (REBEL_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_REBEL_VISITS, NumVisits);
+
+ Rebels ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_yehat (void)
+{
+ return (0);
+}
+
+static void
+post_yehat_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_rebel_yehat_comm (void)
+{
+ LOCDATA *retval;
+
+ yehat_desc.init_encounter_func = Intro;
+ yehat_desc.post_encounter_func = post_yehat_enc;
+ yehat_desc.uninit_encounter_func = uninit_yehat;
+
+ yehat_desc.AlienTextBaseline.x = SIS_SCREEN_WIDTH * 2 / 3;
+ yehat_desc.AlienTextBaseline.y = 60;
+ yehat_desc.AlienTextWidth = (SIS_TEXT_WIDTH - 16) * 2 / 3;
+
+ // use alternate "Rebels" track if available
+ yehat_desc.AlienAltSongRes = REBEL_MUSIC;
+ yehat_desc.AlienSongFlags |= LDASF_USE_ALTERNATE;
+
+ setSegue (Segue_peace);
+ retval = &yehat_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/rebel/strings.h b/src/uqm/comm/rebel/strings.h
new file mode 100644
index 0000000..c7e0b4f
--- /dev/null
+++ b/src/uqm/comm/rebel/strings.h
@@ -0,0 +1,61 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef REBEL_STRINGS_H
+#define REBEL_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ REBEL_HELLO_1,
+ REBEL_HELLO_2,
+ REBEL_HELLO_3,
+ REBEL_HELLO_4,
+ how_goes_revolution,
+ REBEL_REVOLUTION_1,
+ REBEL_REVOLUTION_2,
+ REBEL_REVOLUTION_3,
+ REBEL_REVOLUTION_4,
+ any_ships,
+ NO_ROOM,
+ HAVE_ALL_SHIPS,
+ HAVE_FEW_SHIPS,
+ NO_SHIPS_YET,
+ give_info_rebels,
+ WHAT_INFO,
+ what_about_royalty,
+ ABOUT_ROYALTY,
+ what_about_war,
+ ABOUT_WAR,
+ what_about_urquan,
+ ABOUT_URQUAN,
+ what_about_vux,
+ ABOUT_VUX,
+ what_about_clue,
+ ABOUT_CLUE,
+ enough_info,
+ OK_ENOUGH_INFO,
+ bye_rebel,
+ GOODBYE_REBEL,
+ YEHAT_CAVALRY,
+ what_about_pkunk_rebel,
+ PKUNK_ABSORBED_REBEL,
+ HATE_PKUNK_REBEL,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/shofixt/Makeinfo b/src/uqm/comm/shofixt/Makeinfo
new file mode 100644
index 0000000..3cad2ac
--- /dev/null
+++ b/src/uqm/comm/shofixt/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="shofixt.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/shofixt/resinst.h b/src/uqm/comm/shofixt/resinst.h
new file mode 100644
index 0000000..8fe9a87
--- /dev/null
+++ b/src/uqm/comm/shofixt/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SHOFIXTI_COLOR_MAP "comm.shofixti.colortable"
+#define SHOFIXTI_CONVERSATION_PHRASES "comm.shofixti.dialogue"
+#define SHOFIXTI_FONT "comm.shofixti.font"
+#define SHOFIXTI_MUSIC "comm.shofixti.music"
+#define SHOFIXTI_PMAP_ANIM "comm.shofixti.graphics"
diff --git a/src/uqm/comm/shofixt/shofixt.c b/src/uqm/comm/shofixt/shofixt.c
new file mode 100644
index 0000000..e76d8d0
--- /dev/null
+++ b/src/uqm/comm/shofixt/shofixt.c
@@ -0,0 +1,652 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/gameev.h"
+
+
+static LOCDATA shofixti_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ SHOFIXTI_PMAP_ANIM, /* AlienFrame */
+ SHOFIXTI_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ SHOFIXTI_COLOR_MAP, /* AlienColorMap */
+ SHOFIXTI_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ SHOFIXTI_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 11, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 5, /* StartIndex */
+ 15, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND / 30, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 20, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ (ONE_SECOND >> 1), (ONE_SECOND >> 1) * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 23, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ (ONE_SECOND >> 1), (ONE_SECOND >> 1) * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 26, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 29, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+
+ {
+ 33, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 39, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* RestartRate */
+ (1 << 7), /* BlockMask */
+ },
+ {
+ 46, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* RestartRate */
+ (1 << 6), /* BlockMask */
+ },
+ {
+ 52, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 56, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* RestartRate */
+ (1 << 10), /* BlockMask */
+ },
+ {
+ 63, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 20, ONE_SECOND / 30, /* RestartRate */
+ (1 << 9), /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 4, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND / 15, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static RESPONSE_REF shofixti_name;
+
+static void
+GetShofixtiName (void)
+{
+ if (GET_GAME_STATE (SHOFIXTI_KIA))
+ shofixti_name = katana;
+ else
+ shofixti_name = tanaka;
+}
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, bye0))
+ {
+ NPCPhrase (GOODBYE);
+
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, go_ahead))
+ {
+ NPCPhrase (ON_SECOND_THOUGHT);
+
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, need_you_for_duty))
+ {
+ NPCPhrase (OK_WILL_BE_SENTRY);
+
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, females)
+ || PLAYER_SAID (R, nubiles)
+ || PLAYER_SAID (R, rat_babes))
+ {
+ NPCPhrase (LEAPING_HAPPINESS);
+
+ SET_GAME_STATE (SHOFIXTI_RECRUITED, 1);
+ SET_GAME_STATE (MAIDENS_ON_SHIP, 0);
+ setSegue (Segue_peace);
+
+ AddEvent (RELATIVE_EVENT, 2, 0, 0, SHOFIXTI_RETURN_EVENT);
+ }
+ else if (PLAYER_SAID (R, dont_attack))
+ {
+ NPCPhrase (TYPICAL_PLOY);
+
+ SET_GAME_STATE (SHOFIXTI_STACK1, 1);
+ }
+ else if (PLAYER_SAID (R, hey_stop))
+ {
+ NPCPhrase (ONLY_STOP);
+
+ SET_GAME_STATE (SHOFIXTI_STACK1, 2);
+ }
+ else if (PLAYER_SAID (R, look_you_are))
+ {
+ NPCPhrase (TOO_BAD);
+
+ SET_GAME_STATE (SHOFIXTI_STACK1, 3);
+ }
+ else if (PLAYER_SAID (R, no_one_insults))
+ {
+ NPCPhrase (YOU_LIMP);
+
+ SET_GAME_STATE (SHOFIXTI_STACK2, 1);
+ }
+ else if (PLAYER_SAID (R, mighty_words))
+ {
+ NPCPhrase (HANG_YOUR);
+
+ SET_GAME_STATE (SHOFIXTI_STACK2, 2);
+ }
+ else if (PLAYER_SAID (R, dont_know))
+ {
+ NPCPhrase (NEVER);
+
+ SET_GAME_STATE (SHOFIXTI_STACK3, 1);
+ }
+ else if (PLAYER_SAID (R, look0))
+ {
+ NPCPhrase (FOR_YOU);
+
+ SET_GAME_STATE (SHOFIXTI_STACK3, 2);
+ }
+ else if (PLAYER_SAID (R, no_bloodshed))
+ {
+ NPCPhrase (YES_BLOODSHED);
+
+ SET_GAME_STATE (SHOFIXTI_STACK3, 3);
+ }
+ else if (PLAYER_SAID (R, dont_want_to_fight))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SHOFIXTI_STACK4);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (MUST_FIGHT_YOU_URQUAN_1);
+ break;
+ case 1:
+ NPCPhrase (MUST_FIGHT_YOU_URQUAN_2);
+ break;
+ case 2:
+ NPCPhrase (MUST_FIGHT_YOU_URQUAN_3);
+ break;
+ case 3:
+ NPCPhrase (MUST_FIGHT_YOU_URQUAN_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SHOFIXTI_STACK4, NumVisits);
+ }
+}
+
+static void
+GiveMaidens (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, important_duty))
+ {
+ NPCPhrase (WHAT_DUTY);
+
+ Response (procreating_wildly, GiveMaidens);
+ Response (replenishing_your_species, GiveMaidens);
+ Response (hope_you_have, GiveMaidens);
+ }
+ else
+ {
+ NPCPhrase (SOUNDS_GREAT_BUT_HOW);
+
+ Response (females, ExitConversation);
+ Response (nubiles, ExitConversation);
+ Response (rat_babes, ExitConversation);
+ }
+}
+
+static void
+ConsoleShofixti (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, dont_do_it))
+ {
+ NPCPhrase (YES_I_DO_IT);
+ DISABLE_PHRASE (dont_do_it);
+ }
+ else
+ NPCPhrase (VERY_SAD_KILL_SELF);
+
+ if (GET_GAME_STATE (MAIDENS_ON_SHIP))
+ {
+ Response (important_duty, GiveMaidens);
+ }
+ if (PHRASE_ENABLED (dont_do_it))
+ {
+ Response (dont_do_it, ConsoleShofixti);
+ }
+ Response (need_you_for_duty, ExitConversation);
+ Response (go_ahead, ExitConversation);
+}
+
+static void
+ExplainDefeat (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, i_am_nice))
+ NPCPhrase (MUST_UNDERSTAND);
+ else if (PLAYER_SAID (R, i_am_guy))
+ NPCPhrase (NICE_BUT_WHAT_IS_DONKEY);
+ else /* if (PLAYER_SAID (R, i_am_captain0)) */
+ NPCPhrase (SO_SORRY);
+ NPCPhrase (IS_DEFEAT_TRUE);
+
+ Response (yes_and_no, ConsoleShofixti);
+ Response (clobbered, ConsoleShofixti);
+ Response (butt_blasted, ConsoleShofixti);
+}
+
+static void
+RealizeMistake (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ NPCPhrase (DGRUNTI);
+ SET_GAME_STATE (SHOFIXTI_STACK1, 0);
+ SET_GAME_STATE (SHOFIXTI_STACK3, 0);
+ SET_GAME_STATE (SHOFIXTI_STACK2, 3);
+
+ {
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ i_am_captain0,
+ GLOBAL_SIS (CommanderName),
+ i_am_captain1,
+ buf,
+ i_am_captain2,
+ GLOBAL_SIS (ShipName),
+ i_am_captain3,
+ (UNICODE*)NULL);
+ }
+ DoResponsePhrase (i_am_captain0, ExplainDefeat, shared_phrase_buf);
+ Response (i_am_nice, ExplainDefeat);
+ Response (i_am_guy, ExplainDefeat);
+}
+
+static void
+Hostile (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ switch (GET_GAME_STATE (SHOFIXTI_STACK1))
+ {
+ case 0:
+ Response (dont_attack, ExitConversation);
+ break;
+ case 1:
+ Response (hey_stop, ExitConversation);
+ break;
+ case 2:
+ Response (look_you_are, ExitConversation);
+ break;
+ }
+ switch (GET_GAME_STATE (SHOFIXTI_STACK2))
+ {
+ case 0:
+ Response (no_one_insults, ExitConversation);
+ break;
+ case 1:
+ Response (mighty_words, ExitConversation);
+ break;
+ case 2:
+ Response (donkey_breath, RealizeMistake);
+ break;
+ }
+ switch (GET_GAME_STATE (SHOFIXTI_STACK3))
+ {
+ case 0:
+ Response (dont_know, ExitConversation);
+ break;
+ case 1:
+ {
+ construct_response (
+ shared_phrase_buf,
+ look0,
+ "",
+ shofixti_name,
+ "",
+ look1,
+ (UNICODE*)NULL);
+ DoResponsePhrase (look0, ExitConversation, shared_phrase_buf);
+ break;
+ }
+ case 2:
+ Response (look_you_are, ExitConversation);
+ break;
+ }
+ Response (dont_want_to_fight, ExitConversation);
+}
+
+static void
+Friendly (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ struct
+ {
+ RESPONSE_REF pStr;
+ UNICODE *c_buf;
+ } Resp[3];
+ static UNICODE buf0[80], buf1[80];
+
+ LastStack = 0;
+ memset (Resp, 0, sizeof (Resp));
+ if (PLAYER_SAID (R, report0))
+ {
+ NPCPhrase (NOTHING_NEW);
+
+ DISABLE_PHRASE (report0);
+ }
+ else if (PLAYER_SAID (R, why_here0))
+ {
+ NPCPhrase (I_GUARD);
+
+ LastStack = 1;
+ SET_GAME_STATE (SHOFIXTI_STACK1, 1);
+ }
+ else if (PLAYER_SAID (R, what_happened))
+ {
+ NPCPhrase (MET_VUX);
+
+ LastStack = 1;
+ SET_GAME_STATE (SHOFIXTI_STACK1, 2);
+ }
+ else if (PLAYER_SAID (R, glory_device))
+ {
+ NPCPhrase (SWITCH_BROKE);
+
+ SET_GAME_STATE (SHOFIXTI_STACK1, 3);
+ }
+ else if (PLAYER_SAID (R, where_world))
+ {
+ NPCPhrase (BLEW_IT_UP);
+
+ LastStack = 2;
+ SET_GAME_STATE (SHOFIXTI_STACK3, 1);
+ }
+ else if (PLAYER_SAID (R, how_survive))
+ {
+ NPCPhrase (NOT_HERE);
+
+ SET_GAME_STATE (SHOFIXTI_STACK3, 2);
+ }
+
+ if (PHRASE_ENABLED (report0))
+ {
+ construct_response (
+ buf0,
+ report0,
+ "",
+ shofixti_name,
+ "",
+ report1,
+ (UNICODE*)NULL);
+ Resp[0].pStr = report0;
+ Resp[0].c_buf = buf0;
+ }
+
+ switch (GET_GAME_STATE (SHOFIXTI_STACK1))
+ {
+ case 0:
+ construct_response (
+ buf1,
+ why_here0,
+ "",
+ shofixti_name,
+ "",
+ why_here1,
+ (UNICODE*)NULL);
+ Resp[1].pStr = why_here0;
+ Resp[1].c_buf = buf1;
+ break;
+ case 1:
+ Resp[1].pStr = what_happened;
+ break;
+ case 2:
+ Resp[1].pStr = glory_device;
+ break;
+ }
+
+ switch (GET_GAME_STATE (SHOFIXTI_STACK3))
+ {
+ case 0:
+ Resp[2].pStr = where_world;
+ break;
+ case 1:
+ Resp[2].pStr = how_survive;
+ break;
+ }
+
+ if (Resp[LastStack].pStr)
+ DoResponsePhrase (Resp[LastStack].pStr, Friendly, Resp[LastStack].c_buf);
+ for (i = 0; i < 3; ++i)
+ {
+ if (i != LastStack && Resp[i].pStr)
+ DoResponsePhrase (Resp[i].pStr, Friendly, Resp[i].c_buf);
+ }
+ if (GET_GAME_STATE (MAIDENS_ON_SHIP))
+ {
+ Response (important_duty, GiveMaidens);
+ }
+
+ construct_response (
+ shared_phrase_buf,
+ bye0,
+ "",
+ shofixti_name,
+ "",
+ bye1,
+ (UNICODE*)NULL);
+ DoResponsePhrase (bye0, ExitConversation, shared_phrase_buf);
+}
+
+static void
+Intro (void)
+{
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ GetShofixtiName ();
+
+ if (GET_GAME_STATE (SHOFIXTI_STACK2) > 2)
+ {
+ NPCPhrase (FRIENDLY_HELLO);
+
+ Friendly ((RESPONSE_REF)0);
+ }
+ else
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SHOFIXTI_VISITS);
+ if (GET_GAME_STATE (SHOFIXTI_KIA))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_KATANA_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_KATANA_2);
+ break;
+ case 2:
+ NPCPhrase (HOSTILE_KATANA_3);
+ break;
+ case 3:
+ NPCPhrase (HOSTILE_KATANA_4);
+ --NumVisits;
+ break;
+ }
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_TANAKA_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_TANAKA_2);
+ break;
+ case 2:
+ NPCPhrase (HOSTILE_TANAKA_3);
+ break;
+ case 3:
+ NPCPhrase (HOSTILE_TANAKA_4);
+ break;
+ case 4:
+ NPCPhrase (HOSTILE_TANAKA_5);
+ break;
+ case 5:
+ NPCPhrase (HOSTILE_TANAKA_6);
+ break;
+ case 6:
+ NPCPhrase (HOSTILE_TANAKA_7);
+ break;
+ case 7:
+ NPCPhrase (HOSTILE_TANAKA_8);
+ --NumVisits;
+ break;
+ }
+ }
+ SET_GAME_STATE (SHOFIXTI_VISITS, NumVisits);
+
+ Hostile ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_shofixti (void)
+{
+ return(0);
+}
+
+static void
+post_shofixti_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_shofixti_comm (void)
+{
+ LOCDATA *retval;
+
+ shofixti_desc.init_encounter_func = Intro;
+ shofixti_desc.post_encounter_func = post_shofixti_enc;
+ shofixti_desc.uninit_encounter_func = uninit_shofixti;
+
+ shofixti_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ shofixti_desc.AlienTextBaseline.y = 0;
+ shofixti_desc.AlienTextWidth = SIS_TEXT_WIDTH;
+
+ setSegue (Segue_peace);
+
+ retval = &shofixti_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/shofixt/strings.h b/src/uqm/comm/shofixt/strings.h
new file mode 100644
index 0000000..b4f165b
--- /dev/null
+++ b/src/uqm/comm/shofixt/strings.h
@@ -0,0 +1,122 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SHOFIXT_STRINGS_H
+#define SHOFIXT_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ tanaka,
+ katana,
+ HOSTILE_KATANA_1,
+ HOSTILE_KATANA_2,
+ HOSTILE_KATANA_3,
+ HOSTILE_KATANA_4,
+ HOSTILE_TANAKA_1,
+ HOSTILE_TANAKA_2,
+ HOSTILE_TANAKA_3,
+ HOSTILE_TANAKA_4,
+ HOSTILE_TANAKA_5,
+ HOSTILE_TANAKA_6,
+ HOSTILE_TANAKA_7,
+ HOSTILE_TANAKA_8,
+ dont_attack,
+ TYPICAL_PLOY,
+ hey_stop,
+ ONLY_STOP,
+ look_you_are,
+ TOO_BAD,
+ dont_know,
+ NEVER,
+ look0,
+ look1,
+ FOR_YOU,
+ no_bloodshed,
+ YES_BLOODSHED,
+ dont_want_to_fight,
+ MUST_FIGHT_YOU_URQUAN_1,
+ MUST_FIGHT_YOU_URQUAN_2,
+ MUST_FIGHT_YOU_URQUAN_3,
+ MUST_FIGHT_YOU_URQUAN_4,
+ no_one_insults,
+ YOU_LIMP,
+ mighty_words,
+ HANG_YOUR,
+ donkey_breath,
+ DGRUNTI,
+ i_am_captain0,
+ i_am_captain1,
+ i_am_captain2,
+ i_am_captain3,
+ i_am_nice,
+ i_am_guy,
+ SO_SORRY,
+ MUST_UNDERSTAND,
+ NICE_BUT_WHAT_IS_DONKEY,
+ IS_DEFEAT_TRUE,
+ yes_and_no,
+ butt_blasted,
+ clobbered,
+ VERY_SAD_KILL_SELF,
+ important_duty,
+ WHAT_DUTY,
+ need_you_for_duty,
+ OK_WILL_BE_SENTRY,
+ dont_do_it,
+ YES_I_DO_IT,
+ go_ahead,
+ ON_SECOND_THOUGHT,
+ procreating_wildly,
+ replenishing_your_species,
+ hope_you_have,
+ SOUNDS_GREAT_BUT_HOW,
+ females,
+ nubiles,
+ rat_babes,
+ LEAPING_HAPPINESS,
+ bye0,
+ bye1,
+ GOODBYE0,
+ GOODBYE1,
+ why_here0,
+ why_here1,
+ I_GUARD,
+ where_world,
+ BLEW_IT_UP,
+ how_survive,
+ NOT_HERE,
+ what_happened,
+ MET_VUX,
+ glory_device,
+ SWITCH_BROKE,
+ bye,
+ GOODBYE,
+ FRIENDLY_HELLO,
+ report0,
+ report1,
+ NOTHING_NEW,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/slyhome/Makeinfo b/src/uqm/comm/slyhome/Makeinfo
new file mode 100644
index 0000000..a09471e
--- /dev/null
+++ b/src/uqm/comm/slyhome/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="slyhome.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/slyhome/resinst.h b/src/uqm/comm/slyhome/resinst.h
new file mode 100644
index 0000000..1bbf162
--- /dev/null
+++ b/src/uqm/comm/slyhome/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SLYLANDRO_COLOR_MAP "comm.slylandro.colortable"
+#define SLYLANDRO_CONVERSATION_PHRASES "comm.slylandro.dialogue"
+#define SLYLANDRO_FONT "comm.slylandro.font"
+#define SLYLANDRO_MUSIC "comm.slylandro.music"
+#define SLYLANDRO_PMAP_ANIM "comm.slylandro.graphics"
diff --git a/src/uqm/comm/slyhome/slyhome.c b/src/uqm/comm/slyhome/slyhome.c
new file mode 100644
index 0000000..80cb839
--- /dev/null
+++ b/src/uqm/comm/slyhome/slyhome.c
@@ -0,0 +1,921 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/gameev.h"
+
+
+static LOCDATA slylandro_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ SLYLANDRO_PMAP_ANIM, /* AlienFrame */
+ SLYLANDRO_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ SLYLANDRO_COLOR_MAP, /* AlienColorMap */
+ SLYLANDRO_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ SLYLANDRO_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 13, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 0, /* StartIndex */
+ 5, /* NumFrames */
+ RANDOM_ANIM | COLORXFORM_ANIM, /* AnimFlags */
+ ONE_SECOND / 8, ONE_SECOND * 5 / 8, /* FrameRate */
+ ONE_SECOND / 8, ONE_SECOND * 5 / 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 1, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 6, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 11, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 16, /* StartIndex */
+ 6, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 15, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 30, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 8) | (1 << 9), /* BlockMask */
+ },
+ {
+ 39, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 43, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 6), /* BlockMask */
+ },
+ {
+ 48, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 6), /* BlockMask */
+ },
+ {
+ 54, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 12), /* BlockMask */
+ },
+ {
+ 60, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 12), /* BlockMask */
+ },
+ {
+ 67, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 10) | (1 << 11), /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ setSegue (Segue_peace);
+
+ switch (GET_GAME_STATE (SLYLANDRO_HOME_VISITS))
+ {
+ case 1:
+ NPCPhrase (GOODBYE_1);
+ break;
+ default:
+ NPCPhrase (GOODBYE_2);
+ break;
+ }
+}
+
+static void HomeWorld (RESPONSE_REF R);
+
+static void
+HumanInfo (RESPONSE_REF R)
+{
+ BYTE InfoLeft;
+
+ if (PLAYER_SAID (R, happy_to_tell_more))
+ {
+ NPCPhrase (TELL_MORE);
+
+ SET_GAME_STATE (SLYLANDRO_STACK4, 1);
+ }
+ else if (PLAYER_SAID (R, would_you_like_to_know_more))
+ {
+ NPCPhrase (YES_TELL_MORE);
+ }
+ else if (PLAYER_SAID (R, we_come_from_earth))
+ {
+ NPCPhrase (OK_EARTH);
+
+ SET_GAME_STATE (SLYLANDRO_KNOW_EARTH, 1);
+ }
+ else if (PLAYER_SAID (R, we_explore))
+ {
+ NPCPhrase (OK_EXPLORE);
+
+ SET_GAME_STATE (SLYLANDRO_KNOW_EXPLORE, 1);
+ }
+ else if (PLAYER_SAID (R, we_fight_urquan))
+ {
+ NPCPhrase (URQUAN_NICE_GUYS);
+
+ SET_GAME_STATE (SLYLANDRO_KNOW_URQUAN, 1);
+ }
+ else if (PLAYER_SAID (R, not_same_urquan))
+ {
+ NPCPhrase (PERSONALITY_CHANGE);
+
+ SET_GAME_STATE (SLYLANDRO_KNOW_URQUAN, 2);
+ }
+ else if (PLAYER_SAID (R, we_gather))
+ {
+ NPCPhrase (MAYBE_INTERESTED);
+
+ SET_GAME_STATE (SLYLANDRO_KNOW_GATHER, 1);
+ }
+
+ InfoLeft = FALSE;
+ if (GET_GAME_STATE (SLYLANDRO_KNOW_URQUAN) == 1)
+ {
+ InfoLeft = TRUE;
+ Response (not_same_urquan, HumanInfo);
+ }
+ if (!GET_GAME_STATE (SLYLANDRO_KNOW_EARTH))
+ {
+ InfoLeft = TRUE;
+ Response (we_come_from_earth, HumanInfo);
+ }
+ if (!GET_GAME_STATE (SLYLANDRO_KNOW_EXPLORE))
+ {
+ InfoLeft = TRUE;
+ Response (we_explore, HumanInfo);
+ }
+ if (!GET_GAME_STATE (SLYLANDRO_KNOW_URQUAN))
+ {
+ InfoLeft = TRUE;
+ Response (we_fight_urquan, HumanInfo);
+ }
+ if (!GET_GAME_STATE (SLYLANDRO_KNOW_GATHER))
+ {
+ InfoLeft = TRUE;
+ Response (we_gather, HumanInfo);
+ }
+
+ Response (enough_about_me, HomeWorld);
+ if (!InfoLeft)
+ {
+ SET_GAME_STATE (SLYLANDRO_STACK4, 2);
+ }
+}
+
+static void
+SlylandroInfo (RESPONSE_REF R)
+{
+ BYTE InfoLeft;
+
+ if (PLAYER_SAID (R, like_more_about_you))
+ {
+ NPCPhrase (SURE_KNOW_WHAT);
+ }
+ else if (PLAYER_SAID (R, what_about_home))
+ {
+ NPCPhrase (ABOUT_HOME);
+
+ DISABLE_PHRASE (what_about_home);
+ }
+ else if (PLAYER_SAID (R, what_about_culture))
+ {
+ NPCPhrase (ABOUT_CULTURE);
+
+ DISABLE_PHRASE (what_about_culture);
+ }
+ else if (PLAYER_SAID (R, what_about_history))
+ {
+ NPCPhrase (ABOUT_HISTORY);
+
+ DISABLE_PHRASE (what_about_history);
+ }
+ else if (PLAYER_SAID (R, what_about_biology))
+ {
+ NPCPhrase (ABOUT_BIOLOGY);
+
+ DISABLE_PHRASE (what_about_biology);
+ }
+
+ InfoLeft = FALSE;
+ if (PHRASE_ENABLED (what_about_home))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_home, SlylandroInfo);
+ }
+ if (PHRASE_ENABLED (what_about_culture))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_culture, SlylandroInfo);
+ }
+ if (PHRASE_ENABLED (what_about_history))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_history, SlylandroInfo);
+ }
+ if (PHRASE_ENABLED (what_about_biology))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_biology, SlylandroInfo);
+ }
+
+ Response (enough_info, HomeWorld);
+ if (!InfoLeft)
+ {
+ DISABLE_PHRASE (like_more_about_you);
+ }
+}
+
+static void
+FixBug (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, think_about_rep_priorities))
+ NPCPhrase (UH_OH);
+ else if (PLAYER_SAID (R, hunt_them_down))
+ {
+ NPCPhrase (GROW_TOO_FAST);
+
+ DISABLE_PHRASE (hunt_them_down);
+ }
+ else if (PLAYER_SAID (R, sue_melnorme))
+ {
+ NPCPhrase (SIGNED_WAIVER);
+
+ DISABLE_PHRASE (sue_melnorme);
+ }
+ else if (PLAYER_SAID (R, recall_signal))
+ {
+ NPCPhrase (NOT_THIS_MODEL);
+
+ DISABLE_PHRASE (recall_signal);
+ }
+
+ if (PHRASE_ENABLED (hunt_them_down))
+ Response (hunt_them_down, FixBug);
+ if (PHRASE_ENABLED (sue_melnorme))
+ Response (sue_melnorme, FixBug);
+ if (PHRASE_ENABLED (recall_signal))
+ Response (recall_signal, FixBug);
+ Response (mega_self_destruct, HomeWorld);
+}
+
+static void
+ProbeBug (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, probe_has_bug))
+ NPCPhrase (NO_IT_DOESNT);
+ else if (PLAYER_SAID (R, tell_me_about_rep_2))
+ {
+ NPCPhrase (REP_NO_PROBLEM);
+
+ DISABLE_PHRASE (tell_me_about_rep_2);
+ }
+ else if (PLAYER_SAID (R, what_about_rep_priorities))
+ {
+ NPCPhrase (MAXIMUM_SO_WHAT);
+
+ DISABLE_PHRASE (what_about_rep_priorities);
+ }
+ else if (PLAYER_SAID (R, tell_me_about_attack))
+ {
+ NPCPhrase (ATTACK_NO_PROBLEM);
+
+ DISABLE_PHRASE (tell_me_about_attack);
+ }
+
+ if (PHRASE_ENABLED (tell_me_about_rep_2))
+ Response (tell_me_about_rep_2, ProbeBug);
+ else if (PHRASE_ENABLED (what_about_rep_priorities))
+ Response (what_about_rep_priorities, ProbeBug);
+ else
+ {
+ Response (think_about_rep_priorities, FixBug);
+ }
+ if (PHRASE_ENABLED (tell_me_about_attack))
+ Response (tell_me_about_attack, ProbeBug);
+}
+
+static void ProbeInfo (RESPONSE_REF R);
+
+static void
+ProbeFunction (RESPONSE_REF R)
+{
+ BYTE LastStack;
+ RESPONSE_REF pStr[2];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = 0;
+ if (PLAYER_SAID (R, talk_more_probe_attack))
+ {
+ NPCPhrase (NO_PROBLEM_BUT_SURE);
+ }
+ else if (PLAYER_SAID (R, tell_me_about_basics))
+ {
+ NPCPhrase (BASIC_COMMANDS);
+
+ SET_GAME_STATE (PLAYER_KNOWS_PROGRAM, 1);
+ DISABLE_PHRASE (tell_basics_again);
+ }
+ else if (PLAYER_SAID (R, tell_basics_again))
+ {
+ NPCPhrase (OK_BASICS_AGAIN);
+
+ DISABLE_PHRASE (tell_basics_again);
+ }
+ else if (PLAYER_SAID (R, what_effect))
+ {
+ NPCPhrase (AFFECTS_BEHAVIOR);
+
+ SET_GAME_STATE (PLAYER_KNOWS_EFFECTS, 1);
+ DISABLE_PHRASE (what_effect);
+ }
+ else if (PLAYER_SAID (R, tell_me_about_rep_1))
+ {
+ NPCPhrase (ABOUT_REP);
+
+ LastStack = 2;
+ SET_GAME_STATE (SLYLANDRO_STACK8, 3);
+ }
+ else if (PLAYER_SAID (R, what_set_priority))
+ {
+ NPCPhrase (MAXIMUM);
+
+ SET_GAME_STATE (PLAYER_KNOWS_PRIORITY, 1);
+ DISABLE_PHRASE (what_set_priority);
+ }
+ else if (PLAYER_SAID (R, how_does_probe_defend))
+ {
+ NPCPhrase (ONLY_SELF_DEFENSE);
+
+ LastStack = 1;
+ SET_GAME_STATE (SLYLANDRO_STACK9, 1);
+ }
+ else if (PLAYER_SAID (R, combat_behavior))
+ {
+ NPCPhrase (MISSILE_BATTERIES);
+
+ LastStack = 1;
+ SET_GAME_STATE (SLYLANDRO_STACK9, 2);
+ }
+ else if (PLAYER_SAID (R, what_missile_batteries))
+ {
+ NPCPhrase (LIGHTNING_ONLY_FOR_HARVESTING);
+
+ SET_GAME_STATE (SLYLANDRO_STACK9, 3);
+ }
+
+ switch (GET_GAME_STATE (SLYLANDRO_STACK9))
+ {
+ case 0:
+ pStr[0] = how_does_probe_defend;
+ break;
+ case 1:
+ pStr[0] = combat_behavior;
+ break;
+ case 2:
+ pStr[0] = what_missile_batteries;
+ break;
+ }
+ switch (GET_GAME_STATE (SLYLANDRO_STACK8))
+ {
+ case 2:
+ pStr[1] = tell_me_about_rep_1;
+ break;
+ case 3:
+ if (PHRASE_ENABLED (what_set_priority))
+ pStr[1] = what_set_priority;
+ break;
+ }
+
+ if (LastStack && pStr[LastStack - 1])
+ Response (pStr[LastStack - 1], ProbeFunction);
+ if (!GET_GAME_STATE (PLAYER_KNOWS_PROGRAM))
+ Response (tell_me_about_basics, ProbeFunction);
+ else
+ {
+ if (GET_GAME_STATE (PLAYER_KNOWS_PRIORITY))
+ {
+ if (GET_GAME_STATE (PLAYER_KNOWS_EFFECTS))
+ {
+ Response (probe_has_bug, ProbeBug);
+ }
+ if (PHRASE_ENABLED (what_effect))
+ Response (what_effect, ProbeFunction);
+ }
+ if (PHRASE_ENABLED (tell_basics_again))
+ Response (tell_basics_again, ProbeFunction);
+ }
+ if (LastStack == 0)
+ {
+ do
+ {
+ if (pStr[LastStack])
+ Response (pStr[LastStack], ProbeFunction);
+ } while (++LastStack < 2);
+ }
+ else
+ {
+ LastStack = (LastStack - 1) ^ 1;
+ if (pStr[LastStack])
+ Response (pStr[LastStack], ProbeFunction);
+ }
+
+ Response (enough_problem, ProbeInfo);
+}
+
+static void
+ProbeInfo (RESPONSE_REF R)
+{
+ BYTE i, LastStack, InfoLeft;
+ RESPONSE_REF pStr[3];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = 0;
+ if (PLAYER_SAID (R, what_are_probes))
+ {
+ NPCPhrase (PROBES_ARE);
+
+ SET_GAME_STATE (SLYLANDRO_STACK5, 1);
+ }
+ else if (PLAYER_SAID (R, know_more_probe))
+ NPCPhrase (OK_WHAT_MORE_PROBE);
+ else if (PLAYER_SAID (R, why_probe_always_attack))
+ {
+ NPCPhrase (ONLY_DEFEND);
+
+ SET_GAME_STATE (SLYLANDRO_STACK6, 1);
+ }
+ else if (PLAYER_SAID (R, talk_more_probe_attack))
+ {
+ ProbeFunction (R);
+ return;
+ }
+ else if (PLAYER_SAID (R, where_probes_from))
+ {
+ NPCPhrase (PROBES_FROM_MELNORME);
+
+ LastStack = 1;
+ SET_GAME_STATE (SLYLANDRO_STACK7, 1);
+ }
+ else if (PLAYER_SAID (R, why_sell))
+ {
+ NPCPhrase (SELL_FOR_INFO);
+
+ LastStack = 1;
+ SET_GAME_STATE (SLYLANDRO_STACK7, 2);
+ }
+ else if (PLAYER_SAID (R, how_long_ago))
+ {
+ NPCPhrase (FIFTY_THOUSAND_ROTATIONS);
+
+ SET_GAME_STATE (SLYLANDRO_STACK7, 3);
+ }
+ else if (PLAYER_SAID (R, whats_probes_mission))
+ {
+ NPCPhrase (SEEK_OUT_NEW_LIFE);
+
+ LastStack = 2;
+ SET_GAME_STATE (SLYLANDRO_STACK8, 1);
+ }
+ else if (PLAYER_SAID (R, if_only_one))
+ {
+ NPCPhrase (THEY_REPLICATE);
+
+ SET_GAME_STATE (SLYLANDRO_STACK8, 2);
+ }
+ else if (PLAYER_SAID (R, enough_problem))
+ NPCPhrase (OK_ENOUGH_PROBLEM);
+
+ if (!GET_GAME_STATE (SLYLANDRO_KNOW_BROKEN)
+ && GET_GAME_STATE (PROBE_EXHIBITED_BUG))
+ {
+ switch (GET_GAME_STATE (SLYLANDRO_STACK6))
+ {
+ case 0:
+ pStr[0] = why_probe_always_attack;
+ break;
+ case 1:
+ pStr[0] = talk_more_probe_attack;
+ break;
+ }
+ }
+ switch (GET_GAME_STATE (SLYLANDRO_STACK7))
+ {
+ case 0:
+ pStr[1] = where_probes_from;
+ break;
+ case 1:
+ pStr[1] = why_sell;
+ break;
+ case 2:
+ pStr[1] = how_long_ago;
+ break;
+ }
+ switch (GET_GAME_STATE (SLYLANDRO_STACK8))
+ {
+ case 0:
+ pStr[2] = whats_probes_mission;
+ break;
+ case 1:
+ pStr[2] = if_only_one;
+ break;
+ }
+
+ InfoLeft = FALSE;
+ if (pStr[LastStack])
+ {
+ InfoLeft = TRUE;
+ Response (pStr[LastStack], ProbeInfo);
+ }
+ for (i = 0; i < 3; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ {
+ InfoLeft = TRUE;
+ Response (pStr[i], ProbeInfo);
+ }
+ }
+
+ Response (enough_probe, HomeWorld);
+ if (!InfoLeft)
+ {
+ DISABLE_PHRASE (know_more_probe);
+ }
+}
+
+static void
+HomeWorld (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[3];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = 0;
+ if (PLAYER_SAID (R, we_are_us0))
+ {
+ NPCPhrase (TERRIBLY_EXCITING);
+
+ SET_GAME_STATE (SLYLANDRO_STACK1, 1);
+ DISABLE_PHRASE (we_are_us0);
+ }
+ else if (PLAYER_SAID (R, what_other_visitors))
+ {
+ NPCPhrase (VISITORS);
+
+ SET_GAME_STATE (PLAYER_KNOWS_PROBE, 1);
+ SET_GAME_STATE (SLYLANDRO_STACK1, 2);
+ }
+ else if (PLAYER_SAID (R, any_other_visitors))
+ {
+ NPCPhrase (LONG_AGO);
+
+ SET_GAME_STATE (SLYLANDRO_STACK1, 3);
+ }
+ else if (PLAYER_SAID (R, what_about_sentient_milieu))
+ {
+ NPCPhrase (MET_TAALO_THEY_ARE_FROM);
+
+ SET_GAME_STATE (SLYLANDRO_STACK1, 4);
+ }
+ else if (PLAYER_SAID (R, who_else))
+ {
+ NPCPhrase (PRECURSORS);
+
+ SET_GAME_STATE (SLYLANDRO_STACK1, 5);
+ }
+ else if (PLAYER_SAID (R, precursors_yow))
+ {
+ NPCPhrase (ABOUT_PRECURSORS);
+
+ SET_GAME_STATE (SLYLANDRO_STACK1, 6);
+ }
+ else if (PLAYER_SAID (R, must_know_more))
+ {
+ NPCPhrase (ALL_WE_KNOW);
+
+ SET_GAME_STATE (SLYLANDRO_STACK1, 7);
+ }
+ else if (PLAYER_SAID (R, who_are_you))
+ {
+ NPCPhrase (WE_ARE_SLY);
+
+ LastStack = 1;
+ SET_GAME_STATE (SLYLANDRO_STACK2, 1);
+ }
+ else if (PLAYER_SAID (R, where_are_you))
+ {
+ NPCPhrase (DOWN_HERE);
+
+ LastStack = 2;
+ SET_GAME_STATE (SLYLANDRO_STACK3, 1);
+ }
+ else if (PLAYER_SAID (R, thats_impossible_1))
+ {
+ NPCPhrase (NO_ITS_NOT_1);
+
+ LastStack = 2;
+ SET_GAME_STATE (SLYLANDRO_STACK3, 2);
+ }
+ else if (PLAYER_SAID (R, thats_impossible_2))
+ {
+ NPCPhrase (NO_ITS_NOT_2);
+
+ LastStack = 2;
+ SET_GAME_STATE (SLYLANDRO_STACK3, 3);
+ }
+ else if (PLAYER_SAID (R, like_more_about_you))
+ {
+ SlylandroInfo (R);
+ return;
+ }
+ else if (PLAYER_SAID (R, enough_about_me))
+ NPCPhrase (OK_ENOUGH_YOU);
+ else if (PLAYER_SAID (R, enough_info))
+ NPCPhrase (OK_ENOUGH_INFO);
+ else if (PLAYER_SAID (R, enough_probe))
+ NPCPhrase (OK_ENOUGH_PROBE);
+ else if (PLAYER_SAID (R, mega_self_destruct))
+ {
+ NPCPhrase (WHY_YES_THERE_IS);
+
+ SET_GAME_STATE (SLYLANDRO_KNOW_BROKEN, 1);
+ SET_GAME_STATE (DESTRUCT_CODE_ON_SHIP, 1);
+ i = GET_GAME_STATE (SLYLANDRO_MULTIPLIER) + 1;
+ SET_GAME_STATE (SLYLANDRO_MULTIPLIER, i);
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, SLYLANDRO_RAMP_DOWN);
+ }
+
+ switch (GET_GAME_STATE (SLYLANDRO_STACK1))
+ {
+ case 0:
+ construct_response (shared_phrase_buf,
+ we_are_us0,
+ GLOBAL_SIS (CommanderName),
+ we_are_us1,
+ GLOBAL_SIS (ShipName),
+ we_are_us2,
+ (UNICODE*)NULL);
+ pStr[0] = we_are_us0;
+ break;
+ case 1:
+ pStr[0] = what_other_visitors;
+ break;
+ case 2:
+ pStr[0] = any_other_visitors;
+ break;
+ case 3:
+ pStr[0] = what_about_sentient_milieu;
+ break;
+ case 4:
+ pStr[0] = who_else;
+ break;
+ case 5:
+ pStr[0] = precursors_yow;
+ break;
+ case 6:
+ pStr[0] = must_know_more;
+ break;
+ }
+ switch (GET_GAME_STATE (SLYLANDRO_STACK2))
+ {
+ case 0:
+ pStr[1] = who_are_you;
+ break;
+ case 1:
+ if (PHRASE_ENABLED (like_more_about_you))
+ pStr[1] = like_more_about_you;
+ break;
+ }
+ switch (GET_GAME_STATE (SLYLANDRO_STACK3))
+ {
+ case 0:
+ pStr[2] = where_are_you;
+ break;
+ case 1:
+ pStr[2] = thats_impossible_1;
+ break;
+ case 2:
+ pStr[2] = thats_impossible_2;
+ break;
+ }
+
+ if (pStr[LastStack])
+ {
+ if (pStr[LastStack] != we_are_us0)
+ Response (pStr[LastStack], HomeWorld);
+ else
+ DoResponsePhrase (pStr[LastStack], HomeWorld, shared_phrase_buf);
+ }
+ for (i = 0; i < 3; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ {
+ if (pStr[i] != we_are_us0)
+ Response (pStr[i], HomeWorld);
+ else
+ DoResponsePhrase (pStr[i], HomeWorld, shared_phrase_buf);
+ }
+ }
+ if (GET_GAME_STATE (SLYLANDRO_STACK1))
+ {
+ switch (GET_GAME_STATE (SLYLANDRO_STACK4))
+ {
+ case 0:
+ Response (happy_to_tell_more, HumanInfo);
+ break;
+ case 1:
+ Response (would_you_like_to_know_more, HumanInfo);
+ break;
+ }
+ }
+ if (GET_GAME_STATE (PLAYER_KNOWS_PROBE)
+ && !GET_GAME_STATE (SLYLANDRO_KNOW_BROKEN))
+ {
+ switch (GET_GAME_STATE (SLYLANDRO_STACK5))
+ {
+ case 0:
+ Response (what_are_probes, ProbeInfo);
+ break;
+ case 1:
+ if (PHRASE_ENABLED (know_more_probe))
+ Response (know_more_probe, ProbeInfo);
+ break;
+ }
+ }
+ Response (bye, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (GET_GAME_STATE (SLYLANDRO_KNOW_BROKEN)
+ && (NumVisits = GET_GAME_STATE (RECALL_VISITS)) == 0)
+ {
+ NPCPhrase (RECALL_PROGRAM_1);
+ ++NumVisits;
+ SET_GAME_STATE (RECALL_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (SLYLANDRO_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SLYLANDRO_HOME_VISITS, NumVisits);
+ }
+
+ HomeWorld ((RESPONSE_REF)0);
+}
+
+static COUNT
+uninit_slylandro (void)
+{
+ return (0);
+}
+
+static void
+post_slylandro_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_slylandro_comm (void)
+{
+ LOCDATA *retval;
+
+ slylandro_desc.init_encounter_func = Intro;
+ slylandro_desc.post_encounter_func = post_slylandro_enc;
+ slylandro_desc.uninit_encounter_func = uninit_slylandro;
+
+ slylandro_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ slylandro_desc.AlienTextBaseline.y = 0;
+ slylandro_desc.AlienTextWidth = SIS_TEXT_WIDTH;
+
+ setSegue (Segue_peace);
+ retval = &slylandro_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/slyhome/strings.h b/src/uqm/comm/slyhome/strings.h
new file mode 100644
index 0000000..bedac27
--- /dev/null
+++ b/src/uqm/comm/slyhome/strings.h
@@ -0,0 +1,143 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SLYHOME_STRINGS_H
+#define SLYHOME_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ HELLO_1,
+ HELLO_2,
+ HELLO_3,
+ HELLO_4,
+ RECALL_PROGRAM_1,
+ we_are_us0,
+ we_are_us1,
+ we_are_us2,
+ TERRIBLY_EXCITING,
+ happy_to_tell_more,
+ TELL_MORE,
+ would_you_like_to_know_more,
+ YES_TELL_MORE,
+ we_come_from_earth,
+ OK_EARTH,
+ we_explore,
+ OK_EXPLORE,
+ we_fight_urquan,
+ URQUAN_NICE_GUYS,
+ not_same_urquan,
+ PERSONALITY_CHANGE,
+ we_gather,
+ MAYBE_INTERESTED,
+ enough_about_me,
+ OK_ENOUGH_YOU,
+ what_other_visitors,
+ VISITORS,
+ any_other_visitors,
+ LONG_AGO,
+ what_about_sentient_milieu,
+ MET_TAALO_THEY_ARE_FROM,
+ who_else,
+ PRECURSORS,
+ precursors_yow,
+ ABOUT_PRECURSORS,
+ must_know_more,
+ ALL_WE_KNOW,
+ who_are_you,
+ WE_ARE_SLY,
+ like_more_about_you,
+ SURE_KNOW_WHAT,
+ what_about_home,
+ ABOUT_HOME,
+ what_about_culture,
+ ABOUT_CULTURE,
+ what_about_history,
+ ABOUT_HISTORY,
+ what_about_biology,
+ ABOUT_BIOLOGY,
+ enough_info,
+ OK_ENOUGH_INFO,
+ where_are_you,
+ DOWN_HERE,
+ thats_impossible_1,
+ NO_ITS_NOT_1,
+ thats_impossible_2,
+ NO_ITS_NOT_2,
+ bye,
+ GOODBYE_1,
+ GOODBYE_2,
+ what_are_probes,
+ PROBES_ARE,
+ know_more_probe,
+ OK_WHAT_MORE_PROBE,
+ where_probes_from,
+ PROBES_FROM_MELNORME,
+ why_sell,
+ SELL_FOR_INFO,
+ how_long_ago,
+ FIFTY_THOUSAND_ROTATIONS,
+ whats_probes_mission,
+ SEEK_OUT_NEW_LIFE,
+ if_only_one,
+ THEY_REPLICATE,
+ enough_probe,
+ OK_ENOUGH_PROBE,
+ why_probe_always_attack,
+ ONLY_DEFEND,
+ talk_more_probe_attack,
+ NO_PROBLEM_BUT_SURE,
+ tell_me_about_basics,
+ BASIC_COMMANDS,
+ tell_basics_again,
+ OK_BASICS_AGAIN,
+ what_effect,
+ AFFECTS_BEHAVIOR,
+ how_does_probe_defend,
+ ONLY_SELF_DEFENSE,
+ combat_behavior,
+ MISSILE_BATTERIES,
+ what_missile_batteries,
+ LIGHTNING_ONLY_FOR_HARVESTING,
+ tell_me_about_rep_1,
+ ABOUT_REP,
+ what_set_priority,
+ MAXIMUM,
+ enough_problem,
+ OK_ENOUGH_PROBLEM,
+ probe_has_bug,
+ NO_IT_DOESNT,
+ tell_me_about_attack,
+ ATTACK_NO_PROBLEM,
+ tell_me_about_rep_2,
+ REP_NO_PROBLEM,
+ what_about_rep_priorities,
+ MAXIMUM_SO_WHAT,
+ think_about_rep_priorities,
+ UH_OH,
+ hunt_them_down,
+ GROW_TOO_FAST,
+ sue_melnorme,
+ SIGNED_WAIVER,
+ recall_signal,
+ NOT_THIS_MODEL,
+ mega_self_destruct,
+ WHY_YES_THERE_IS,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/slyland/Makeinfo b/src/uqm/comm/slyland/Makeinfo
new file mode 100644
index 0000000..10d3a53
--- /dev/null
+++ b/src/uqm/comm/slyland/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="slyland.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/slyland/resinst.h b/src/uqm/comm/slyland/resinst.h
new file mode 100644
index 0000000..d0b9a36
--- /dev/null
+++ b/src/uqm/comm/slyland/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SLYLAND_COLOR_MAP "comm.probe.colortable"
+#define SLYLAND_CONVERSATION_PHRASES "comm.probe.dialogue"
+#define SLYLAND_FONT "comm.probe.font"
+#define SLYLAND_MUSIC "comm.probe.music"
+#define SLYLAND_PMAP_ANIM "comm.probe.graphics"
diff --git a/src/uqm/comm/slyland/slyland.c b/src/uqm/comm/slyland/slyland.c
new file mode 100644
index 0000000..ae7cb44
--- /dev/null
+++ b/src/uqm/comm/slyland/slyland.c
@@ -0,0 +1,518 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include <stdlib.h>
+#include "resinst.h"
+#include "strings.h"
+
+#include "options.h"
+#include "uqm/battle.h"
+#include "uqm/setup.h"
+
+
+static const NUMBER_SPEECH_DESC probe_numbers_english;
+
+static LOCDATA slylandro_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ SLYLAND_PMAP_ANIM, /* AlienFrame */
+ SLYLAND_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ SLYLAND_COLOR_MAP, /* AlienColorMap */
+ SLYLAND_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ SLYLAND_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 6, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 1, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 3, /* RestartRate */
+ (1 << 3) /* BlockMask */
+ },
+ {
+ 10, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4) /* BlockMask */
+ },
+ {
+ 18, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5) /* BlockMask */
+ },
+ {
+ 26, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 3, /* RestartRate */
+ (1 << 0) /* BlockMask */
+ },
+ {
+ 34, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 3, /* RestartRate */
+ (1 << 1) /* BlockMask */
+ },
+ {
+ 42, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 3, /* RestartRate */
+ (1 << 2) /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ &probe_numbers_english, /* AlienNumberSpeech - default */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static COUNT probe_digit_names[] =
+{
+ ENUMERATE_ZERO,
+ ENUMERATE_ONE,
+ ENUMERATE_TWO,
+ ENUMERATE_THREE,
+ ENUMERATE_FOUR,
+ ENUMERATE_FIVE,
+ ENUMERATE_SIX,
+ ENUMERATE_SEVEN,
+ ENUMERATE_EIGHT,
+ ENUMERATE_NINE,
+};
+
+static COUNT probe_teen_names[] =
+{
+ ENUMERATE_TEN,
+ ENUMERATE_ELEVEN,
+ ENUMERATE_TWELVE,
+ ENUMERATE_THIRTEEN,
+ ENUMERATE_FOURTEEN,
+ ENUMERATE_FIFTEEN,
+ ENUMERATE_SIXTEEN,
+ ENUMERATE_SEVENTEEN,
+ ENUMERATE_EIGHTEEN,
+ ENUMERATE_NINETEEN,
+};
+
+static COUNT probe_tens_names[] =
+{
+ 0, /* invalid */
+ 0, /* skip digit */
+ ENUMERATE_TWENTY,
+ ENUMERATE_THIRTY,
+ ENUMERATE_FOURTY,
+ ENUMERATE_FIFTY,
+ ENUMERATE_SIXTY,
+ ENUMERATE_SEVENTY,
+ ENUMERATE_EIGHTY,
+ ENUMERATE_NINETY,
+};
+
+static const NUMBER_SPEECH_DESC probe_numbers_english =
+{
+ 5, /* NumDigits */
+ {
+ { /* 1000-999999 */
+ 1000, /* Divider */
+ 0, /* Subtrahend */
+ NULL, /* StrDigits - recurse */
+ NULL, /* Names - not used */
+ ENUMERATE_THOUSAND /* CommonIndex */
+ },
+ { /* 100-999 */
+ 100, /* Divider */
+ 0, /* Subtrahend */
+ probe_digit_names, /* StrDigits */
+ NULL, /* Names - not used */
+ ENUMERATE_HUNDRED /* CommonIndex */
+ },
+ { /* 20-99 */
+ 10, /* Divider */
+ 0, /* Subtrahend */
+ probe_tens_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ },
+ { /* 10-19 */
+ 1, /* Divider */
+ 10, /* Subtrahend */
+ probe_teen_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ },
+ { /* 0-9 */
+ 1, /* Divider */
+ 0, /* Subtrahend */
+ probe_digit_names, /* StrDigits */
+ NULL, /* Names - not used */
+ 0 /* CommonIndex - not used */
+ }
+ }
+};
+
+static RESPONSE_REF threat,
+ something_wrong,
+ we_are_us,
+ why_attack,
+ bye;
+
+static void
+sayCoord (int coord)
+{
+ int ac;
+
+ ac = abs (coord);
+
+ NPCPhrase_splice (coord < 0 ? COORD_MINUS : COORD_PLUS);
+ NPCNumber (ac / 10, "%03d");
+ NPCPhrase_splice (COORD_POINT);
+ NPCNumber (ac % 10, NULL);
+}
+
+static void
+CombatIsInevitable (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ if (GET_GAME_STATE (DESTRUCT_CODE_ON_SHIP))
+ Response (destruct_code, CombatIsInevitable);
+ switch (GET_GAME_STATE (SLYLANDRO_PROBE_THREAT))
+ {
+ case 0:
+ threat = threat_1;
+ break;
+ case 1:
+ threat = threat_2;
+ break;
+ case 2:
+ threat = threat_3;
+ break;
+ default:
+ threat = threat_4;
+ break;
+ }
+ Response (threat, CombatIsInevitable);
+ switch (GET_GAME_STATE (SLYLANDRO_PROBE_WRONG))
+ {
+ case 0:
+ something_wrong = something_wrong_1;
+ break;
+ case 1:
+ something_wrong = something_wrong_2;
+ break;
+ case 2:
+ something_wrong = something_wrong_3;
+ break;
+ default:
+ something_wrong = something_wrong_4;
+ break;
+ }
+ Response (something_wrong, CombatIsInevitable);
+ switch (GET_GAME_STATE (SLYLANDRO_PROBE_ID))
+ {
+ case 0:
+ we_are_us = we_are_us_1;
+ break;
+ case 1:
+ we_are_us = we_are_us_2;
+ break;
+ case 2:
+ we_are_us = we_are_us_3;
+ break;
+ default:
+ we_are_us = we_are_us_4;
+ break;
+ }
+ Response (we_are_us, CombatIsInevitable);
+ switch (GET_GAME_STATE (SLYLANDRO_PROBE_INFO))
+ {
+ case 0:
+ why_attack = why_attack_1;
+ break;
+ case 1:
+ why_attack = why_attack_2;
+ break;
+ case 2:
+ why_attack = why_attack_3;
+ break;
+ default:
+ why_attack = why_attack_4;
+ break;
+ }
+ Response (why_attack, CombatIsInevitable);
+ switch (GET_GAME_STATE (SLYLANDRO_PROBE_EXIT))
+ {
+ case 0:
+ bye = bye_1;
+ break;
+ case 1:
+ bye = bye_2;
+ break;
+ case 2:
+ bye = bye_3;
+ break;
+ default:
+ bye = bye_4;
+ break;
+ }
+ Response (bye, CombatIsInevitable);
+ }
+ else if (PLAYER_SAID (R, destruct_code))
+ {
+ NPCPhrase (DESTRUCT_SEQUENCE);
+ setSegue (Segue_victory);
+ }
+ else
+ {
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, threat))
+ {
+ NumVisits = GET_GAME_STATE (SLYLANDRO_PROBE_THREAT);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (PROGRAMMED_TO_DEFEND_1);
+ break;
+ case 1:
+ NPCPhrase (PROGRAMMED_TO_DEFEND_2);
+ break;
+ case 2:
+ NPCPhrase (PROGRAMMED_TO_DEFEND_3);
+ break;
+ case 3:
+ NPCPhrase (PROGRAMMED_TO_DEFEND_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SLYLANDRO_PROBE_THREAT, NumVisits);
+ }
+ else if (PLAYER_SAID (R, something_wrong))
+ {
+ NumVisits = GET_GAME_STATE (SLYLANDRO_PROBE_WRONG);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NOMINAL_FUNCTION_1);
+ break;
+ case 1:
+ NPCPhrase (NOMINAL_FUNCTION_2);
+ break;
+ case 2:
+ NPCPhrase (NOMINAL_FUNCTION_3);
+ break;
+ case 3:
+ NPCPhrase (NOMINAL_FUNCTION_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SLYLANDRO_PROBE_WRONG, NumVisits);
+ }
+ else if (PLAYER_SAID (R, we_are_us))
+ {
+ NumVisits = GET_GAME_STATE (SLYLANDRO_PROBE_ID);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (THIS_IS_PROBE_1);
+ break;
+ case 1:
+ NPCPhrase (THIS_IS_PROBE_2);
+ break;
+ case 2:
+ NPCPhrase (THIS_IS_PROBE_3);
+ break;
+ case 3:
+ {
+ SIZE dx, dy;
+
+ // Probe's coordinate system is {-Y,X} relative to B Corvi
+ dx = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x)) - 333;
+ dy = 9812 - LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+
+ NPCPhrase (THIS_IS_PROBE_40);
+ sayCoord (dy);
+ NPCPhrase_splice (THIS_IS_PROBE_41);
+ sayCoord (dx);
+ NPCPhrase (THIS_IS_PROBE_42);
+
+ --NumVisits;
+ break;
+ }
+ }
+ SET_GAME_STATE (SLYLANDRO_PROBE_ID, NumVisits);
+ }
+ else if (PLAYER_SAID (R, why_attack))
+ {
+ NumVisits = GET_GAME_STATE (SLYLANDRO_PROBE_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (PEACEFUL_MISSION_1);
+ break;
+ case 1:
+ NPCPhrase (PEACEFUL_MISSION_2);
+ break;
+ case 2:
+ NPCPhrase (PEACEFUL_MISSION_3);
+ break;
+ case 3:
+ NPCPhrase (PEACEFUL_MISSION_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SLYLANDRO_PROBE_INFO, NumVisits);
+ }
+ else if (PLAYER_SAID (R, bye))
+ {
+ NumVisits = GET_GAME_STATE (SLYLANDRO_PROBE_EXIT);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GOODBYE_1);
+ break;
+ case 1:
+ NPCPhrase (GOODBYE_2);
+ break;
+ case 2:
+ NPCPhrase (GOODBYE_3);
+ break;
+ case 3:
+ NPCPhrase (GOODBYE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SLYLANDRO_PROBE_EXIT, NumVisits);
+ }
+
+ NPCPhrase (HOSTILE);
+
+ SET_GAME_STATE (PROBE_EXHIBITED_BUG, 1);
+ setSegue (Segue_hostile);
+ }
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SLYLANDRO_PROBE_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (WE_COME_IN_PEACE_1);
+ break;
+ case 1:
+ NPCPhrase (WE_COME_IN_PEACE_2);
+ break;
+ case 2:
+ NPCPhrase (WE_COME_IN_PEACE_3);
+ break;
+ case 3:
+ NPCPhrase (WE_COME_IN_PEACE_4);
+ break;
+ case 4:
+ NPCPhrase (WE_COME_IN_PEACE_5);
+ break;
+ case 5:
+ NPCPhrase (WE_COME_IN_PEACE_6);
+ break;
+ case 6:
+ NPCPhrase (WE_COME_IN_PEACE_7);
+ break;
+ case 7:
+ NPCPhrase (WE_COME_IN_PEACE_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SLYLANDRO_PROBE_VISITS, NumVisits);
+
+ CombatIsInevitable ((RESPONSE_REF)0);
+}
+
+static COUNT
+uninit_slyland (void)
+{
+ return (0);
+}
+
+static void
+post_slyland_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_slyland_comm (void)
+{
+ LOCDATA *retval;
+
+ slylandro_desc.init_encounter_func = Intro;
+ slylandro_desc.post_encounter_func = post_slyland_enc;
+ slylandro_desc.uninit_encounter_func = uninit_slyland;
+
+ slylandro_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ slylandro_desc.AlienTextBaseline.y = 0;
+ slylandro_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ setSegue (Segue_hostile);
+ retval = &slylandro_desc;
+
+ return (retval);
+}
+
diff --git a/src/uqm/comm/slyland/strings.h b/src/uqm/comm/slyland/strings.h
new file mode 100644
index 0000000..4a1ef17
--- /dev/null
+++ b/src/uqm/comm/slyland/strings.h
@@ -0,0 +1,113 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SLYLAND_STRINGS_H
+#define SLYLAND_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ WE_COME_IN_PEACE_1,
+ WE_COME_IN_PEACE_2,
+ WE_COME_IN_PEACE_3,
+ WE_COME_IN_PEACE_4,
+ WE_COME_IN_PEACE_5,
+ WE_COME_IN_PEACE_6,
+ WE_COME_IN_PEACE_7,
+ WE_COME_IN_PEACE_8,
+ threat_1,
+ threat_2,
+ threat_3,
+ threat_4,
+ PROGRAMMED_TO_DEFEND_1,
+ PROGRAMMED_TO_DEFEND_2,
+ PROGRAMMED_TO_DEFEND_3,
+ PROGRAMMED_TO_DEFEND_4,
+ something_wrong_1,
+ something_wrong_2,
+ something_wrong_3,
+ something_wrong_4,
+ NOMINAL_FUNCTION_1,
+ NOMINAL_FUNCTION_2,
+ NOMINAL_FUNCTION_3,
+ NOMINAL_FUNCTION_4,
+ we_are_us_1,
+ we_are_us_2,
+ we_are_us_3,
+ we_are_us_4,
+ THIS_IS_PROBE_1,
+ THIS_IS_PROBE_2,
+ THIS_IS_PROBE_3,
+ THIS_IS_PROBE_40,
+ THIS_IS_PROBE_41,
+ THIS_IS_PROBE_42,
+ why_attack_1,
+ why_attack_2,
+ why_attack_3,
+ why_attack_4,
+ PEACEFUL_MISSION_1,
+ PEACEFUL_MISSION_2,
+ PEACEFUL_MISSION_3,
+ PEACEFUL_MISSION_4,
+ bye_1,
+ bye_2,
+ bye_3,
+ bye_4,
+ GOODBYE_1,
+ GOODBYE_2,
+ GOODBYE_3,
+ GOODBYE_4,
+ HOSTILE,
+ DESTRUCT_SEQUENCE,
+ destruct_code,
+ COORD_PLUS,
+ COORD_MINUS,
+ COORD_POINT,
+ ENUMERATE_HUNDRED,
+ ENUMERATE_THOUSAND,
+ ENUMERATE_ZERO,
+ ENUMERATE_ONE,
+ ENUMERATE_TWO,
+ ENUMERATE_THREE,
+ ENUMERATE_FOUR,
+ ENUMERATE_FIVE,
+ ENUMERATE_SIX,
+ ENUMERATE_SEVEN,
+ ENUMERATE_EIGHT,
+ ENUMERATE_NINE,
+ ENUMERATE_TEN,
+ ENUMERATE_ELEVEN,
+ ENUMERATE_TWELVE,
+ ENUMERATE_THIRTEEN,
+ ENUMERATE_FOURTEEN,
+ ENUMERATE_FIFTEEN,
+ ENUMERATE_SIXTEEN,
+ ENUMERATE_SEVENTEEN,
+ ENUMERATE_EIGHTEEN,
+ ENUMERATE_NINETEEN,
+ ENUMERATE_TWENTY,
+ ENUMERATE_THIRTY,
+ ENUMERATE_FOURTY,
+ ENUMERATE_FIFTY,
+ ENUMERATE_SIXTY,
+ ENUMERATE_SEVENTY,
+ ENUMERATE_EIGHTY,
+ ENUMERATE_NINETY,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/spahome/Makeinfo b/src/uqm/comm/spahome/Makeinfo
new file mode 100644
index 0000000..6972013
--- /dev/null
+++ b/src/uqm/comm/spahome/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="spahome.c"
+uqm_HFILES="strings.h"
diff --git a/src/uqm/comm/spahome/spahome.c b/src/uqm/comm/spahome/spahome.c
new file mode 100644
index 0000000..190e3bb
--- /dev/null
+++ b/src/uqm/comm/spahome/spahome.c
@@ -0,0 +1,1018 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "../spathi/resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/gameev.h"
+
+
+static LOCDATA spahome_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ SPATHI_HOME_PMAP_ANIM, /* AlienFrame */
+ SPATHI_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ SPATHI_HOME_COLOR_MAP, /* AlienColorMap */
+ SPATHI_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ SPATHI_HOME_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 14, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 4, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 9, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 10) | (1 << 11), /* BlockMask */
+ },
+ {
+ 13, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND / 20, 0, /* RestartRate */
+ (1 << 4) | (1 << 5) /* BlockMask */
+ },
+ {
+ 19, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 3) | (1 << 5), /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* RestartRate */
+ (1 << 3) | (1 << 4)
+ | (1 << 10), /* BlockMask */
+ },
+ {
+ 26, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 10), /* BlockMask */
+ },
+ {
+ 29, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 32, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND / 20, 0, /* RestartRate */
+ (1 << 9) | (1 << 10), /* BlockMask */
+ },
+ {
+ 39, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 8) | (1 << 10), /* BlockMask */
+ },
+ {
+ 42, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, 0, /* RestartRate */
+ (1 << 8) | (1 << 9)
+ | (1 << 6) | (1 << 2)
+ | (1 << 11) | (1 << 5), /* BlockMask */
+ },
+ {
+ 46, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* RestartRate */
+ (1 << 2) | (1 << 10), /* BlockMask */
+ },
+ {
+ 50, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND / 20, 0, /* RestartRate */
+ (1 << 13), /* BlockMask */
+ },
+ {
+ 56, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 12), /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+ if (PLAYER_SAID (R, we_attack_again))
+ {
+ NPCPhrase (WE_FIGHT_AGAIN);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, surrender_or_die))
+ {
+ NPCPhrase (DEFEND_OURSELVES);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, we_are_vindicator0))
+ {
+ NPCPhrase (NO_PASSWORD);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, gort_merenga)
+ || PLAYER_SAID (R, guph_florp)
+ || PLAYER_SAID (R, wagngl_fthagn)
+ || PLAYER_SAID (R, pleeese))
+ {
+ NPCPhrase (WRONG_PASSWORD);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, screw_password))
+ {
+ NPCPhrase (NO_PASSWORD);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, bye_no_ally_offer))
+ NPCPhrase (GOODBYE_NO_ALLY_OFFER);
+ else if (PLAYER_SAID (R, bye_angry_spathi))
+ NPCPhrase (GOODBYE_ANGRY_SPATHI);
+ else if (PLAYER_SAID (R, bye_ally))
+ NPCPhrase (GOODBYE_ALLY);
+ else if (PLAYER_SAID (R, already_got_them))
+ {
+ NPCPhrase (EARLY_BIRD_CHECK);
+
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 0);
+ SET_GAME_STATE (SPATHI_VISITS, 0);
+ SET_GAME_STATE (SPATHI_PARTY, 1);
+ SET_GAME_STATE (SPATHI_MANNER, 3);
+ }
+ else if (PLAYER_SAID (R, too_dangerous))
+ NPCPhrase (WE_AGREE);
+ else if (PLAYER_SAID (R, think_more))
+ NPCPhrase (COWARD);
+ else if (PLAYER_SAID (R, i_accept))
+ {
+ NPCPhrase (AWAIT_RETURN);
+
+ SET_GAME_STATE (SPATHI_QUEST, 1);
+ SET_GAME_STATE (SPATHI_MANNER, 3);
+ SET_GAME_STATE (SPATHI_VISITS, 0);
+ }
+ else if (PLAYER_SAID (R, do_as_we_say))
+ {
+ NPCPhrase (DEPART_FOR_EARTH);
+
+ SetRaceAllied (SPATHI_SHIP, TRUE);
+ AddEvent (RELATIVE_EVENT, 6, 0, 0, SPATHI_SHIELD_EVENT);
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 0);
+ SET_GAME_STATE (SPATHI_VISITS, 0);
+ }
+ else if (PLAYER_SAID (R, killed_them_all_1))
+ {
+ NPCPhrase (WILL_CHECK_1);
+
+ if (!GET_GAME_STATE (SPATHI_CREATURES_ELIMINATED))
+ {
+ SET_GAME_STATE (LIED_ABOUT_CREATURES, 1);
+ }
+ else
+ {
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 0);
+ SET_GAME_STATE (SPATHI_VISITS, 0);
+ SET_GAME_STATE (SPATHI_PARTY, 1);
+ SET_GAME_STATE (SPATHI_MANNER, 3);
+ }
+ }
+ else if (PLAYER_SAID (R, killed_them_all_2))
+ {
+ NPCPhrase (WILL_CHECK_2);
+
+ if (!GET_GAME_STATE (SPATHI_CREATURES_ELIMINATED))
+ {
+ SET_GAME_STATE (LIED_ABOUT_CREATURES, 2);
+ }
+ else
+ {
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 0);
+ SET_GAME_STATE (SPATHI_VISITS, 0);
+ SET_GAME_STATE (SPATHI_PARTY, 1);
+ SET_GAME_STATE (SPATHI_MANNER, 3);
+ }
+ }
+ else if (PLAYER_SAID (R, bye_before_party))
+ {
+ NPCPhrase (GOODBYE_BEFORE_PARTY);
+ }
+ else if (PLAYER_SAID (R, bye_from_party_1)
+ || PLAYER_SAID (R, bye_from_party_2)
+ || PLAYER_SAID (R, bye_from_party_3))
+ {
+ NPCPhrase (GOODBYE_FROM_PARTY);
+ }
+}
+
+static void SpathiAllies (RESPONSE_REF R);
+
+static void
+SpathiInfo (RESPONSE_REF R)
+{
+ BYTE InfoLeft;
+
+ InfoLeft = FALSE;
+ if (PLAYER_SAID (R, like_some_info))
+ NPCPhrase (WHAT_ABOUT);
+ else if (PLAYER_SAID (R, what_about_hierarchy))
+ {
+ NPCPhrase (ABOUT_HIERARCHY);
+
+ DISABLE_PHRASE (what_about_hierarchy);
+ }
+ else if (PLAYER_SAID (R, what_about_history))
+ {
+ NPCPhrase (ABOUT_HISTORY);
+
+ DISABLE_PHRASE (what_about_history);
+ }
+ else if (PLAYER_SAID (R, what_about_alliance))
+ {
+ NPCPhrase (ABOUT_ALLIANCE);
+
+ DISABLE_PHRASE (what_about_alliance);
+ }
+ else if (PLAYER_SAID (R, what_about_other))
+ {
+ NPCPhrase (ABOUT_OTHER);
+
+ DISABLE_PHRASE (what_about_other);
+ }
+ else if (PLAYER_SAID (R, what_about_precursors))
+ {
+ NPCPhrase (ABOUT_PRECURSORS);
+
+ DISABLE_PHRASE (what_about_precursors);
+ }
+
+ if (PHRASE_ENABLED (what_about_hierarchy))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_hierarchy, SpathiInfo);
+ }
+ if (PHRASE_ENABLED (what_about_history))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_history, SpathiInfo);
+ }
+ if (PHRASE_ENABLED (what_about_alliance))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_alliance, SpathiInfo);
+ }
+ if (PHRASE_ENABLED (what_about_other))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_other, SpathiInfo);
+ }
+ if (PHRASE_ENABLED (what_about_precursors))
+ {
+ InfoLeft = TRUE;
+ Response (what_about_precursors, SpathiInfo);
+ }
+ Response (enough_info, SpathiAllies);
+
+ if (!InfoLeft)
+ {
+ DISABLE_PHRASE (like_some_info);
+ }
+}
+
+static void
+SpathiAllies (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (R == 0)
+ {
+ NumVisits = GET_GAME_STATE (SPATHI_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_ALLIES_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_ALLIES_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_ALLIES_3);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SPATHI_HOME_VISITS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, whats_up))
+ {
+ NumVisits = GET_GAME_STATE (SPATHI_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_3);
+ SET_GAME_STATE (KNOW_URQUAN_STORY, 1);
+ SET_GAME_STATE (KNOW_KOHR_AH_STORY, 1);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_4);
+ break;
+ case 4:
+ NPCPhrase (GENERAL_INFO_5);
+ --NumVisits;
+ break;
+ case 5:
+ NPCPhrase (GENERAL_INFO_5);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SPATHI_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up);
+ }
+ else if (PLAYER_SAID (R, resources_please))
+ {
+ NPCPhrase (SORRY_NO_RESOURCES);
+
+ DISABLE_PHRASE (resources_please);
+ }
+ else if (PLAYER_SAID (R, something_fishy))
+ {
+ NPCPhrase (NOTHING_FISHY);
+
+ SET_GAME_STATE (SPATHI_INFO, 5);
+ }
+ else if (PLAYER_SAID (R, enough_info))
+ NPCPhrase (OK_ENOUGH_INFO);
+
+ if (GET_GAME_STATE (SPATHI_INFO) == 4)
+ Response (something_fishy, SpathiAllies);
+ if (PHRASE_ENABLED (whats_up))
+ Response (whats_up, SpathiAllies);
+ if (PHRASE_ENABLED (resources_please))
+ Response (resources_please, SpathiAllies);
+ if (PHRASE_ENABLED (like_some_info))
+ Response (like_some_info, SpathiInfo);
+ Response (bye_ally, ExitConversation);
+}
+
+static void
+SpathiQuest (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ if (!GET_GAME_STATE (LIED_ABOUT_CREATURES))
+ NPCPhrase (HOW_GO_EFFORTS);
+ else
+ NPCPhrase (YOU_LIED_1);
+ }
+ else if (PLAYER_SAID (R, little_mistake))
+ {
+ NPCPhrase (BIG_MISTAKE);
+
+ DISABLE_PHRASE (little_mistake);
+ }
+ else if (PLAYER_SAID (R, talk_test))
+ {
+ NPCPhrase (TEST_AGAIN);
+
+ DISABLE_PHRASE (talk_test);
+ }
+ else if (PLAYER_SAID (R, zapped_a_few))
+ {
+ NPCPhrase (YOU_FORTUNATE);
+
+ DISABLE_PHRASE (zapped_a_few);
+ }
+
+ if (!GET_GAME_STATE (LIED_ABOUT_CREATURES))
+ Response (killed_them_all_1, ExitConversation);
+ else
+ {
+ if (PHRASE_ENABLED (little_mistake))
+ {
+ Response (little_mistake, SpathiQuest);
+ }
+ Response (killed_them_all_2, ExitConversation);
+ }
+ if (!GET_GAME_STATE (SPATHI_CREATURES_ELIMINATED))
+ {
+ if (PHRASE_ENABLED (talk_test))
+ {
+ Response (talk_test, SpathiQuest);
+ }
+ if (PHRASE_ENABLED (zapped_a_few)
+ && GET_GAME_STATE (SPATHI_CREATURES_EXAMINED))
+ {
+ Response (zapped_a_few, SpathiQuest);
+ }
+ Response (bye_before_party, ExitConversation);
+ }
+}
+
+static void
+LearnQuest (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ NPCPhrase (QUEST_AGAIN);
+
+ DISABLE_PHRASE (what_test);
+ if (GET_GAME_STATE (KNOW_SPATHI_EVIL))
+ {
+ DISABLE_PHRASE (tell_evil);
+ }
+ }
+ else if (PLAYER_SAID (R, how_prove))
+ NPCPhrase (BETTER_IDEA);
+ else if (PLAYER_SAID (R, what_test))
+ {
+ NPCPhrase (WIPE_EVIL);
+
+ SET_GAME_STATE (KNOW_SPATHI_QUEST, 1);
+ DISABLE_PHRASE (what_test);
+ }
+ else if (PLAYER_SAID (R, tell_evil))
+ {
+ NPCPhrase (BEFORE_ACCEPT);
+
+ SET_GAME_STATE (KNOW_SPATHI_EVIL, 1);
+ DISABLE_PHRASE (tell_evil);
+ DISABLE_PHRASE (prove_strength);
+ }
+ else if (PLAYER_SAID (R, prove_strength))
+ {
+ NPCPhrase (YOUR_BEHAVIOR);
+
+ DISABLE_PHRASE (prove_strength);
+ }
+ else if (PLAYER_SAID (R, why_dont_you_do_it))
+ {
+ NPCPhrase (WE_WONT_BECAUSE);
+
+ DISABLE_PHRASE (why_dont_you_do_it);
+ }
+
+ if (PHRASE_ENABLED (what_test))
+ Response (what_test, LearnQuest);
+ else if (GET_GAME_STATE (SPATHI_CREATURES_ELIMINATED))
+ {
+ Response (already_got_them, ExitConversation);
+ }
+ else if (PHRASE_ENABLED (tell_evil))
+ {
+ Response (too_dangerous, ExitConversation);
+ Response (tell_evil, LearnQuest);
+ }
+ else
+ {
+ Response (too_dangerous, ExitConversation);
+ Response (think_more, ExitConversation);
+ Response (i_accept, ExitConversation);
+ }
+ if (PHRASE_ENABLED (prove_strength) && !GET_GAME_STATE (KNOW_SPATHI_QUEST))
+ Response (prove_strength, LearnQuest);
+ if (PHRASE_ENABLED (why_dont_you_do_it))
+ Response (why_dont_you_do_it, LearnQuest);
+}
+
+static void
+AllianceOffer (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, misunderstanding))
+ {
+ NPCPhrase (JUST_MISUNDERSTANDING);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1)
+ ), ONE_SECOND / 4);
+
+ SET_GAME_STATE (SPATHI_MANNER, 3);
+ SET_GAME_STATE (SPATHI_VISITS, 0);
+ }
+ else if (PLAYER_SAID (R, we_come_in_peace))
+ NPCPhrase (OF_COURSE);
+ else if (PLAYER_SAID (R, hand_in_friendship))
+ {
+ NPCPhrase (TOO_AFRAID);
+
+ DISABLE_PHRASE (hand_in_friendship);
+ }
+ else if (PLAYER_SAID (R, stronger))
+ {
+ NPCPhrase (YOURE_NOT);
+
+ DISABLE_PHRASE (stronger);
+ }
+ else if (PLAYER_SAID (R, yes_we_are))
+ {
+ NPCPhrase (NO_YOURE_NOT);
+
+ DISABLE_PHRASE (yes_we_are);
+ }
+ else if (PLAYER_SAID (R, share_info))
+ {
+ NPCPhrase (NO_INFO);
+
+ DISABLE_PHRASE (share_info);
+ }
+ else if (PLAYER_SAID (R, give_us_resources))
+ {
+ NPCPhrase (NO_RESOURCES);
+
+ DISABLE_PHRASE (give_us_resources);
+ }
+
+ if (PHRASE_ENABLED (hand_in_friendship))
+ Response (hand_in_friendship, AllianceOffer);
+ else if (PHRASE_ENABLED (stronger))
+ Response (stronger, AllianceOffer);
+ else if (PHRASE_ENABLED (yes_we_are))
+ Response (yes_we_are, AllianceOffer);
+ else
+ {
+ Response (how_prove, LearnQuest);
+ }
+ if (PHRASE_ENABLED (share_info))
+ Response (share_info, AllianceOffer);
+ if (PHRASE_ENABLED (give_us_resources))
+ Response (give_us_resources, AllianceOffer);
+}
+
+static void
+SpathiAngry (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ NPCPhrase (MEAN_GUYS_RETURN);
+
+ Response (we_apologize, SpathiAngry);
+ }
+ else /* if (R == we_apologize) */
+ {
+ NPCPhrase (DONT_BELIEVE);
+
+ Response (misunderstanding, AllianceOffer);
+ }
+
+ Response (we_attack_again, ExitConversation);
+ Response (bye_angry_spathi, ExitConversation);
+}
+
+static void
+SpathiParty (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SPATHI_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (MUST_PARTY_1);
+ break;
+ case 1:
+ NPCPhrase (MUST_PARTY_2);
+ break;
+ case 2:
+ NPCPhrase (MUST_PARTY_3);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SPATHI_HOME_VISITS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, deals_a_deal))
+ {
+ NPCPhrase (WAIT_A_WHILE);
+
+ DISABLE_PHRASE (deals_a_deal);
+ }
+ else if (PLAYER_SAID (R, how_long))
+ {
+ NPCPhrase (TEN_YEARS);
+
+ DISABLE_PHRASE (how_long);
+ }
+ else if (PLAYER_SAID (R, reneging))
+ {
+ NPCPhrase (ADULT_VIEW);
+
+ DISABLE_PHRASE (reneging);
+ }
+ else if (PLAYER_SAID (R, return_beasts))
+ {
+ NPCPhrase (WHAT_RELATIONSHIP);
+
+ DISABLE_PHRASE (return_beasts);
+ }
+ else if (PLAYER_SAID (R, minds_and_might))
+ {
+ NPCPhrase (HUH);
+
+ DISABLE_PHRASE (minds_and_might);
+ }
+ else if (PLAYER_SAID (R, fellowship))
+ {
+ NPCPhrase (WHAT);
+
+ DISABLE_PHRASE (fellowship);
+ }
+
+ if (PHRASE_ENABLED (deals_a_deal))
+ Response (deals_a_deal, SpathiParty);
+ else if (PHRASE_ENABLED (how_long))
+ Response (how_long, SpathiParty);
+ else if (PHRASE_ENABLED (reneging))
+ Response (reneging, SpathiParty);
+ else if (PHRASE_ENABLED (return_beasts))
+ Response (return_beasts, SpathiParty);
+ else
+ {
+ if (PHRASE_ENABLED (minds_and_might))
+ Response (minds_and_might, SpathiParty);
+ if (PHRASE_ENABLED (fellowship))
+ Response (fellowship, SpathiParty);
+ Response (do_as_we_say, ExitConversation);
+
+ return;
+ }
+ switch (GET_GAME_STATE (SPATHI_HOME_VISITS) - 1)
+ {
+ case 0:
+ Response (bye_from_party_1, ExitConversation);
+ break;
+ case 1:
+ Response (bye_from_party_2, ExitConversation);
+ break;
+ default:
+ Response (bye_from_party_3, ExitConversation);
+ break;
+ }
+}
+
+static void
+SpathiCouncil (RESPONSE_REF R)
+{
+ if (R == 0)
+ NPCPhrase (HELLO_AGAIN);
+ else if (PLAYER_SAID (R, good_password))
+ {
+ NPCPhrase (YES_GOOD_PASSWORD);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1)
+ ), ONE_SECOND / 4);
+
+ SET_GAME_STATE (KNOW_SPATHI_PASSWORD, 1);
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 0);
+ }
+ else if (PLAYER_SAID (R, we_come_in_peace))
+ {
+ NPCPhrase (KILLED_SPATHI);
+
+ DISABLE_PHRASE (we_come_in_peace);
+ }
+ else if (PLAYER_SAID (R, spathi_on_pluto))
+ {
+ NPCPhrase (WHERE_SPATHI);
+
+ DISABLE_PHRASE (spathi_on_pluto);
+ }
+ else if (PLAYER_SAID (R, hostage))
+ {
+ NPCPhrase (GUN_TO_HEAD);
+
+ SET_GAME_STATE (FOUND_PLUTO_SPATHI, 3);
+ DISABLE_PHRASE (hostage);
+ }
+ else if (PLAYER_SAID (R, killed_fwiffo))
+ {
+ NPCPhrase (POOR_FWIFFO);
+
+ SET_GAME_STATE (FOUND_PLUTO_SPATHI, 3);
+ DISABLE_PHRASE (killed_fwiffo);
+ }
+ else if (PLAYER_SAID (R, fwiffo_fine))
+ {
+ NPCPhrase (NOT_LIKELY);
+
+ R = killed_fwiffo;
+ DISABLE_PHRASE (fwiffo_fine);
+ }
+ else if (PLAYER_SAID (R, surrender))
+ {
+ NPCPhrase (NO_SURRENDER);
+
+ DISABLE_PHRASE (surrender);
+ }
+
+ if (GET_GAME_STATE (SPATHI_MANNER) == 0)
+ {
+ Response (we_come_in_peace, AllianceOffer);
+ }
+ else if (PHRASE_ENABLED (we_come_in_peace))
+ {
+ Response (we_come_in_peace, SpathiCouncil);
+ }
+ else
+ {
+ Response (misunderstanding, AllianceOffer);
+ }
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI)
+ && GET_GAME_STATE (FOUND_PLUTO_SPATHI) < 3)
+ {
+ if (PHRASE_ENABLED (spathi_on_pluto))
+ Response (spathi_on_pluto, SpathiCouncil);
+ else if (HaveEscortShip (SPATHI_SHIP))
+ {
+ if (PHRASE_ENABLED (hostage))
+ Response (hostage, SpathiCouncil);
+ }
+ else if (PHRASE_ENABLED (killed_fwiffo))
+ {
+ Response (killed_fwiffo, SpathiCouncil);
+ if (PHRASE_ENABLED (fwiffo_fine))
+ Response (fwiffo_fine, SpathiCouncil);
+ }
+ }
+ if (PHRASE_ENABLED (surrender))
+ Response (surrender, SpathiCouncil);
+ else
+ {
+ Response (surrender_or_die, ExitConversation);
+ }
+ Response (bye_no_ally_offer, ExitConversation);
+}
+
+static void
+SpathiPassword (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SPATHI_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ default:
+ NPCPhrase (WHAT_IS_PASSWORD);
+ NumVisits = 1;
+ break;
+ case 1:
+ NPCPhrase (WHAT_IS_PASSWORD_AGAIN);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SPATHI_HOME_VISITS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, what_do_i_get))
+ {
+ NPCPhrase (YOU_GET_TO_LIVE);
+
+ DISABLE_PHRASE (what_do_i_get);
+ }
+
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI)
+ || GET_GAME_STATE (KNOW_SPATHI_PASSWORD))
+ {
+ Response (good_password, SpathiCouncil);
+ if (PHRASE_ENABLED (what_do_i_get))
+ {
+ Response (what_do_i_get, SpathiPassword);
+ }
+ }
+ else
+ {
+ construct_response (shared_phrase_buf,
+ we_are_vindicator0,
+ GLOBAL_SIS (CommanderName),
+ we_are_vindicator1,
+ GLOBAL_SIS (ShipName),
+ we_are_vindicator2,
+ (UNICODE*)NULL);
+ DoResponsePhrase (we_are_vindicator0, ExitConversation, shared_phrase_buf);
+ Response (gort_merenga, ExitConversation);
+ Response (guph_florp, ExitConversation);
+ Response (wagngl_fthagn, ExitConversation);
+ Response (pleeese, ExitConversation);
+ Response (screw_password, ExitConversation);
+ }
+}
+
+static void
+Intro (void)
+{
+ BYTE Manner;
+
+ Manner = GET_GAME_STATE (SPATHI_MANNER);
+ if (Manner == 2)
+ {
+ NPCPhrase (HATE_YOU_FOREVER);
+ setSegue (Segue_hostile);
+ }
+ else if (Manner == 1
+ && GET_GAME_STATE (KNOW_SPATHI_PASSWORD)
+ && (GET_GAME_STATE (FOUND_PLUTO_SPATHI)
+ || GET_GAME_STATE (SPATHI_HOME_VISITS) != 7))
+ {
+ SpathiAngry ((RESPONSE_REF)0);
+ }
+ else if (CheckAlliance (SPATHI_SHIP) == GOOD_GUY)
+ {
+ CommData.AlienColorMap =
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1);
+ SpathiAllies ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (SPATHI_PARTY))
+ {
+ CommData.AlienColorMap =
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1);
+ SpathiParty ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (SPATHI_QUEST))
+ {
+ if (GET_GAME_STATE (LIED_ABOUT_CREATURES) < 2)
+ {
+ CommData.AlienColorMap =
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1);
+ SpathiQuest ((RESPONSE_REF)0);
+ }
+ else
+ {
+ NPCPhrase (YOU_LIED_2);
+
+ SET_GAME_STATE (SPATHI_MANNER, 2);
+ setSegue (Segue_hostile);
+ }
+ }
+ else if (GET_GAME_STATE (KNOW_SPATHI_QUEST))
+ {
+ CommData.AlienColorMap =
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1);
+ LearnQuest ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (KNOW_SPATHI_PASSWORD)
+ && (GET_GAME_STATE (FOUND_PLUTO_SPATHI)
+ || GET_GAME_STATE (SPATHI_HOME_VISITS) != 7))
+ {
+ CommData.AlienColorMap =
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1);
+ SpathiCouncil ((RESPONSE_REF)0);
+ }
+ else
+ {
+ SpathiPassword ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_spahome (void)
+{
+ return (0);
+}
+
+static void
+post_spahome_enc (void)
+{
+ BYTE Manner;
+
+ if (getSegue () == Segue_hostile
+ && (Manner = GET_GAME_STATE (SPATHI_MANNER)) != 2)
+ {
+ SET_GAME_STATE (SPATHI_MANNER, 1);
+ if (Manner != 1)
+ {
+ SET_GAME_STATE (SPATHI_VISITS, 0);
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 0);
+ }
+ }
+}
+
+LOCDATA*
+init_spahome_comm ()
+{
+ LOCDATA *retval;
+
+ spahome_desc.init_encounter_func = Intro;
+ spahome_desc.post_encounter_func = post_spahome_enc;
+ spahome_desc.uninit_encounter_func = uninit_spahome;
+
+ spahome_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ spahome_desc.AlienTextBaseline.y = 0;
+ spahome_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ // use alternate "Safe Ones" track if available
+ spahome_desc.AlienAltSongRes = SPAHOME_MUSIC;
+ spahome_desc.AlienSongFlags |= LDASF_USE_ALTERNATE;
+
+ if (GET_GAME_STATE (SPATHI_MANNER) == 3)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+
+ retval = &spahome_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/spahome/strings.h b/src/uqm/comm/spahome/strings.h
new file mode 100644
index 0000000..6df9560
--- /dev/null
+++ b/src/uqm/comm/spahome/strings.h
@@ -0,0 +1,174 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SPAHOME_STRINGS_H
+#define SPAHOME_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ killed_fwiffo,
+ POOR_FWIFFO,
+ fwiffo_fine,
+ NOT_LIKELY,
+ we_attack_again,
+ WE_FIGHT_AGAIN,
+ bye_no_ally_offer,
+ GOODBYE_NO_ALLY_OFFER,
+ bye_angry_spathi,
+ GOODBYE_ANGRY_SPATHI,
+ why_dont_you_do_it,
+ WE_WONT_BECAUSE,
+ MEAN_GUYS_RETURN,
+ we_apologize,
+ DONT_BELIEVE,
+ HELLO_AGAIN,
+ HATE_YOU_FOREVER,
+ WHAT_IS_PASSWORD,
+ WHAT_IS_PASSWORD_AGAIN,
+ we_are_vindicator0,
+ we_are_vindicator1,
+ we_are_vindicator2,
+ gort_merenga,
+ guph_florp,
+ pleeese,
+ wagngl_fthagn,
+ screw_password,
+ good_password,
+ WRONG_PASSWORD,
+ NO_PASSWORD,
+ what_do_i_get,
+ YOU_GET_TO_LIVE,
+ YES_GOOD_PASSWORD,
+ spathi_on_pluto,
+ WHERE_SPATHI,
+ hostage,
+ GUN_TO_HEAD,
+ we_come_in_peace,
+ OF_COURSE,
+ KILLED_SPATHI,
+ misunderstanding,
+ JUST_MISUNDERSTANDING,
+// JUST_MISUNDERSTANDING0,
+// JUST_MISUNDERSTANDING1,
+ give_us_resources,
+ NO_RESOURCES,
+ resources_please,
+ SORRY_NO_RESOURCES,
+ bye_ally,
+ GOODBYE_ALLY,
+ what_about_hierarchy,
+ what_about_history,
+ what_about_alliance,
+ what_about_other,
+ what_about_precursors,
+ enough_info,
+ OK_ENOUGH_INFO,
+ ABOUT_HIERARCHY,
+ ABOUT_HISTORY,
+ ABOUT_ALLIANCE,
+ ABOUT_OTHER,
+ ABOUT_PRECURSORS,
+ little_mistake,
+ BIG_MISTAKE,
+ bye_before_party,
+ QUEST_AGAIN,
+ GOODBYE_BEFORE_PARTY,
+ GOOD_START,
+ something_fishy,
+ NOTHING_FISHY,
+ surrender,
+ NO_SURRENDER,
+ surrender_or_die,
+ DEFEND_OURSELVES,
+ hand_in_friendship,
+ TOO_AFRAID,
+ stronger,
+ YOURE_NOT,
+ yes_we_are,
+ NO_YOURE_NOT,
+ how_prove,
+ BETTER_IDEA,
+ share_info,
+ NO_INFO,
+ WE_UNDERSTAND,
+ prove_strength,
+ YOUR_BEHAVIOR,
+ what_test,
+ BEFORE_ACCEPT,
+ WIPE_EVIL,
+ think_more,
+ COWARD,
+ tell_evil,
+ i_accept,
+ AWAIT_RETURN,
+ talk_test,
+ already_got_them,
+ EARLY_BIRD_CHECK,
+ NOT_SURPRISED,
+ TEST_AGAIN,
+ too_dangerous,
+ WE_AGREE,
+ HOW_GO_EFFORTS,
+ killed_them_all_1,
+ killed_them_all_2,
+ WILL_CHECK_1,
+ WILL_CHECK_2,
+ zapped_a_few,
+ RETURN_COMPLETE,
+ MUST_DESTROY_ALL,
+ no_landing,
+ saw_creatures,
+ YOU_FORTUNATE,
+ YOU_LIED_1,
+ YOU_LIED_2,
+ bye_from_party_1,
+ bye_from_party_2,
+ bye_from_party_3,
+ GOODBYE_FROM_PARTY,
+ MUST_PARTY_1,
+ MUST_PARTY_2,
+ MUST_PARTY_3,
+ deals_a_deal,
+ WAIT_A_WHILE,
+ how_long,
+ TEN_YEARS,
+ reneging,
+ ADULT_VIEW,
+ return_beasts,
+ WHAT_RELATIONSHIP,
+ minds_and_might,
+ HUH,
+ fellowship,
+ WHAT,
+ do_as_we_say,
+ DEPART_FOR_EARTH,
+ HELLO_ALLIES_1,
+ HELLO_ALLIES_2,
+ HELLO_ALLIES_3,
+ whats_up,
+ GENERAL_INFO_1,
+ GENERAL_INFO_2,
+ GENERAL_INFO_3,
+ GENERAL_INFO_4,
+ GENERAL_INFO_5,
+ like_some_info,
+ WHAT_ABOUT
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/spathi/Makeinfo b/src/uqm/comm/spathi/Makeinfo
new file mode 100644
index 0000000..09e8874
--- /dev/null
+++ b/src/uqm/comm/spathi/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="spathic.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/spathi/resinst.h b/src/uqm/comm/spathi/resinst.h
new file mode 100644
index 0000000..f83962b
--- /dev/null
+++ b/src/uqm/comm/spathi/resinst.h
@@ -0,0 +1,14 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define FWIFFO_MUSIC "comm.fwiffo.music"
+#define SPAHOME_MUSIC "comm.safeones.music"
+#define SPATHI_COLOR_MAP "comm.spathi.colortable"
+#define SPATHI_CONVERSATION_PHRASES "comm.spathi.dialogue"
+#define SPATHI_FONT "comm.spathi.font"
+#define SPATHI_HOME_COLOR_MAP "comm.safeones.colortable"
+#define SPATHI_HOME_CONVERSATION_PHRASES "comm.safeones.dialogue"
+#define SPATHI_HOME_PMAP_ANIM "comm.safeones.graphics"
+#define SPATHI_MUSIC "comm.spathi.music"
+#define SPATHI_PMAP_ANIM "comm.spathi.graphics"
diff --git a/src/uqm/comm/spathi/spathic.c b/src/uqm/comm/spathi/spathic.c
new file mode 100644
index 0000000..29288dc
--- /dev/null
+++ b/src/uqm/comm/spathi/spathic.c
@@ -0,0 +1,834 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+
+
+static LOCDATA spathi_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ SPATHI_PMAP_ANIM, /* AlienFrame */
+ SPATHI_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ SPATHI_COLOR_MAP, /* AlienColorMap */
+ SPATHI_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ SPATHI_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 8, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 1, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ 0, ONE_SECOND, /* RestartRate */
+ (1 << 1), /* BlockMask */
+ },
+ {
+ 7, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND / 20, 0, /* RestartRate */
+ (1 << 0), /* BlockMask */
+ },
+ {
+ 16, /* StartIndex */
+ 4, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* RestartRate */
+ (1 << 4), /* BlockMask */
+ },
+ {
+ 20, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ (1 << 5)
+ },
+ {
+ 24, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 2), /* BlockMask */
+ },
+
+
+ {
+ 34, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 3), /* BlockMask */
+ },
+ {
+ 38, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 41, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+#ifdef NEVER
+ { /* AlienTalkDesc */
+ 29, /* StartIndex */
+ 5, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+#else
+ { /* AlienTalkDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+#endif /* NEVER */
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF Response)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (Response, bye_ally_space))
+ NPCPhrase (GOODBYE_ALLY_SPACE);
+ else if (PLAYER_SAID (Response, bye_friendly_space))
+ NPCPhrase (GOODBYE_FRIENDLY_SPACE);
+ else if (PLAYER_SAID (Response, part_in_peace))
+ NPCPhrase (KEEP_IT_SECRET);
+ else if (PLAYER_SAID (Response, we_sorry_space))
+ NPCPhrase (APOLOGIZE_AT_HOMEWORLD);
+ else if (PLAYER_SAID (Response, give_info_space))
+ NPCPhrase (HERES_SOME_INFO);
+ else if (PLAYER_SAID (Response, bye_angry_space))
+ NPCPhrase (GOODBYE_ANGRY_SPACE);
+ else if (PLAYER_SAID (Response, attack_you_now))
+ {
+ NPCPhrase (YIPES);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (Response, we_fight_again_space))
+ {
+ NPCPhrase (OK_FIGHT_AGAIN_SPACE);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (Response, die_slugboy)
+ || PLAYER_SAID (Response, we_fight_1)
+ || PLAYER_SAID (Response, we_fight_2)
+ || PLAYER_SAID (Response, pay_for_crimes)
+ || PLAYER_SAID (Response, tell_me_coordinates)
+ || PLAYER_SAID (Response, changed_mind))
+ {
+ if (PLAYER_SAID (Response, tell_me_coordinates))
+ NPCPhrase (FAKE_COORDINATES);
+ NPCPhrase (OK_WE_FIGHT_AT_PLUTO);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (Response, join_us))
+ {
+ if (EscortFeasibilityStudy (SPATHI_SHIP) == 0)
+ NPCPhrase (TOO_SCARY);
+ else
+ {
+ NPCPhrase (WILL_JOIN);
+
+ AlienTalkSegue ((COUNT)~0);
+ AddEscortShips (SPATHI_SHIP, 1);
+ /* Make the Eluder escort captained by Fwiffo alone */
+ SetEscortCrewComplement (SPATHI_SHIP, 1,
+ NAME_OFFSET + NUM_CAPTAINS_NAMES);
+ }
+ }
+}
+
+static BYTE join_us_refusals;
+
+static void
+SpathiOnPluto (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, hi_there))
+ NPCPhrase (ARE_YOU_SURE);
+ else if (PLAYER_SAID (R, dont_kill))
+ NPCPhrase (OK_WONT);
+ else if (PLAYER_SAID (R, youre_forgiven))
+ NPCPhrase (THANKS_FOR_FORGIVENESS);
+ else if (PLAYER_SAID (R, you_wont_die_yet))
+ NPCPhrase (ETERNAL_GRATITUDE);
+ else if (PLAYER_SAID (R, you_may_live))
+ NPCPhrase (GEE_THANKS);
+ else if (PLAYER_SAID (R, youve_got_me_all_wrong))
+ NPCPhrase (SORRY_NO_COORDS);
+ else if (PLAYER_SAID (R, what_doing_on_pluto_1))
+ {
+ NPCPhrase (ABOUT_20_YEARS_AGO);
+
+ DISABLE_PHRASE (what_doing_on_pluto_1);
+ }
+ else if (PLAYER_SAID (R, what_doing_on_pluto_2))
+ {
+ NPCPhrase (WHEN_URQUAN_ARRIVED);
+
+ DISABLE_PHRASE (what_doing_on_pluto_2);
+ }
+ else if (PLAYER_SAID (R, what_doing_on_pluto_3))
+ {
+ NPCPhrase (STATIONED_ON_EARTH_MOON);
+
+ DISABLE_PHRASE (what_doing_on_pluto_3);
+ }
+ else if (PLAYER_SAID (R, what_about_ilwrath))
+ {
+ NPCPhrase (ABOUT_ILWRATH);
+
+ DISABLE_PHRASE (what_about_ilwrath);
+ }
+ else if (PLAYER_SAID (R, when_ilwrath))
+ {
+ NPCPhrase (THEN_ILWRATH);
+
+ DISABLE_PHRASE (when_ilwrath);
+ }
+ else if (PLAYER_SAID (R, what_about_moonbase))
+ {
+ NPCPhrase (SET_UP_BASE);
+
+ DISABLE_PHRASE (what_about_moonbase);
+ }
+ else if (PLAYER_SAID (R, what_about_other_spathi))
+ {
+ NPCPhrase (SPATHI_ARE);
+
+ DISABLE_PHRASE (what_about_other_spathi);
+ }
+ else if (PLAYER_SAID (R, what_about_other_spathi))
+ {
+ NPCPhrase (THEN_ILWRATH);
+
+ DISABLE_PHRASE (what_about_other_spathi);
+ }
+ else if (PLAYER_SAID (R, how_many_crew))
+ {
+ NPCPhrase (THOUSANDS);
+
+ DISABLE_PHRASE (how_many_crew);
+ }
+ else if (PLAYER_SAID (R, really_thousands))
+ {
+ NPCPhrase (JUST_ME);
+
+ DISABLE_PHRASE (really_thousands);
+ }
+ else if (PLAYER_SAID (R, full_of_monsters))
+ {
+ NPCPhrase (HOW_TRUE);
+
+ DISABLE_PHRASE (full_of_monsters);
+ }
+ else if (PLAYER_SAID (R, what_enemy))
+ {
+ NPCPhrase (ENEMY_IS);
+
+ DISABLE_PHRASE (what_enemy);
+ }
+ else if (PLAYER_SAID (R, why_you_here))
+ {
+ NPCPhrase (DREW_SHORT_STRAW);
+
+ DISABLE_PHRASE (why_you_here);
+ }
+ else if (PLAYER_SAID (R, where_are_urquan))
+ {
+ NPCPhrase (URQUAN_LEFT);
+
+ DISABLE_PHRASE (where_are_urquan);
+ }
+ else if (PLAYER_SAID (R, what_about_other_races))
+ {
+ NPCPhrase (ABOUT_OTHER_RACES);
+
+ DISABLE_PHRASE (what_about_other_races);
+ }
+ else if (PLAYER_SAID (R, what_blaze_of_glory))
+ {
+ NPCPhrase (BLAZE_IS);
+
+ DISABLE_PHRASE (what_blaze_of_glory);
+ }
+ else if (PLAYER_SAID (R, what_about_yourself))
+ {
+ NPCPhrase (ABOUT_MYSELF);
+
+ DISABLE_PHRASE (what_about_yourself);
+ }
+ else if (PLAYER_SAID (R, join_us))
+ {
+ if (join_us_refusals == 0)
+ {
+ NPCPhrase (WONT_JOIN_1);
+ ++join_us_refusals;
+ }
+ else if (join_us_refusals == 1)
+ {
+ NPCPhrase (WONT_JOIN_2);
+ ++join_us_refusals;
+ }
+ else
+ NPCPhrase (WONT_JOIN_3);
+ }
+
+ if (PHRASE_ENABLED (what_doing_on_pluto_1))
+ Response (what_doing_on_pluto_1, SpathiOnPluto);
+ else if (PHRASE_ENABLED (what_doing_on_pluto_2))
+ Response (what_doing_on_pluto_2, SpathiOnPluto);
+ else if (PHRASE_ENABLED (what_doing_on_pluto_3))
+ Response (what_doing_on_pluto_3, SpathiOnPluto);
+ else
+ {
+ if (PHRASE_ENABLED (what_about_ilwrath))
+ Response (what_about_ilwrath, SpathiOnPluto);
+ else if (PHRASE_ENABLED (when_ilwrath))
+ Response (when_ilwrath, SpathiOnPluto);
+
+ if (PHRASE_ENABLED (what_about_moonbase))
+ Response (what_about_moonbase, SpathiOnPluto);
+ else if (PHRASE_ENABLED (what_about_other_spathi))
+ Response (what_about_other_spathi, SpathiOnPluto);
+ else
+ {
+ if (PHRASE_ENABLED (how_many_crew))
+ Response (how_many_crew, SpathiOnPluto);
+ else if (PHRASE_ENABLED (really_thousands))
+ Response (really_thousands, SpathiOnPluto);
+ else if (PHRASE_ENABLED (full_of_monsters))
+ Response (full_of_monsters, SpathiOnPluto);
+
+ if (PHRASE_ENABLED (what_enemy))
+ Response (what_enemy, SpathiOnPluto);
+ else if (PHRASE_ENABLED (why_you_here))
+ Response (why_you_here, SpathiOnPluto);
+ }
+ }
+ if (PHRASE_ENABLED (where_are_urquan))
+ Response (where_are_urquan, SpathiOnPluto);
+ else if (PHRASE_ENABLED (what_about_other_races))
+ Response (what_about_other_races, SpathiOnPluto);
+ else if (PHRASE_ENABLED (what_blaze_of_glory))
+ Response (what_blaze_of_glory, SpathiOnPluto);
+ else if (PHRASE_ENABLED (what_about_yourself))
+ Response (what_about_yourself, SpathiOnPluto);
+
+ if (!PHRASE_ENABLED (full_of_monsters))
+ Response (join_us, ExitConversation);
+ else
+ Response (join_us, SpathiOnPluto);
+ Response (changed_mind, ExitConversation);
+}
+
+static void
+SpathiMustGrovel (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, identify))
+ {
+ NPCPhrase (I_FWIFFO);
+
+ Response (do_cultural, SpathiMustGrovel);
+ Response (youre_forgiven, SpathiOnPluto);
+ Response (die_slugboy, ExitConversation);
+ }
+ else if (PLAYER_SAID (R, do_cultural))
+ {
+ NPCPhrase (WEZZY_WEZZAH);
+
+ Response (begin_ritual, SpathiMustGrovel);
+ Response (you_wont_die_yet, SpathiOnPluto);
+ Response (we_fight_2, ExitConversation);
+ }
+ else if (PLAYER_SAID (R, begin_ritual))
+ {
+ NPCPhrase (MUST_DO_RITUAL_AT_HOME);
+
+ Response (you_may_live, SpathiOnPluto);
+ Response (pay_for_crimes, ExitConversation);
+ Response (what_are_coordinates, SpathiMustGrovel);
+ }
+ else /* if (R == what_are_coordinates) */
+ {
+ NPCPhrase (COORDINATES_ARE);
+
+ Response (youve_got_me_all_wrong, SpathiOnPluto);
+ Response (tell_me_coordinates, ExitConversation);
+ }
+}
+
+static void
+SpathiAllies (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SPATHI_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_ALLIED_HELLO_SPACE);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_ALLIED_HELLO_SPACE);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SPATHI_VISITS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, whats_up_space_2))
+ {
+ NPCPhrase (GENERAL_INFO_SPACE_2);
+
+ DISABLE_PHRASE (whats_up_space_2);
+ }
+ else if (PLAYER_SAID (R, give_us_info_from_space))
+ {
+ NPCPhrase (GET_INFO_FROM_SPATHIWA);
+
+ DISABLE_PHRASE (give_us_info_from_space);
+ }
+ else if (PLAYER_SAID (R, give_us_resources_space))
+ {
+ NPCPhrase (GET_RESOURCES_FROM_SPATHIWA);
+
+ DISABLE_PHRASE (give_us_resources_space);
+ }
+ else if (PLAYER_SAID (R, what_do_for_fun))
+ {
+ NPCPhrase (DO_THIS_FOR_FUN);
+
+ DISABLE_PHRASE (what_do_for_fun);
+ }
+
+ if (PHRASE_ENABLED (whats_up_space_2))
+ Response (whats_up_space_2, SpathiAllies);
+ if (PHRASE_ENABLED (give_us_info_from_space))
+ Response (give_us_info_from_space, SpathiAllies);
+ if (PHRASE_ENABLED (give_us_resources_space))
+ Response (give_us_resources_space, SpathiAllies);
+ if (PHRASE_ENABLED (what_do_for_fun))
+ Response (what_do_for_fun, SpathiAllies);
+ Response (bye_ally_space, ExitConversation);
+}
+
+static void
+SpathiFriendly (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SPATHI_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_FRIENDLY_HELLO_SPACE);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_FRIENDLY_HELLO_SPACE);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SPATHI_VISITS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, since_friendly_give_stuff))
+ {
+ NPCPhrase (GIVE_ADVICE);
+
+ DISABLE_PHRASE (since_friendly_give_stuff);
+ }
+ else if (PLAYER_SAID (R, whats_up_space_1))
+ {
+ NPCPhrase (GENERAL_INFO_SPACE_1);
+
+ DISABLE_PHRASE (whats_up_space_1);
+ }
+
+ if (PHRASE_ENABLED (whats_up_space_1))
+ Response (whats_up_space_1, SpathiFriendly);
+ if (PHRASE_ENABLED (since_friendly_give_stuff))
+ Response (since_friendly_give_stuff, SpathiFriendly);
+ Response (bye_friendly_space, ExitConversation);
+}
+
+static void SpathiNeutral (RESPONSE_REF R);
+
+static void
+SpathiBefriend (RESPONSE_REF R)
+{
+ BYTE InfoLeft, LastStack;
+ RESPONSE_REF pStr[2];
+
+ InfoLeft = FALSE;
+ LastStack = 0;
+ pStr[0] = pStr[1] = 0;
+ if (PLAYER_SAID (R, come_in_peace))
+ NPCPhrase (AGAINST_NATURE);
+ else if (PLAYER_SAID (R, looking_for_a_few_good_squids))
+ {
+ NPCPhrase (URQUAN_SLAVES);
+
+ DISABLE_PHRASE (looking_for_a_few_good_squids);
+ }
+ else if (PLAYER_SAID (R, why_slaves))
+ {
+ NPCPhrase (UMGAH_TRICK);
+
+ DISABLE_PHRASE (why_slaves);
+ }
+ else if (PLAYER_SAID (R, tell_us_about_you))
+ {
+ NPCPhrase (ABOUT_US);
+
+ DISABLE_PHRASE (tell_us_about_you);
+ LastStack = 1;
+ }
+ else if (PLAYER_SAID (R, what_you_really_want))
+ {
+ NPCPhrase (WANT_THIS);
+
+ DISABLE_PHRASE (what_you_really_want);
+ }
+ else if (PLAYER_SAID (R, how_about_alliance))
+ {
+ NPCPhrase (SURE);
+
+ DISABLE_PHRASE (how_about_alliance);
+ }
+
+ if (PHRASE_ENABLED (looking_for_a_few_good_squids))
+ pStr[0] = looking_for_a_few_good_squids;
+ else if (PHRASE_ENABLED (why_slaves))
+ pStr[0] = why_slaves;
+ if (PHRASE_ENABLED (tell_us_about_you))
+ pStr[1] = tell_us_about_you;
+ else if (PHRASE_ENABLED (what_you_really_want))
+ pStr[1] = what_you_really_want;
+ if (pStr[LastStack])
+ {
+ InfoLeft = TRUE;
+ Response (pStr[LastStack], SpathiBefriend);
+ }
+ LastStack ^= 1;
+ if (pStr[LastStack])
+ {
+ InfoLeft = TRUE;
+ Response (pStr[LastStack], SpathiBefriend);
+ }
+ if (PHRASE_ENABLED (how_about_alliance))
+ {
+ InfoLeft = TRUE;
+ Response (how_about_alliance, SpathiBefriend);
+ }
+
+ if (!InfoLeft)
+ {
+ SET_GAME_STATE (SPATHI_STACK1, 1);
+ SpathiNeutral (R);
+ }
+}
+
+static void
+SpathiAntagonize (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, prepare_to_die))
+ {
+ NPCPhrase (ALWAYS_PREPARED);
+
+ SET_GAME_STATE (SPATHI_STACK2, 1);
+ }
+ else if (PLAYER_SAID (R, heard_youre_cowards))
+ {
+ NPCPhrase (DARN_TOOTIN);
+
+ DISABLE_PHRASE (heard_youre_cowards);
+ }
+ else if (PLAYER_SAID (R, wanna_fight))
+ {
+ NPCPhrase (YES_WE_DO);
+
+ DISABLE_PHRASE (wanna_fight);
+ }
+ else if (PLAYER_SAID (R, so_lets_fight))
+ {
+ NPCPhrase (OK_LETS_FIGHT);
+
+ DISABLE_PHRASE (so_lets_fight);
+ }
+ else if (PLAYER_SAID (R, so_lets_fight_already))
+ {
+ NPCPhrase (DONT_REALLY_WANT_TO_FIGHT);
+
+ DISABLE_PHRASE (so_lets_fight_already);
+ }
+
+ if (PHRASE_ENABLED (wanna_fight))
+ Response (wanna_fight, SpathiAntagonize);
+ else if (PHRASE_ENABLED (so_lets_fight))
+ Response (so_lets_fight, SpathiAntagonize);
+ else if (PHRASE_ENABLED (so_lets_fight_already))
+ Response (so_lets_fight_already, SpathiAntagonize);
+ if (PHRASE_ENABLED (heard_youre_cowards))
+ Response (heard_youre_cowards, SpathiAntagonize);
+ Response (attack_you_now, ExitConversation);
+}
+
+static void
+SpathiNeutral (RESPONSE_REF R)
+{
+ if (R == 0)
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SPATHI_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_NEUTRAL_HELLO_SPACE);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_NEUTRAL_HELLO_SPACE);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SPATHI_VISITS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, look_weird))
+ {
+ NPCPhrase (YOU_LOOK_WEIRD);
+
+ SET_GAME_STATE (SPATHI_STACK0, 1);
+ }
+ else if (PLAYER_SAID (R, no_look_really_weird))
+ {
+ NPCPhrase (NO_YOU_LOOK_REALLY_WEIRD);
+
+ SET_GAME_STATE (SPATHI_STACK0, 2);
+ }
+
+ switch (GET_GAME_STATE (SPATHI_STACK0))
+ {
+ case 0:
+ Response (look_weird, SpathiNeutral);
+ break;
+ case 1:
+ Response (no_look_really_weird, SpathiNeutral);
+ break;
+ }
+ if (GET_GAME_STATE (SPATHI_STACK1) == 0)
+ {
+ Response (come_in_peace, SpathiBefriend);
+ }
+ if (GET_GAME_STATE (SPATHI_STACK2) == 0)
+ {
+ Response (prepare_to_die, SpathiAntagonize);
+ }
+ else
+ {
+ Response (attack_you_now, ExitConversation);
+ }
+ Response (part_in_peace, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE Manner;
+
+ Manner = GET_GAME_STATE (SPATHI_MANNER);
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI) == 1)
+ {
+ join_us_refusals = 0;
+
+ NPCPhrase (SORRY_ABOUT_THAT);
+
+ /* if already know password from Melnorme,
+ * but haven't visited Spathiwa yet . . .
+ */
+ if (GET_GAME_STATE (SPATHI_HOME_VISITS) == 7)
+ {
+ SET_GAME_STATE (KNOW_SPATHI_PASSWORD, 0);
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 0);
+ }
+
+ Response (identify, SpathiMustGrovel);
+ Response (hi_there, SpathiOnPluto);
+ Response (dont_kill, SpathiOnPluto);
+ Response (we_fight_1, ExitConversation);
+ }
+ else if (Manner == 2)
+ {
+ NPCPhrase (HATE_YOU_FOREVER_SPACE);
+ setSegue (Segue_hostile);
+ }
+ else if (Manner == 1)
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SPATHI_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_ANGRY_HELLO_SPACE);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_ANGRY_HELLO_SPACE);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SPATHI_VISITS, NumVisits);
+
+ Response (give_info_space, ExitConversation);
+ Response (we_sorry_space,ExitConversation);
+ Response (we_fight_again_space, ExitConversation);
+ Response (bye_angry_space, ExitConversation);
+ }
+ else if (CheckAlliance (SPATHI_SHIP) == GOOD_GUY)
+ {
+ SpathiAllies ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (SPATHI_QUEST))
+ {
+ SpathiFriendly ((RESPONSE_REF)0);
+ }
+ else
+ {
+ SpathiNeutral ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_spathi (void)
+{
+ return (0);
+}
+
+static void
+post_spathi_enc (void)
+{
+ BYTE Manner;
+
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI) == 1)
+ {
+ SET_GAME_STATE (FOUND_PLUTO_SPATHI, 2);
+ }
+ else if (getSegue () == Segue_hostile
+ && (Manner = GET_GAME_STATE (SPATHI_MANNER)) != 2)
+ {
+ SET_GAME_STATE (SPATHI_MANNER, 1);
+ if (Manner != 1)
+ {
+ SET_GAME_STATE (SPATHI_VISITS, 0);
+ /* if don't know about Spathi via Melnorme . . . */
+ if (GET_GAME_STATE (SPATHI_HOME_VISITS) != 7)
+ {
+ SET_GAME_STATE (SPATHI_HOME_VISITS, 0);
+ }
+ }
+ }
+}
+
+LOCDATA*
+init_spathi_comm (void)
+{
+ LOCDATA *retval;
+
+ spathi_desc.init_encounter_func = Intro;
+ spathi_desc.post_encounter_func = post_spathi_enc;
+ spathi_desc.uninit_encounter_func = uninit_spathi;
+
+ spathi_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ spathi_desc.AlienTextBaseline.y = 0;
+ spathi_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI) == 1)
+ { // use alternate Fwiffo track if available
+ spathi_desc.AlienAltSongRes = FWIFFO_MUSIC;
+ spathi_desc.AlienSongFlags |= LDASF_USE_ALTERNATE;
+ }
+ else
+ { // regular track -- let's make sure
+ spathi_desc.AlienSongFlags &= ~LDASF_USE_ALTERNATE;
+ }
+
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI) == 1
+ || GET_GAME_STATE (SPATHI_MANNER) == 3
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &spathi_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/spathi/strings.h b/src/uqm/comm/spathi/strings.h
new file mode 100644
index 0000000..a09ea87
--- /dev/null
+++ b/src/uqm/comm/spathi/strings.h
@@ -0,0 +1,160 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SPATHI_STRINGS_H
+#define SPATHI_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ SORRY_ABOUT_THAT,
+ identify,
+ I_FWIFFO,
+ hi_there,
+ ARE_YOU_SURE,
+ dont_kill,
+ we_fight_1,
+ we_fight_2,
+ OK_WONT,
+ do_cultural,
+ WEZZY_WEZZAH,
+ die_slugboy,
+ begin_ritual,
+ MUST_DO_RITUAL_AT_HOME,
+ you_wont_die_yet,
+ ETERNAL_GRATITUDE,
+ we_fight,
+ pay_for_crimes,
+ CLUTCH_MAVEN,
+ you_may_live,
+ HONEST_AND_FRIENDLY,
+ what_are_coordinates,
+ COORDINATES_ARE,
+ tell_me_coordinates,
+ FAKE_COORDINATES,
+ TOO_SCARY,
+ youve_got_me_all_wrong,
+ SORRY_NO_COORDS,
+ what_doing_on_pluto_1,
+ ABOUT_20_YEARS_AGO,
+ what_doing_on_pluto_2,
+ WHEN_URQUAN_ARRIVED,
+ where_are_urquan,
+ URQUAN_LEFT,
+ what_about_other_races,
+ ABOUT_OTHER_RACES,
+ what_doing_on_pluto_3,
+ what_about_yourself,
+ ABOUT_MYSELF,
+ STATIONED_ON_EARTH_MOON,
+ what_blaze_of_glory,
+ BLAZE_IS,
+ what_about_moonbase,
+ SET_UP_BASE,
+ what_about_ilwrath,
+ ABOUT_ILWRATH,
+ what_about_other_spathi,
+ really_thousands,
+ SPATHI_ARE,
+ what_enemy,
+ ENEMY_IS,
+ when_ilwrath,
+ THEN_ILWRATH,
+ why_you_here,
+ DREW_SHORT_STRAW,
+ how_many_crew,
+ JUST_ME,
+ THOUSANDS,
+ full_of_monsters,
+ HOW_TRUE,
+ join_us,
+ WILL_JOIN,
+ WONT_JOIN_1,
+ give_ship_or_die,
+ WONT_JOIN_2,
+ WONT_JOIN_3,
+ GEE_THANKS,
+ changed_mind,
+ youre_forgiven,
+ THANKS_FOR_FORGIVENESS,
+ HATE_YOU_FOREVER_SPACE,
+ INIT_ANGRY_HELLO_SPACE,
+ SUBSEQUENT_ANGRY_HELLO_SPACE,
+ INIT_NEUTRAL_HELLO_SPACE,
+ SUBSEQUENT_NEUTRAL_HELLO_SPACE,
+ INIT_FRIENDLY_HELLO_SPACE,
+ SUBSEQUENT_FRIENDLY_HELLO_SPACE,
+ INIT_ALLIED_HELLO_SPACE,
+ SUBSEQUENT_ALLIED_HELLO_SPACE,
+ give_info_space,
+ HERES_SOME_INFO,
+ we_sorry_space,
+ APOLOGIZE_AT_HOMEWORLD,
+ we_fight_again_space,
+ OK_FIGHT_AGAIN_SPACE,
+ bye_angry_space,
+ GOODBYE_ANGRY_SPACE,
+ look_weird,
+ YOU_LOOK_WEIRD,
+ no_look_really_weird,
+ NO_YOU_LOOK_REALLY_WEIRD,
+ come_in_peace,
+ AGAINST_NATURE,
+ prepare_to_die,
+ ALWAYS_PREPARED,
+ since_friendly_give_stuff,
+ GIVE_ADVICE,
+ whats_up_space_1,
+ GENERAL_INFO_SPACE_1,
+ bye_friendly_space,
+ GOODBYE_FRIENDLY_SPACE,
+ looking_for_a_few_good_squids,
+ URQUAN_SLAVES,
+ why_slaves,
+ UMGAH_TRICK,
+ tell_us_about_you,
+ ABOUT_US,
+ what_you_really_want,
+ WANT_THIS,
+ how_about_alliance,
+ SURE,
+ part_in_peace,
+ KEEP_IT_SECRET,
+ heard_youre_cowards,
+ DARN_TOOTIN,
+ wanna_fight,
+ YES_WE_DO,
+ so_lets_fight,
+ OK_LETS_FIGHT,
+ so_lets_fight_already,
+ DONT_REALLY_WANT_TO_FIGHT,
+ attack_you_now,
+ YIPES,
+ whats_up_space_2,
+ GENERAL_INFO_SPACE_2,
+ give_us_info_from_space,
+ GET_INFO_FROM_SPATHIWA,
+ give_us_resources_space,
+ GET_RESOURCES_FROM_SPATHIWA,
+ what_do_for_fun,
+ DO_THIS_FOR_FUN,
+ bye_ally_space,
+ GOODBYE_ALLY_SPACE,
+ OK_WE_FIGHT_AT_PLUTO,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/starbas/Makeinfo b/src/uqm/comm/starbas/Makeinfo
new file mode 100644
index 0000000..7f1eab8
--- /dev/null
+++ b/src/uqm/comm/starbas/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="starbas.c"
+uqm_HFILES="strings.h"
diff --git a/src/uqm/comm/starbas/starbas.c b/src/uqm/comm/starbas/starbas.c
new file mode 100644
index 0000000..5f25a2d
--- /dev/null
+++ b/src/uqm/comm/starbas/starbas.c
@@ -0,0 +1,1961 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "../comandr/resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/setup.h"
+#include "uqm/shipcont.h"
+#include "uqm/sis.h"
+ // for DeltaSISGauges()
+#include "libs/graphics/gfx_common.h"
+#include "libs/mathlib.h"
+#include "libs/inplib.h"
+
+
+static void TellMission (RESPONSE_REF R);
+static void SellMinerals (RESPONSE_REF R);
+
+
+static LOCDATA commander_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ COMMANDER_PMAP_ANIM, /* AlienFrame */
+ COMMANDER_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_MIDDLE, /* AlienTextValign */
+ COMMANDER_COLOR_MAP, /* AlienColorMap */
+ COMMANDER_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ STARBASE_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 10, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ { /* Blink */
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Running light */
+ 10, /* StartIndex */
+ 30, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ ONE_SECOND * 2, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Arc welder 0 */
+ 40, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Arc welder 1 */
+ 47, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Arc welder 2 */
+ 55, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Arc welder 3 */
+ 61, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Arc welder 4 */
+ 67, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Arc welder 5 */
+ 74, /* StartIndex */
+ 11, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Arc welder 6 */
+ 85, /* StartIndex */
+ 10, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 40, 0, /* FrameRate */
+ 0, ONE_SECOND * 8, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Flagship picture */
+ 95, /* StartIndex */
+ 1, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 4, /* StartIndex */
+ 6, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 7 / 60, ONE_SECOND / 12, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static DWORD CurBulletinMask;
+
+static void
+ByeBye (RESPONSE_REF R)
+{
+ (void) R; // ignored
+
+ CurBulletinMask |= GET_GAME_STATE_32 (STARBASE_BULLETS0);
+ SET_GAME_STATE_32 (STARBASE_BULLETS0, CurBulletinMask);
+
+ /* if (R == goodbye_starbase_commander) */
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) >= 2)
+ NPCPhrase (GOOD_LUCK_AGAIN);
+ else
+ {
+ RESPONSE_REF pStr0 = 0;
+ RESPONSE_REF pStr1 = 0;
+
+ switch ((BYTE)TFB_Random () & 7)
+ {
+ case 0:
+ pStr0 = NORMAL_GOODBYE_A0;
+ pStr1 = NORMAL_GOODBYE_A1;
+ break;
+ case 1:
+ pStr0 = NORMAL_GOODBYE_B0;
+ pStr1 = NORMAL_GOODBYE_B1;
+ break;
+ case 2:
+ pStr0 = NORMAL_GOODBYE_C0;
+ pStr1 = NORMAL_GOODBYE_C1;
+ break;
+ case 3:
+ pStr0 = NORMAL_GOODBYE_D0;
+ pStr1 = NORMAL_GOODBYE_D1;
+ break;
+ case 4:
+ pStr0 = NORMAL_GOODBYE_E0;
+ pStr1 = NORMAL_GOODBYE_E1;
+ break;
+ case 5:
+ pStr0 = NORMAL_GOODBYE_F0;
+ pStr1 = NORMAL_GOODBYE_F1;
+ break;
+ case 6:
+ pStr0 = NORMAL_GOODBYE_G0;
+ pStr1 = NORMAL_GOODBYE_G1;
+ break;
+ case 7:
+ pStr0 = NORMAL_GOODBYE_H0;
+ pStr1 = NORMAL_GOODBYE_H1;
+ break;
+ }
+
+ NPCPhrase (pStr0);
+ if (!usingSpeech)
+ {
+ NPCPhrase (SPACE);
+ NPCPhrase (GLOBAL_PLAYER_NAME);
+ }
+ NPCPhrase (pStr1);
+ }
+}
+
+static void NeedInfo (RESPONSE_REF R);
+static void TellHistory (RESPONSE_REF R);
+static void AlienRaces (RESPONSE_REF R);
+
+static BYTE stack0;
+static BYTE stack1;
+static BYTE stack2;
+static BYTE stack3;
+
+static void
+AllianceInfo (RESPONSE_REF R)
+{
+#define ALLIANCE_SHOFIXTI (1 << 0)
+#define ALLIANCE_YEHAT (1 << 1)
+#define ALLIANCE_ARILOU (1 << 2)
+#define ALLIANCE_CHENJESU (1 << 3)
+#define ALLIANCE_MMRNMHRM (1 << 4)
+#define ALLIANCE_SYREEN (1 << 5)
+ static BYTE AllianceMask = 0;
+
+ if (PLAYER_SAID (R, what_about_alliance))
+ {
+ NPCPhrase (WHICH_ALLIANCE);
+ AllianceMask = 0;
+ }
+ else if (PLAYER_SAID (R, shofixti))
+ {
+ NPCPhrase (ABOUT_SHOFIXTI);
+ AllianceMask |= ALLIANCE_SHOFIXTI;
+ }
+ else if (PLAYER_SAID (R, yehat))
+ {
+ NPCPhrase (ABOUT_YEHAT);
+ AllianceMask |= ALLIANCE_YEHAT;
+ }
+ else if (PLAYER_SAID (R, arilou))
+ {
+ NPCPhrase (ABOUT_ARILOU);
+ AllianceMask |= ALLIANCE_ARILOU;
+ }
+ else if (PLAYER_SAID (R, chenjesu))
+ {
+ NPCPhrase (ABOUT_CHENJESU);
+ AllianceMask |= ALLIANCE_CHENJESU;
+ }
+ else if (PLAYER_SAID (R, mmrnmhrm))
+ {
+ NPCPhrase (ABOUT_MMRNMHRM);
+ AllianceMask |= ALLIANCE_MMRNMHRM;
+ }
+ else if (PLAYER_SAID (R, syreen))
+ {
+ NPCPhrase (ABOUT_SYREEN);
+ AllianceMask |= ALLIANCE_SYREEN;
+ }
+
+ if (!(AllianceMask & ALLIANCE_SHOFIXTI))
+ Response (shofixti, AllianceInfo);
+ if (!(AllianceMask & ALLIANCE_YEHAT))
+ Response (yehat, AllianceInfo);
+ if (!(AllianceMask & ALLIANCE_ARILOU))
+ Response (arilou, AllianceInfo);
+ if (!(AllianceMask & ALLIANCE_CHENJESU))
+ Response (chenjesu, AllianceInfo);
+ if (!(AllianceMask & ALLIANCE_MMRNMHRM))
+ Response (mmrnmhrm, AllianceInfo);
+ if (!(AllianceMask & ALLIANCE_SYREEN))
+ Response (syreen, AllianceInfo);
+ Response (enough_alliance, AlienRaces);
+}
+
+static void
+HierarchyInfo (RESPONSE_REF R)
+{
+#define HIERARCHY_MYCON (1 << 0)
+#define HIERARCHY_SPATHI (1 << 1)
+#define HIERARCHY_UMGAH (1 << 2)
+#define HIERARCHY_ANDROSYNTH (1 << 3)
+#define HIERARCHY_ILWRATH (1 << 4)
+#define HIERARCHY_VUX (1 << 5)
+#define HIERARCHY_URQUAN (1 << 6)
+ static BYTE HierarchyMask = 0;
+
+ if (PLAYER_SAID (R, what_about_hierarchy))
+ {
+ NPCPhrase (WHICH_HIERARCHY);
+ HierarchyMask = 0;
+ }
+ else if (PLAYER_SAID (R, urquan))
+ {
+ NPCPhrase (ABOUT_URQUAN);
+ HierarchyMask |= HIERARCHY_URQUAN;
+ }
+ else if (PLAYER_SAID (R, mycon))
+ {
+ NPCPhrase (ABOUT_MYCON);
+ HierarchyMask |= HIERARCHY_MYCON;
+ }
+ else if (PLAYER_SAID (R, spathi))
+ {
+ NPCPhrase (ABOUT_SPATHI);
+ HierarchyMask |= HIERARCHY_SPATHI;
+ }
+ else if (PLAYER_SAID (R, umgah))
+ {
+ NPCPhrase (ABOUT_UMGAH);
+ HierarchyMask |= HIERARCHY_UMGAH;
+ }
+ else if (PLAYER_SAID (R, androsynth))
+ {
+ NPCPhrase (ABOUT_ANDROSYNTH);
+ HierarchyMask |= HIERARCHY_ANDROSYNTH;
+ }
+ else if (PLAYER_SAID (R, ilwrath))
+ {
+ NPCPhrase (ABOUT_ILWRATH);
+ HierarchyMask |= HIERARCHY_ILWRATH;
+ }
+ else if (PLAYER_SAID (R, vux))
+ {
+ NPCPhrase (ABOUT_VUX);
+ HierarchyMask |= HIERARCHY_VUX;
+ }
+
+ if (!(HierarchyMask & HIERARCHY_URQUAN))
+ Response (urquan, HierarchyInfo);
+ if (!(HierarchyMask & HIERARCHY_MYCON))
+ Response (mycon, HierarchyInfo);
+ if (!(HierarchyMask & HIERARCHY_SPATHI))
+ Response (spathi, HierarchyInfo);
+ if (!(HierarchyMask & HIERARCHY_UMGAH))
+ Response (umgah, HierarchyInfo);
+ if (!(HierarchyMask & HIERARCHY_ANDROSYNTH))
+ Response (androsynth, HierarchyInfo);
+ if (!(HierarchyMask & HIERARCHY_ILWRATH))
+ Response (ilwrath, HierarchyInfo);
+ if (!(HierarchyMask & HIERARCHY_VUX))
+ Response (vux, HierarchyInfo);
+ Response (enough_hierarchy, AlienRaces);
+}
+
+static void
+AlienRaces (RESPONSE_REF R)
+{
+#define RACES_ALLIANCE (1 << 0)
+#define RACES_HIERARCHY (1 << 1)
+#define RACES_OTHER (1 << 2)
+ static BYTE RacesMask = 0;
+
+ if (PLAYER_SAID (R, alien_races))
+ {
+ NPCPhrase (WHICH_ALIEN);
+ RacesMask = 0;
+ }
+ else if (PLAYER_SAID (R, enough_alliance))
+ {
+ NPCPhrase (OK_ENOUGH_ALLIANCE);
+ RacesMask |= RACES_ALLIANCE;
+ }
+ else if (PLAYER_SAID (R, enough_hierarchy))
+ {
+ NPCPhrase (OK_ENOUGH_HIERARCHY);
+ RacesMask |= RACES_HIERARCHY;
+ }
+ else if (PLAYER_SAID (R, what_about_other))
+ {
+ NPCPhrase (ABOUT_OTHER);
+ RacesMask |= RACES_OTHER;
+ }
+
+ if (!(RacesMask & RACES_ALLIANCE))
+ {
+ Response (what_about_alliance, AllianceInfo);
+ }
+ if (!(RacesMask & RACES_HIERARCHY))
+ {
+ Response (what_about_hierarchy, HierarchyInfo);
+ }
+ if (!(RacesMask & RACES_OTHER))
+ {
+ Response (what_about_other, AlienRaces);
+ }
+ Response (enough_aliens, TellHistory);
+}
+
+static void
+WarInfo (RESPONSE_REF R)
+{
+#define WAR_STARTED (1 << 0)
+#define WAR_WAS_LIKE (1 << 1)
+#define WAR_LOST (1 << 2)
+#define WAR_AFTERMATH (1 << 3)
+ static BYTE WarMask = 0;
+
+ if (PLAYER_SAID (R, the_war))
+ {
+ NPCPhrase (WHICH_WAR);
+ WarMask = 0;
+ }
+ else if (PLAYER_SAID (R, what_started_war))
+ {
+ NPCPhrase (URQUAN_STARTED_WAR);
+ WarMask |= WAR_STARTED;
+ }
+ else if (PLAYER_SAID (R, what_was_war_like))
+ {
+ NPCPhrase (WAR_WAS_LIKE_SO);
+ WarMask |= WAR_WAS_LIKE;
+ }
+ else if (PLAYER_SAID (R, why_lose_war))
+ {
+ NPCPhrase (LOST_WAR_BECAUSE);
+ WarMask |= WAR_LOST;
+ }
+ else if (PLAYER_SAID (R, what_after_war))
+ {
+ NPCPhrase (AFTER_WAR);
+ WarMask |= WAR_AFTERMATH;
+ }
+
+ if (!(WarMask & WAR_STARTED))
+ Response (what_started_war, WarInfo);
+ if (!(WarMask & WAR_WAS_LIKE))
+ Response (what_was_war_like, WarInfo);
+ if (!(WarMask & WAR_LOST))
+ Response (why_lose_war, WarInfo);
+ if (!(WarMask & WAR_AFTERMATH))
+ Response (what_after_war, WarInfo);
+ Response (enough_war, TellHistory);
+}
+
+static void
+AncientHistory (RESPONSE_REF R)
+{
+#define ANCIENT_PRECURSORS (1 << 0)
+#define ANCIENT_RACES (1 << 1)
+#define ANCIENT_EARTH (1 << 2)
+ static BYTE AncientMask = 0;
+
+ if (PLAYER_SAID (R, ancient_history))
+ {
+ NPCPhrase (WHICH_ANCIENT);
+ AncientMask = 0;
+ }
+ else if (PLAYER_SAID (R, precursors))
+ {
+ NPCPhrase (ABOUT_PRECURSORS);
+ AncientMask |= ANCIENT_PRECURSORS;
+ }
+ else if (PLAYER_SAID (R, old_races))
+ {
+ NPCPhrase (ABOUT_OLD_RACES);
+ AncientMask |= ANCIENT_RACES;
+ }
+ else if (PLAYER_SAID (R, aliens_on_earth))
+ {
+ NPCPhrase (ABOUT_ALIENS_ON_EARTH);
+ AncientMask |= ANCIENT_EARTH;
+ }
+
+ if (!(AncientMask & ANCIENT_PRECURSORS))
+ Response (precursors, AncientHistory);
+ if (!(AncientMask & ANCIENT_RACES))
+ Response (old_races, AncientHistory);
+ if (!(AncientMask & ANCIENT_EARTH))
+ Response (aliens_on_earth, AncientHistory);
+ Response (enough_ancient, TellHistory);
+}
+
+static void
+TellHistory (RESPONSE_REF R)
+{
+ RESPONSE_REF pstack[3];
+
+ if (PLAYER_SAID (R, history))
+ {
+ NPCPhrase (WHICH_HISTORY);
+ stack0 = 0;
+ stack1 = 0;
+ stack2 = 0;
+ }
+ else if (PLAYER_SAID (R, enough_aliens))
+ {
+ NPCPhrase (OK_ENOUGH_ALIENS);
+
+ stack0 = 1;
+ }
+ else if (PLAYER_SAID (R, enough_war))
+ {
+ NPCPhrase (OK_ENOUGH_WAR);
+
+ stack1 = 1;
+ }
+ else if (PLAYER_SAID (R, enough_ancient))
+ {
+ NPCPhrase (OK_ENOUGH_ANCIENT);
+
+ stack2 = 1;
+ }
+
+ switch (stack0)
+ {
+ case 0:
+ pstack[0] = alien_races;
+ break;
+ default:
+ pstack[0] = 0;
+ break;
+ }
+ switch (stack1)
+ {
+ case 0:
+ pstack[1] = the_war;
+ break;
+ default:
+ pstack[1] = 0;
+ break;
+ }
+ switch (stack2)
+ {
+ case 0:
+ pstack[2] = ancient_history;
+ break;
+ default:
+ pstack[2] = 0;
+ break;
+ }
+
+ if (pstack[0])
+ {
+ Response (pstack[0], AlienRaces);
+ }
+ if (pstack[1])
+ {
+ Response (pstack[1], WarInfo);
+ }
+ if (pstack[2])
+ {
+ Response (pstack[2], AncientHistory);
+ }
+ Response (enough_history, NeedInfo);
+}
+
+static void
+DefeatUrquan (RESPONSE_REF R)
+{
+#define HOW_FIND_URQUAN (1 << 0)
+#define HOW_FIGHT_URQUAN (1 << 1)
+#define HOW_ALLY_AGAINST_URQUAN (1 << 2)
+#define HOW_STRONG_AGAINST_URQUAN (1 << 3)
+ static BYTE DefeatMask = 0;
+
+ if (PLAYER_SAID (R, how_defeat))
+ {
+ NPCPhrase (DEFEAT_LIKE_SO);
+ DefeatMask = 0;
+ }
+ else if (PLAYER_SAID (R, how_find_urquan))
+ {
+ NPCPhrase (FIND_URQUAN);
+ DefeatMask |= HOW_FIND_URQUAN;
+ }
+ else if (PLAYER_SAID (R, how_fight_urquan))
+ {
+ NPCPhrase (FIGHT_URQUAN);
+ DefeatMask |= HOW_FIGHT_URQUAN;
+ }
+ else if (PLAYER_SAID (R, how_ally))
+ {
+ NPCPhrase (ALLY_LIKE_SO);
+ DefeatMask |= HOW_ALLY_AGAINST_URQUAN;
+ }
+ else if (PLAYER_SAID (R, how_get_strong))
+ {
+ NPCPhrase (STRONG_LIKE_SO);
+ DefeatMask |= HOW_STRONG_AGAINST_URQUAN;
+ }
+
+ if (!(DefeatMask & HOW_FIND_URQUAN))
+ Response (how_find_urquan, DefeatUrquan);
+ if (!(DefeatMask & HOW_FIGHT_URQUAN))
+ Response (how_fight_urquan, DefeatUrquan);
+ if (!(DefeatMask & HOW_ALLY_AGAINST_URQUAN))
+ Response (how_ally, DefeatUrquan);
+ if (!(DefeatMask & HOW_STRONG_AGAINST_URQUAN))
+ Response (how_get_strong, DefeatUrquan);
+ Response (enough_defeat, TellMission);
+}
+
+static void
+AnalyzeCondition (void)
+{
+ BYTE i;
+ BYTE num_thrusters = 0,
+ num_jets = 0,
+ num_guns = 0,
+ num_bays = 0,
+ num_batts = 0,
+ num_track = 0,
+ num_defense = 0;
+ BOOLEAN HasMinimum;
+
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (DriveSlots[i]) < EMPTY_SLOT)
+ ++num_thrusters;
+ }
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (JetSlots[i]) < EMPTY_SLOT)
+ ++num_jets;
+ }
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ BYTE which_piece;
+
+ switch (which_piece = GLOBAL_SIS (ModuleSlots[i]))
+ {
+ case STORAGE_BAY:
+ ++num_bays;
+ break;
+ case DYNAMO_UNIT:
+ case SHIVA_FURNACE:
+ num_batts += 1 + (which_piece - DYNAMO_UNIT);
+ break;
+ case GUN_WEAPON:
+ case BLASTER_WEAPON:
+ case CANNON_WEAPON:
+ num_guns += 1 + (which_piece - GUN_WEAPON);
+ break;
+ case TRACKING_SYSTEM:
+ ++num_track;
+ break;
+ case ANTIMISSILE_DEFENSE:
+ ++num_defense;
+ break;
+ }
+ }
+ if (num_track && num_guns)
+ num_guns += 2;
+
+ HasMinimum = (num_thrusters >= 7 && num_jets >= 5
+ && GLOBAL_SIS (CrewEnlisted) >= CREW_POD_CAPACITY
+ && GLOBAL_SIS (FuelOnBoard) >= FUEL_TANK_CAPACITY
+ && num_bays >= 1 && GLOBAL_SIS (NumLanders)
+ && num_batts >= 1 && num_guns >= 2);
+ NPCPhrase (LETS_SEE);
+ if (!HasMinimum && GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
+ {
+ NPCPhrase (IMPROVE_1);
+ if (num_thrusters < 7)
+ NPCPhrase (NEED_THRUSTERS_1);
+ if (num_jets < 5)
+ NPCPhrase (NEED_TURN_1);
+ if (num_guns < 2)
+ NPCPhrase (NEED_GUNS_1);
+ if (GLOBAL_SIS (CrewEnlisted) < CREW_POD_CAPACITY)
+ NPCPhrase (NEED_CREW_1);
+ if (GLOBAL_SIS (FuelOnBoard) < FUEL_TANK_CAPACITY)
+ NPCPhrase (NEED_FUEL_1);
+ if (num_bays < 1)
+ NPCPhrase (NEED_STORAGE_1);
+ if (GLOBAL_SIS (NumLanders) == 0)
+ NPCPhrase (NEED_LANDERS_2);
+ if (num_batts < 1)
+ NPCPhrase (NEED_DYNAMOS_1);
+
+ if (GLOBAL_SIS (ResUnits) >= 3000)
+ NPCPhrase (IMPROVE_FLAGSHIP_WITH_RU);
+ else
+ NPCPhrase (GO_GET_MINERALS);
+ }
+ else
+ {
+ BYTE num_aliens = 0;
+ COUNT FleetStrength;
+ BOOLEAN HasMaximum;
+
+ FleetStrength = CalculateEscortsWorth ();
+ for (i = 0; i < NUM_AVAILABLE_RACES; ++i)
+ {
+ if (i != HUMAN_SHIP && CheckAlliance (i) == GOOD_GUY)
+ ++num_aliens;
+ }
+
+ HasMaximum = (num_thrusters == NUM_DRIVE_SLOTS
+ && num_jets == NUM_JET_SLOTS
+ && GLOBAL_SIS (CrewEnlisted) >= CREW_POD_CAPACITY * 3
+ && GLOBAL_SIS (FuelOnBoard) >= FUEL_TANK_CAPACITY * 3
+ && GLOBAL_SIS (NumLanders) >= 3
+ && num_batts >= 4 && num_guns >= 7 && num_defense >= 2);
+ if (!HasMaximum && GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
+ NPCPhrase (GOT_OK_FLAGSHIP);
+ else
+ NPCPhrase (GOT_AWESOME_FLAGSHIP);
+
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) >= 2)
+ {
+ NPCPhrase (CHMMR_IMPROVED_BOMB);
+ if (FleetStrength < 20000)
+ NPCPhrase (MUST_ACQUIRE_AWESOME_FLEET);
+ else
+ {
+ NPCPhrase (GOT_AWESOME_FLEET);
+ if (!GET_GAME_STATE (TALKING_PET_ON_SHIP))
+ NPCPhrase (MUST_ELIMINATE_URQUAN_GUARDS);
+ else
+ NPCPhrase (GO_DESTROY_SAMATRA);
+ }
+ }
+ else if (num_aliens < 2)
+ NPCPhrase (GO_ALLY_WITH_ALIENS);
+ else
+ {
+ NPCPhrase (MADE_SOME_ALLIES);
+ if (FleetStrength < 6000)
+ {
+ if (GLOBAL_SIS (ResUnits) >= 3000)
+ NPCPhrase (BUY_COMBAT_SHIPS);
+ else
+ NPCPhrase (GET_SHIPS_BY_MINING_OR_ALLIANCE);
+ }
+ else
+ {
+ NPCPhrase (GOT_OK_FLEET);
+ if (!HasMaximum)
+ {
+ NPCPhrase (IMPROVE_2);
+ if (num_thrusters < NUM_DRIVE_SLOTS)
+ NPCPhrase (NEED_THRUSTERS_2);
+ if (num_jets < NUM_JET_SLOTS)
+ NPCPhrase (NEED_TURN_2);
+ if (num_guns < 7)
+ NPCPhrase (NEED_GUNS_2);
+ if (GLOBAL_SIS (CrewEnlisted) < CREW_POD_CAPACITY * 3)
+ NPCPhrase (NEED_CREW_2);
+ if (GLOBAL_SIS (FuelOnBoard) < FUEL_TANK_CAPACITY * 3)
+ NPCPhrase (NEED_FUEL_2);
+ if (GLOBAL_SIS (NumLanders) < 3)
+ NPCPhrase (NEED_LANDERS_1);
+ if (num_batts < 4)
+ NPCPhrase (NEED_DYNAMOS_2);
+ if (num_defense < 2)
+ NPCPhrase (NEED_POINT);
+ }
+ else if (!GET_GAME_STATE (AWARE_OF_SAMATRA))
+ NPCPhrase (GO_LEARN_ABOUT_URQUAN);
+ else
+ {
+ NPCPhrase (KNOW_ABOUT_SAMATRA);
+ if (!GET_GAME_STATE (UTWIG_BOMB))
+ NPCPhrase (FIND_WAY_TO_DESTROY_SAMATRA);
+ else if (GET_GAME_STATE (UTWIG_BOMB_ON_SHIP))
+ NPCPhrase (MUST_INCREASE_BOMB_STRENGTH);
+ }
+ }
+ }
+ }
+}
+
+static void
+TellMission (RESPONSE_REF R)
+{
+ RESPONSE_REF pstack[4];
+
+ if (PLAYER_SAID (R, our_mission))
+ {
+ NPCPhrase (WHICH_MISSION);
+ stack0 = 0;
+ stack1 = 0;
+ stack2 = 0;
+ stack3 = 0;
+ }
+ else if (PLAYER_SAID (R, where_get_minerals))
+ {
+ NPCPhrase (GET_MINERALS);
+
+ stack0 = 1;
+ }
+ else if (PLAYER_SAID (R, what_about_aliens))
+ {
+ NPCPhrase (ABOUT_ALIENS);
+
+ stack1 = 1;
+ }
+ else if (PLAYER_SAID (R, what_do_now))
+ {
+ AnalyzeCondition ();
+
+ stack2 = 1;
+ }
+ else if (PLAYER_SAID (R, what_about_urquan))
+ {
+ NPCPhrase (MUST_DEFEAT);
+
+ stack3 = 1;
+ }
+ else if (PLAYER_SAID (R, enough_defeat))
+ {
+ NPCPhrase (OK_ENOUGH_DEFEAT);
+
+ stack3 = 2;
+ }
+
+ switch (stack0)
+ {
+ case 0:
+ pstack[0] = where_get_minerals;
+ break;
+ default:
+ pstack[0] = 0;
+ break;
+ }
+ switch (stack1)
+ {
+ case 0:
+ pstack[1] = what_about_aliens;
+ break;
+ default:
+ pstack[1] = 0;
+ break;
+ }
+ switch (stack2)
+ {
+ case 0:
+ pstack[2] = what_do_now;
+ break;
+ default:
+ pstack[2] = 0;
+ break;
+ }
+ switch (stack3)
+ {
+ case 0:
+ pstack[3] = what_about_urquan;
+ break;
+ case 1:
+ pstack[3] = how_defeat;
+ break;
+ default:
+ pstack[3] = 0;
+ break;
+ }
+
+ if (pstack[0])
+ Response (pstack[0], TellMission);
+ if (pstack[1])
+ Response (pstack[1], TellMission);
+ if (pstack[2])
+ Response (pstack[2], TellMission);
+ if (pstack[3])
+ {
+ if (stack3 == 1)
+ Response (pstack[3], DefeatUrquan);
+ else
+ Response (pstack[3], TellMission);
+ }
+
+ Response (enough_mission, NeedInfo);
+}
+
+static void
+TellStarBase (RESPONSE_REF R)
+{
+ RESPONSE_REF pstack[4];
+ static UNICODE buf0[80];
+
+ if (PLAYER_SAID (R, starbase_functions))
+ {
+ NPCPhrase (WHICH_FUNCTION);
+ stack0 = 0;
+ stack1 = 0;
+ stack2 = 0;
+ stack3 = 0;
+ }
+ else if (PLAYER_SAID (R, tell_me_about_fuel0))
+ {
+ NPCPhrase (ABOUT_FUEL);
+
+ stack1 = 1;
+ }
+ else if (PLAYER_SAID (R, tell_me_about_crew))
+ {
+ NPCPhrase (ABOUT_CREW0);
+ if (usingSpeech)
+ NPCPhrase (YOUR_FLAGSHIP_3DO2);
+ else {
+ NPCPhrase (YOUR_FLAGSHIP_PC);
+ NPCPhrase (GLOBAL_SHIP_NAME);
+ }
+ NPCPhrase (ABOUT_CREW1);
+
+ stack2 = 2;
+ }
+ else if (PLAYER_SAID (R, tell_me_about_modules0))
+ {
+ NPCPhrase (ABOUT_MODULES);
+
+ stack0 = 1;
+ }
+ else if (PLAYER_SAID (R, tell_me_about_ships))
+ {
+ NPCPhrase (ABOUT_SHIPS);
+
+ stack2 = 1;
+ }
+ else if (PLAYER_SAID (R, tell_me_about_ru))
+ {
+ NPCPhrase (ABOUT_RU);
+
+ stack3 = 1;
+ }
+ else if (PLAYER_SAID (R, tell_me_about_minerals))
+ {
+ NPCPhrase (ABOUT_MINERALS);
+
+ stack3 = 2;
+ }
+ else if (PLAYER_SAID (R, tell_me_about_life))
+ {
+ NPCPhrase (ABOUT_LIFE);
+
+ stack3 = 3;
+ }
+
+ switch (stack0)
+ {
+ case 0:
+ construct_response (
+ buf0,
+ tell_me_about_modules0,
+ GLOBAL_SIS (ShipName),
+ tell_me_about_modules1,
+ (UNICODE*)NULL);
+ pstack[0] = tell_me_about_modules0;
+ break;
+ default:
+ pstack[0] = 0;
+ break;
+ }
+ switch (stack1)
+ {
+ case 0:
+ construct_response (
+ shared_phrase_buf,
+ tell_me_about_fuel0,
+ GLOBAL_SIS (ShipName),
+ tell_me_about_fuel1,
+ (UNICODE*)NULL);
+ pstack[1] = tell_me_about_fuel0;
+ break;
+ default:
+ pstack[1] = 0;
+ break;
+ }
+ switch (stack2)
+ {
+ case 0:
+ pstack[2] = tell_me_about_ships;
+ break;
+ case 1:
+ pstack[2] = tell_me_about_crew;
+ break;
+ default:
+ pstack[2] = 0;
+ break;
+ }
+ switch (stack3)
+ {
+ case 0:
+ pstack[3] = tell_me_about_ru;
+ break;
+ case 1:
+ pstack[3] = tell_me_about_minerals;
+ break;
+ case 2:
+ pstack[3] = tell_me_about_life;
+ break;
+ default:
+ pstack[3] = 0;
+ break;
+ }
+
+ if (pstack[0])
+ DoResponsePhrase (pstack[0], TellStarBase, buf0);
+ if (pstack[1])
+ DoResponsePhrase (pstack[1], TellStarBase, shared_phrase_buf);
+ if (pstack[2])
+ Response (pstack[2], TellStarBase);
+ if (pstack[3])
+ Response (pstack[3], TellStarBase);
+
+ Response (enough_starbase, NeedInfo);
+}
+
+static void NormalStarbase (RESPONSE_REF R);
+
+static void
+NeedInfo (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, need_info))
+ NPCPhrase (WHAT_KIND_OF_INFO);
+ else if (PLAYER_SAID (R, enough_starbase))
+ NPCPhrase (OK_ENOUGH_STARBASE);
+ else if (PLAYER_SAID (R, enough_history))
+ NPCPhrase (OK_ENOUGH_HISTORY);
+ else if (PLAYER_SAID (R, enough_mission))
+ NPCPhrase (OK_ENOUGH_MISSION);
+
+ Response (starbase_functions, TellStarBase);
+ Response (history, TellHistory);
+ Response (our_mission, TellMission);
+ Response (no_need_info, NormalStarbase);
+}
+
+static BOOLEAN
+DiscussDevices (BOOLEAN TalkAbout)
+{
+ COUNT i, VuxBeastIndex, PhraseIndex;
+ BOOLEAN Undiscussed;
+
+ if (TalkAbout)
+ NPCPhrase (DEVICE_HEAD);
+ PhraseIndex = 2;
+
+ VuxBeastIndex = 0;
+ Undiscussed = FALSE;
+ for (i = 0; i < NUM_DEVICES; ++i)
+ {
+ RESPONSE_REF pStr;
+
+ pStr = 0;
+ switch (i)
+ {
+ case ROSY_SPHERE_DEVICE:
+ if (GET_GAME_STATE (ROSY_SPHERE_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_ROSY_SPHERE))
+ {
+ pStr = ABOUT_SPHERE;
+ SET_GAME_STATE (DISCUSSED_ROSY_SPHERE, TalkAbout);
+ }
+ break;
+ case ARTIFACT_2_DEVICE:
+ if (GET_GAME_STATE (ARTIFACT_2_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_ARTIFACT_2))
+ {
+ pStr = ABOUT_ARTIFACT_2;
+ SET_GAME_STATE (DISCUSSED_ARTIFACT_2, TalkAbout);
+ }
+ break;
+ case ARTIFACT_3_DEVICE:
+ if (GET_GAME_STATE (ARTIFACT_3_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_ARTIFACT_3))
+ {
+ pStr = ABOUT_ARTIFACT_3;
+ SET_GAME_STATE (DISCUSSED_ARTIFACT_3, TalkAbout);
+ }
+ break;
+ case SUN_EFFICIENCY_DEVICE:
+ if (GET_GAME_STATE (SUN_DEVICE_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_SUN_EFFICIENCY))
+ {
+ pStr = ABOUT_SUN;
+ SET_GAME_STATE (DISCUSSED_SUN_EFFICIENCY, TalkAbout);
+ }
+ break;
+ case UTWIG_BOMB_DEVICE:
+ if (GET_GAME_STATE (UTWIG_BOMB_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_UTWIG_BOMB))
+ {
+ pStr = ABOUT_BOMB;
+ SET_GAME_STATE (DISCUSSED_UTWIG_BOMB, TalkAbout);
+ }
+ break;
+ case ULTRON_0_DEVICE:
+ if (GET_GAME_STATE (ULTRON_CONDITION) == 1
+ && !GET_GAME_STATE (DISCUSSED_ULTRON))
+ {
+ pStr = ABOUT_ULTRON_0;
+ SET_GAME_STATE (DISCUSSED_ULTRON, TalkAbout);
+ }
+ break;
+ case ULTRON_1_DEVICE:
+ if (GET_GAME_STATE (ULTRON_CONDITION) == 2
+ && !GET_GAME_STATE (DISCUSSED_ULTRON))
+ {
+ pStr = ABOUT_ULTRON_1;
+ SET_GAME_STATE (DISCUSSED_ULTRON, TalkAbout);
+ }
+ break;
+ case ULTRON_2_DEVICE:
+ if (GET_GAME_STATE (ULTRON_CONDITION) == 3
+ && !GET_GAME_STATE (DISCUSSED_ULTRON))
+ {
+ pStr = ABOUT_ULTRON_2;
+ SET_GAME_STATE (DISCUSSED_ULTRON, TalkAbout);
+ }
+ break;
+ case ULTRON_3_DEVICE:
+ if (GET_GAME_STATE (ULTRON_CONDITION) == 4
+ && !GET_GAME_STATE (DISCUSSED_ULTRON))
+ {
+ pStr = ABOUT_ULTRON_3;
+ SET_GAME_STATE (DISCUSSED_ULTRON, TalkAbout);
+ }
+ break;
+ case MAIDENS_DEVICE:
+ if (GET_GAME_STATE (MAIDENS_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_MAIDENS))
+ {
+ pStr = ABOUT_MAIDENS;
+ SET_GAME_STATE (DISCUSSED_MAIDENS, TalkAbout);
+ }
+ break;
+ case TALKING_PET_DEVICE:
+ if (GET_GAME_STATE (TALKING_PET_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_TALKING_PET))
+ {
+ pStr = ABOUT_TALKPET;
+ SET_GAME_STATE (DISCUSSED_TALKING_PET, TalkAbout);
+ }
+ break;
+ case AQUA_HELIX_DEVICE:
+ if (GET_GAME_STATE (AQUA_HELIX_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_AQUA_HELIX))
+ {
+ pStr = ABOUT_HELIX;
+ SET_GAME_STATE (DISCUSSED_AQUA_HELIX, TalkAbout);
+ }
+ break;
+ case CLEAR_SPINDLE_DEVICE:
+ if (GET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_CLEAR_SPINDLE))
+ {
+ pStr = ABOUT_SPINDLE;
+ SET_GAME_STATE (DISCUSSED_CLEAR_SPINDLE, TalkAbout);
+ }
+ break;
+ case UMGAH_HYPERWAVE_DEVICE:
+ if (GET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_UMGAH_HYPERWAVE))
+ {
+ pStr = ABOUT_UCASTER;
+ SET_GAME_STATE (DISCUSSED_UMGAH_HYPERWAVE, TalkAbout);
+ }
+ break;
+#ifdef NEVER
+ case DATA_PLATE_1_DEVICE:
+ if (GET_GAME_STATE (DATA_PLATE_1_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_DATA_PLATE_1))
+ {
+ pStr = ABOUT_DATAPLATE_1;
+ SET_GAME_STATE (DISCUSSED_DATA_PLATE_1, TalkAbout);
+ }
+ break;
+ case DATA_PLATE_2_DEVICE:
+ if (GET_GAME_STATE (DATA_PLATE_2_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_DATA_PLATE_2))
+ {
+ pStr = ABOUT_DATAPLATE_2;
+ SET_GAME_STATE (DISCUSSED_DATA_PLATE_2, TalkAbout);
+ }
+ break;
+ case DATA_PLATE_3_DEVICE:
+ if (GET_GAME_STATE (DATA_PLATE_3_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_DATA_PLATE_3))
+ {
+ pStr = ABOUT_DATAPLATE_3;
+ SET_GAME_STATE (DISCUSSED_DATA_PLATE_3, TalkAbout);
+ }
+ break;
+#endif /* NEVER */
+ case TAALO_PROTECTOR_DEVICE:
+ if (GET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_TAALO_PROTECTOR))
+ {
+ pStr = ABOUT_SHIELD;
+ SET_GAME_STATE (DISCUSSED_TAALO_PROTECTOR, TalkAbout);
+ }
+ break;
+ case EGG_CASING0_DEVICE:
+ if (GET_GAME_STATE (EGG_CASE0_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_EGG_CASING0))
+ {
+ pStr = ABOUT_EGGCASE_0;
+ SET_GAME_STATE (DISCUSSED_EGG_CASING0, TalkAbout);
+ SET_GAME_STATE (DISCUSSED_EGG_CASING1, TalkAbout);
+ SET_GAME_STATE (DISCUSSED_EGG_CASING2, TalkAbout);
+ }
+ break;
+ case EGG_CASING1_DEVICE:
+ if (GET_GAME_STATE (EGG_CASE1_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_EGG_CASING1))
+ {
+ pStr = ABOUT_EGGCASE_0;
+ SET_GAME_STATE (DISCUSSED_EGG_CASING0, TalkAbout);
+ SET_GAME_STATE (DISCUSSED_EGG_CASING1, TalkAbout);
+ SET_GAME_STATE (DISCUSSED_EGG_CASING2, TalkAbout);
+ }
+ break;
+ case EGG_CASING2_DEVICE:
+ if (GET_GAME_STATE (EGG_CASE2_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_EGG_CASING2))
+ {
+ pStr = ABOUT_EGGCASE_0;
+ SET_GAME_STATE (DISCUSSED_EGG_CASING0, TalkAbout);
+ SET_GAME_STATE (DISCUSSED_EGG_CASING1, TalkAbout);
+ SET_GAME_STATE (DISCUSSED_EGG_CASING2, TalkAbout);
+ }
+ break;
+ case SYREEN_SHUTTLE_DEVICE:
+ if (GET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_SYREEN_SHUTTLE))
+ {
+ pStr = ABOUT_SHUTTLE;
+ SET_GAME_STATE (DISCUSSED_SYREEN_SHUTTLE, TalkAbout);
+ }
+ break;
+ case VUX_BEAST_DEVICE:
+ if (GET_GAME_STATE (VUX_BEAST_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_VUX_BEAST))
+ {
+ pStr = ABOUT_VUXBEAST0;
+ SET_GAME_STATE (DISCUSSED_VUX_BEAST, TalkAbout);
+ }
+ break;
+ case DESTRUCT_CODE_DEVICE:
+ if (GET_GAME_STATE (DESTRUCT_CODE_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_DESTRUCT_CODE))
+ {
+ pStr = ABOUT_DESTRUCT;
+ SET_GAME_STATE (DISCUSSED_DESTRUCT_CODE, TalkAbout);
+ }
+ break;
+ case PORTAL_SPAWNER_DEVICE:
+ if (GET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_PORTAL_SPAWNER))
+ {
+ pStr = ABOUT_PORTAL;
+ SET_GAME_STATE (DISCUSSED_PORTAL_SPAWNER, TalkAbout);
+ }
+ break;
+ case URQUAN_WARP_DEVICE:
+ if (GET_GAME_STATE (PORTAL_KEY_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_URQUAN_WARP))
+ {
+ pStr = ABOUT_WARPPOD;
+ SET_GAME_STATE (DISCUSSED_URQUAN_WARP, TalkAbout);
+ }
+ break;
+ case BURVIX_HYPERWAVE_DEVICE:
+ if (GET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP)
+ && !GET_GAME_STATE (DISCUSSED_BURVIX_HYPERWAVE))
+ {
+ pStr = ABOUT_BCASTER;
+ SET_GAME_STATE (DISCUSSED_BURVIX_HYPERWAVE, TalkAbout);
+ }
+ break;
+ }
+
+ if (pStr)
+ {
+ if (TalkAbout)
+ {
+ if (PhraseIndex > 2)
+ NPCPhrase (BETWEEN_DEVICES);
+ NPCPhrase (pStr);
+ if (pStr == ABOUT_VUXBEAST0)
+ {
+ VuxBeastIndex = ++PhraseIndex;
+ NPCPhrase (ABOUT_VUXBEAST1);
+ }
+ }
+ PhraseIndex += 2;
+ }
+ }
+
+ if (TalkAbout)
+ {
+ NPCPhrase (DEVICE_TAIL);
+
+ if (VuxBeastIndex)
+ {
+ // Run all tracks upto the Vux Beast scientist's report
+ AlienTalkSegue (VuxBeastIndex - 1);
+ // Disable Commander's speech animation and run the report
+ EnableTalkingAnim (FALSE);
+ AlienTalkSegue (VuxBeastIndex);
+ // Enable Commander's speech animation and run the rest
+ EnableTalkingAnim (TRUE);
+ AlienTalkSegue ((COUNT)~0);
+ }
+ }
+
+ return (PhraseIndex > 2);
+}
+
+static BOOLEAN
+CheckTiming (COUNT month_index, COUNT day_index)
+{
+ COUNT mi, year_index;
+ BYTE days_in_month[12] =
+ {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
+ };
+
+ mi = GET_GAME_STATE (STARBASE_MONTH);
+ year_index = START_YEAR;
+
+ day_index += GET_GAME_STATE (STARBASE_DAY);
+ while (day_index > days_in_month[mi - 1])
+ {
+ day_index -= days_in_month[mi - 1];
+ if (++mi > 12)
+ {
+ mi = 1;
+ ++year_index;
+ }
+ }
+
+ month_index += mi;
+ year_index += (month_index - 1) / 12;
+ month_index = ((month_index - 1) % 12) + 1;
+
+ return (year_index < GLOBAL (GameClock.year_index)
+ || (year_index == GLOBAL (GameClock.year_index)
+ && (month_index < GLOBAL (GameClock.month_index)
+ || (month_index == GLOBAL (GameClock.month_index)
+ && day_index < GLOBAL (GameClock.day_index)))));
+}
+
+static void
+CheckBulletins (BOOLEAN Repeat)
+{
+ RESPONSE_REF pIntro;
+ BYTE b0;
+ DWORD BulletinMask;
+
+ if (Repeat)
+ BulletinMask = CurBulletinMask ^ 0xFFFFFFFFL;
+ else
+ BulletinMask = GET_GAME_STATE_32 (STARBASE_BULLETS0);
+
+ pIntro = 0;
+ for (b0 = 0; b0 < 32; ++b0)
+ {
+ if (!(BulletinMask & (1L << b0)))
+ {
+ RESPONSE_REF pStr;
+
+ pStr = 0;
+ switch (b0)
+ {
+ case 0:
+ if (CheckAlliance (SPATHI_SHIP) == GOOD_GUY)
+ {
+ pStr = STARBASE_BULLETIN_1;
+ }
+ break;
+ case 1:
+ if (CheckAlliance (ZOQFOTPIK_SHIP) == GOOD_GUY)
+ {
+ pStr = STARBASE_BULLETIN_2;
+ }
+ break;
+ case 2:
+ if (CheckAlliance (SUPOX_SHIP) == GOOD_GUY)
+ {
+ pStr = STARBASE_BULLETIN_3;
+ }
+ break;
+ case 3:
+ if (CheckAlliance (UTWIG_SHIP) == GOOD_GUY)
+ {
+ pStr = STARBASE_BULLETIN_4;
+ }
+ break;
+ case 4:
+ if (CheckAlliance (ORZ_SHIP) == GOOD_GUY)
+ {
+ pStr = STARBASE_BULLETIN_5;
+ }
+ break;
+ case 5:
+ if (GET_GAME_STATE (ARILOU_MANNER) == 2)
+ BulletinMask |= 1L << b0;
+ else if (GET_GAME_STATE (PORTAL_SPAWNER)
+ && (Repeat || EscortFeasibilityStudy (
+ ARILOU_SHIP)))
+ {
+#define NUM_GIFT_ARILOUS 3
+ pStr = STARBASE_BULLETIN_6;
+ if (!Repeat)
+ AddEscortShips (ARILOU_SHIP, NUM_GIFT_ARILOUS);
+ }
+ break;
+ case 6:
+ if (GET_GAME_STATE (ZOQFOT_DISTRESS) == 1)
+ {
+ pStr = STARBASE_BULLETIN_7;
+ }
+ break;
+ case 7:
+ if (GET_GAME_STATE (MET_MELNORME))
+ BulletinMask |= 1L << b0;
+ else if (CheckTiming (3, 0))
+ {
+ pStr = STARBASE_BULLETIN_8;
+ }
+ break;
+ case 8:
+ if (GET_GAME_STATE (MET_MELNORME))
+ BulletinMask |= 1L << b0;
+ else if (CheckTiming (6, 0))
+ {
+ pStr = STARBASE_BULLETIN_9;
+ }
+ break;
+ case 9:
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI))
+ BulletinMask |= 1L << b0;
+ else if (CheckTiming (0, 7))
+ {
+ pStr = STARBASE_BULLETIN_10;
+ }
+ break;
+ case 10:
+ if (GET_GAME_STATE (SPATHI_SHIELDED_SELVES))
+ {
+ pStr = STARBASE_BULLETIN_11;
+ }
+ break;
+ case 11:
+ if (GET_GAME_STATE (ZOQFOT_HOME_VISITS)
+ || GET_GAME_STATE_32 (ZOQFOT_GRPOFFS0))
+ BulletinMask |= 1L << b0;
+ else if (CheckTiming (0, 42))
+ {
+ pStr = STARBASE_BULLETIN_12;
+ }
+ break;
+ case 12:
+ if (CheckAlliance (CHMMR_SHIP) == GOOD_GUY)
+ {
+ pStr = STARBASE_BULLETIN_13;
+ }
+ break;
+ case 13:
+ if (CheckAlliance (SHOFIXTI_SHIP) == GOOD_GUY)
+ {
+ pStr = STARBASE_BULLETIN_14;
+ }
+ break;
+ case 14:
+ if (GET_GAME_STATE (PKUNK_MISSION))
+ {
+ pStr = STARBASE_BULLETIN_15;
+ }
+ break;
+ case 15:
+ if (GET_GAME_STATE (DESTRUCT_CODE_ON_SHIP))
+ BulletinMask |= 1L << b0;
+ else if (CheckTiming (7, 0))
+ {
+ pStr = STARBASE_BULLETIN_16;
+ }
+ break;
+ case 16:
+ break;
+ case 17:
+ if (GET_GAME_STATE (YEHAT_ABSORBED_PKUNK))
+ {
+ pStr = STARBASE_BULLETIN_18;
+ }
+ break;
+ case 18:
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
+ {
+ pStr = STARBASE_BULLETIN_19;
+ }
+ break;
+ case 19:
+ break;
+ case 20:
+ break;
+ case 21:
+ if (GET_GAME_STATE (ZOQFOT_DISTRESS) == 2)
+ {
+ pStr = STARBASE_BULLETIN_22;
+ }
+ break;
+ case 22:
+ break;
+ case 23:
+ break;
+ case 24:
+ break;
+ case 25:
+ break;
+ case 26:
+ {
+ COUNT crew_sold;
+
+ crew_sold = MAKE_WORD (
+ GET_GAME_STATE (CREW_SOLD_TO_DRUUGE0),
+ GET_GAME_STATE (CREW_SOLD_TO_DRUUGE1)
+ );
+ if (crew_sold > 100)
+ BulletinMask |= 1L << b0;
+ else if (crew_sold)
+ {
+ pStr = STARBASE_BULLETIN_27;
+ }
+ break;
+ }
+ case 27:
+ {
+ COUNT crew_sold;
+
+ crew_sold = MAKE_WORD (
+ GET_GAME_STATE (CREW_SOLD_TO_DRUUGE0),
+ GET_GAME_STATE (CREW_SOLD_TO_DRUUGE1)
+ );
+ if (crew_sold > 250)
+ BulletinMask |= 1L << b0;
+ else if (crew_sold > 100)
+ {
+ pStr = STARBASE_BULLETIN_28;
+ }
+ break;
+ }
+ case 28:
+ {
+ COUNT crew_bought;
+
+ crew_bought = MAKE_WORD (
+ GET_GAME_STATE (CREW_PURCHASED0),
+ GET_GAME_STATE (CREW_PURCHASED1)
+ );
+ if (crew_bought >= CREW_EXPENSE_THRESHOLD)
+ {
+ pStr = STARBASE_BULLETIN_29;
+ }
+ break;
+ }
+ case 29:
+ if (MAKE_WORD (
+ GET_GAME_STATE (CREW_SOLD_TO_DRUUGE0),
+ GET_GAME_STATE (CREW_SOLD_TO_DRUUGE1)
+ ) > 250)
+ {
+ pStr = STARBASE_BULLETIN_30;
+ }
+ break;
+ case 30:
+ break;
+ case 31:
+ break;
+ }
+
+ if (pStr)
+ {
+ if (pIntro)
+ NPCPhrase (BETWEEN_BULLETINS);
+ else if (Repeat)
+ pIntro = BEFORE_WE_GO_ON_1;
+ else
+ {
+ switch ((BYTE)TFB_Random () % 7)
+ {
+ case 0:
+ pIntro = BEFORE_WE_GO_ON_1;
+ break;
+ case 1:
+ pIntro = BEFORE_WE_GO_ON_2;
+ break;
+ case 2:
+ pIntro = BEFORE_WE_GO_ON_3;
+ break;
+ case 3:
+ pIntro = BEFORE_WE_GO_ON_4;
+ break;
+ case 4:
+ pIntro = BEFORE_WE_GO_ON_5;
+ break;
+ case 5:
+ pIntro = BEFORE_WE_GO_ON_6;
+ break;
+ default:
+ pIntro = BEFORE_WE_GO_ON_7;
+ break;
+ }
+
+ NPCPhrase (pIntro);
+ }
+
+ NPCPhrase (pStr);
+ CurBulletinMask |= 1L << b0;
+ }
+ }
+ }
+
+ if (pIntro == 0 && GET_GAME_STATE (STARBASE_VISITED))
+ NPCPhrase (RETURN_HELLO);
+ else if (!Repeat)
+ SET_GAME_STATE_32 (STARBASE_BULLETS0, BulletinMask);
+}
+
+static void
+NormalStarbase (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, no_need_info))
+ NPCPhrase (OK_NO_NEED_INFO);
+ else if (PLAYER_SAID (R, new_devices))
+ DiscussDevices (TRUE);
+ else if (PLAYER_SAID (R, repeat_bulletins))
+ CheckBulletins (TRUE);
+ else if (R == 0)
+ {
+ if (GET_GAME_STATE (MOONBASE_ON_SHIP))
+ {
+ NPCPhrase (STARBASE_IS_READY_A);
+ if (usingSpeech)
+ NPCPhrase (YOUR_FLAGSHIP_3DO1);
+ else {
+ NPCPhrase (YOUR_FLAGSHIP_PC);
+ NPCPhrase (GLOBAL_SHIP_NAME);
+ }
+ NPCPhrase (STARBASE_IS_READY_B);
+ if (usingSpeech)
+ NPCPhrase (YOUR_FLAGSHIP_3DO0);
+ else
+ NPCPhrase (GLOBAL_SHIP_NAME);
+ NPCPhrase (STARBASE_IS_READY_C);
+ DeltaSISGauges (0, 0, 2500);
+ SET_GAME_STATE (STARBASE_MONTH,
+ GLOBAL (GameClock.month_index));
+ SET_GAME_STATE (STARBASE_DAY,
+ GLOBAL (GameClock.day_index));
+ }
+ else if (GET_GAME_STATE (STARBASE_VISITED))
+ {
+ CheckBulletins (FALSE);
+ }
+ else
+ {
+ RESPONSE_REF pStr0 = 0;
+ RESPONSE_REF pStr1 = 0;
+
+ switch ((BYTE)TFB_Random () & 7)
+ {
+ case 0:
+ pStr0 = NORMAL_HELLO_A0;
+ pStr1 = NORMAL_HELLO_A1;
+ break;
+ case 1:
+ pStr0 = NORMAL_HELLO_B0;
+ pStr1 = NORMAL_HELLO_B1;
+ break;
+ case 2:
+ pStr0 = NORMAL_HELLO_C0;
+ pStr1 = NORMAL_HELLO_C1;
+ break;
+ case 3:
+ pStr0 = NORMAL_HELLO_D0;
+ pStr1 = NORMAL_HELLO_D1;
+ break;
+ case 4:
+ pStr0 = NORMAL_HELLO_E0;
+ pStr1 = NORMAL_HELLO_E1;
+ break;
+ case 5:
+ pStr0 = NORMAL_HELLO_F0;
+ pStr1 = NORMAL_HELLO_F1;
+ break;
+ case 6:
+ pStr0 = NORMAL_HELLO_G0;
+ pStr1 = NORMAL_HELLO_G1;
+ break;
+ case 7:
+ pStr0 = NORMAL_HELLO_H0;
+ pStr1 = NORMAL_HELLO_H1;
+ break;
+ }
+ NPCPhrase (pStr0);
+ if (!usingSpeech)
+ {
+ NPCPhrase (SPACE);
+ NPCPhrase (GLOBAL_PLAYER_NAME);
+ }
+ NPCPhrase (pStr1);
+ CheckBulletins (FALSE);
+ }
+
+ SET_GAME_STATE (STARBASE_VISITED, 1);
+ }
+
+ if (GLOBAL_SIS (TotalElementMass))
+ Response (have_minerals, SellMinerals);
+ if (DiscussDevices (FALSE))
+ Response (new_devices, NormalStarbase);
+ if (CurBulletinMask)
+ Response (repeat_bulletins, NormalStarbase);
+ Response (need_info, NeedInfo);
+ Response (goodbye_commander, ByeBye);
+}
+
+static void
+SellMinerals (RESPONSE_REF R)
+{
+ COUNT i, total;
+ BOOLEAN Sleepy;
+ RESPONSE_REF pStr1 = 0;
+ RESPONSE_REF pStr2 = 0;
+
+ total = 0;
+ Sleepy = TRUE;
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i)
+ {
+ COUNT amount;
+ DWORD TimeIn = 0;
+
+ if (i == 0)
+ {
+ DrawCargoStrings ((BYTE)~0, (BYTE)~0);
+ SleepThread (ONE_SECOND / 2);
+ TimeIn = GetTimeCounter ();
+ DrawCargoStrings ((BYTE)0, (BYTE)0);
+ }
+ else if (Sleepy)
+ {
+ DrawCargoStrings ((BYTE)(i - 1), (BYTE)i);
+ TimeIn = GetTimeCounter ();
+ }
+
+ if ((amount = GLOBAL_SIS (ElementAmounts[i])) != 0)
+ {
+ total += amount * GLOBAL (ElementWorth[i]);
+ do
+ {
+ if (!Sleepy || AnyButtonPress (TRUE) ||
+ (GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ Sleepy = FALSE;
+ GLOBAL_SIS (ElementAmounts[i]) = 0;
+ GLOBAL_SIS (TotalElementMass) -= amount;
+ DeltaSISGauges (0, 0, amount * GLOBAL (ElementWorth[i]));
+ break;
+ }
+
+ --GLOBAL_SIS (ElementAmounts[i]);
+ --GLOBAL_SIS (TotalElementMass);
+ TaskSwitch ();
+ TimeIn = GetTimeCounter ();
+ DrawCargoStrings ((BYTE)i, (BYTE)i);
+ ShowRemainingCapacity ();
+ DeltaSISGauges (0, 0, GLOBAL (ElementWorth[i]));
+ } while (--amount);
+ }
+ if (Sleepy) {
+ SleepThreadUntil (TimeIn + (ONE_SECOND / 4));
+ TimeIn = GetTimeCounter ();
+ }
+ }
+ SleepThread (ONE_SECOND / 2);
+
+ ClearSISRect (DRAW_SIS_DISPLAY);
+// DrawStorageBays (FALSE);
+
+ if (total < 1000)
+ {
+ total = GET_GAME_STATE (LIGHT_MINERAL_LOAD);
+ switch (total++)
+ {
+ case 0:
+ pStr1 = LIGHT_LOAD_A0;
+ pStr2 = LIGHT_LOAD_A1;
+ break;
+ case 1:
+ pStr1 = LIGHT_LOAD_B0;
+ pStr2 = LIGHT_LOAD_B1;
+ break;
+ case 2:
+ pStr1 = LIGHT_LOAD_C0;
+ pStr2 = LIGHT_LOAD_C1;
+ break;
+ case 3:
+ pStr1 = LIGHT_LOAD_D0;
+ pStr2 = LIGHT_LOAD_D1;
+ break;
+ case 4:
+ pStr1 = LIGHT_LOAD_E0;
+ pStr2 = LIGHT_LOAD_E1;
+ break;
+ case 5:
+ pStr1 = LIGHT_LOAD_F0;
+ pStr2 = LIGHT_LOAD_F1;
+ break;
+ case 6:
+ --total;
+ pStr1 = LIGHT_LOAD_G0;
+ pStr2 = LIGHT_LOAD_G1;
+ break;
+ }
+ SET_GAME_STATE (LIGHT_MINERAL_LOAD, total);
+ }
+ else if (total < 2500)
+ {
+ total = GET_GAME_STATE (MEDIUM_MINERAL_LOAD);
+ switch (total++)
+ {
+ case 0:
+ pStr1 = MEDIUM_LOAD_A0;
+ pStr2 = MEDIUM_LOAD_A1;
+ break;
+ case 1:
+ pStr1 = MEDIUM_LOAD_B0;
+ pStr2 = MEDIUM_LOAD_B1;
+ break;
+ case 2:
+ pStr1 = MEDIUM_LOAD_C0;
+ pStr2 = MEDIUM_LOAD_C1;
+ break;
+ case 3:
+ pStr1 = MEDIUM_LOAD_D0;
+ pStr2 = MEDIUM_LOAD_D1;
+ break;
+ case 4:
+ pStr1 = MEDIUM_LOAD_E0;
+ pStr2 = MEDIUM_LOAD_E1;
+ break;
+ case 5:
+ pStr1 = MEDIUM_LOAD_F0;
+ pStr2 = MEDIUM_LOAD_F1;
+ break;
+ case 6:
+ --total;
+ pStr1 = MEDIUM_LOAD_G0;
+ pStr2 = MEDIUM_LOAD_G1;
+ break;
+ }
+ SET_GAME_STATE (MEDIUM_MINERAL_LOAD, total);
+ }
+ else
+ {
+ total = GET_GAME_STATE (HEAVY_MINERAL_LOAD);
+ switch (total++)
+ {
+ case 0:
+ pStr1 = HEAVY_LOAD_A0;
+ pStr2 = HEAVY_LOAD_A1;
+ break;
+ case 1:
+ pStr1 = HEAVY_LOAD_B0;
+ pStr2 = HEAVY_LOAD_B1;
+ break;
+ case 2:
+ pStr1 = HEAVY_LOAD_C0;
+ pStr2 = HEAVY_LOAD_C1;
+ break;
+ case 3:
+ pStr1 = HEAVY_LOAD_D0;
+ pStr2 = HEAVY_LOAD_D1;
+ break;
+ case 4:
+ pStr1 = HEAVY_LOAD_E0;
+ pStr2 = HEAVY_LOAD_E1;
+ break;
+ case 5:
+ pStr1 = HEAVY_LOAD_F0;
+ pStr2 = HEAVY_LOAD_F1;
+ break;
+ case 6:
+ --total;
+ pStr1 = HEAVY_LOAD_G0;
+ pStr2 = HEAVY_LOAD_G1;
+ break;
+ }
+ SET_GAME_STATE (HEAVY_MINERAL_LOAD, total);
+ }
+
+ NPCPhrase (pStr1);
+ if (!usingSpeech)
+ {
+ NPCPhrase (SPACE);
+ NPCPhrase (GLOBAL_PLAYER_NAME);
+ }
+ NPCPhrase (pStr2);
+
+ NormalStarbase (R);
+}
+
+static void
+Intro (void)
+{
+ NormalStarbase (0);
+}
+
+static COUNT
+uninit_starbase (void)
+{
+ return (0);
+}
+
+static void
+post_starbase_enc (void)
+{
+ SET_GAME_STATE (MOONBASE_ON_SHIP, 0);
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
+ {
+ SET_GAME_STATE (CHMMR_BOMB_STATE, 3);
+ }
+}
+
+LOCDATA*
+init_starbase_comm ()
+{
+ LOCDATA *retval;
+
+ commander_desc.init_encounter_func = Intro;
+ commander_desc.post_encounter_func = post_starbase_enc;
+ commander_desc.uninit_encounter_func = uninit_starbase;
+
+ commander_desc.AlienTextWidth = 143;
+ commander_desc.AlienTextBaseline.x = 164;
+ commander_desc.AlienTextBaseline.y = 20;
+
+ // use alternate Starbase track if available
+ commander_desc.AlienAltSongRes = STARBASE_ALT_MUSIC;
+ commander_desc.AlienSongFlags |= LDASF_USE_ALTERNATE;
+
+ CurBulletinMask = 0;
+ setSegue (Segue_peace);
+ retval = &commander_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/starbas/strings.h b/src/uqm/comm/starbas/strings.h
new file mode 100644
index 0000000..df123f3
--- /dev/null
+++ b/src/uqm/comm/starbas/strings.h
@@ -0,0 +1,327 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef STARBAS_STRINGS_H
+#define STARBAS_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ BEFORE_WE_GO_ON_1,
+ BEFORE_WE_GO_ON_2,
+ BEFORE_WE_GO_ON_3,
+ BEFORE_WE_GO_ON_4,
+ BEFORE_WE_GO_ON_5,
+ BEFORE_WE_GO_ON_6,
+ BEFORE_WE_GO_ON_7,
+ NORMAL_HELLO_A0,
+ NORMAL_HELLO_A1,
+ NORMAL_HELLO_B0,
+ NORMAL_HELLO_B1,
+ NORMAL_HELLO_C0,
+ NORMAL_HELLO_C1,
+ NORMAL_HELLO_D0,
+ NORMAL_HELLO_D1,
+ NORMAL_HELLO_E0,
+ NORMAL_HELLO_E1,
+ NORMAL_HELLO_F0,
+ NORMAL_HELLO_F1,
+ NORMAL_HELLO_G0,
+ NORMAL_HELLO_G1,
+ NORMAL_HELLO_H0,
+ NORMAL_HELLO_H1,
+ RETURN_HELLO,
+ NORMAL_HELLO_TAIL,
+ NORMAL_GOODBYE_A0,
+ NORMAL_GOODBYE_A1,
+ NORMAL_GOODBYE_B0,
+ NORMAL_GOODBYE_B1,
+ NORMAL_GOODBYE_C0,
+ NORMAL_GOODBYE_C1,
+ NORMAL_GOODBYE_D0,
+ NORMAL_GOODBYE_D1,
+ NORMAL_GOODBYE_E0,
+ NORMAL_GOODBYE_E1,
+ NORMAL_GOODBYE_F0,
+ NORMAL_GOODBYE_F1,
+ NORMAL_GOODBYE_G0,
+ NORMAL_GOODBYE_G1,
+ NORMAL_GOODBYE_H0,
+ NORMAL_GOODBYE_H1,
+ LIGHT_LOAD_A0,
+ LIGHT_LOAD_A1,
+ LIGHT_LOAD_B0,
+ LIGHT_LOAD_B1,
+ LIGHT_LOAD_C0,
+ LIGHT_LOAD_C1,
+ LIGHT_LOAD_D0,
+ LIGHT_LOAD_D1,
+ LIGHT_LOAD_E0,
+ LIGHT_LOAD_E1,
+ LIGHT_LOAD_F0,
+ LIGHT_LOAD_F1,
+ LIGHT_LOAD_G0,
+ LIGHT_LOAD_G1,
+ MEDIUM_LOAD_A0,
+ MEDIUM_LOAD_A1,
+ MEDIUM_LOAD_B0,
+ MEDIUM_LOAD_B1,
+ MEDIUM_LOAD_C0,
+ MEDIUM_LOAD_C1,
+ MEDIUM_LOAD_D0,
+ MEDIUM_LOAD_D1,
+ MEDIUM_LOAD_E0,
+ MEDIUM_LOAD_E1,
+ MEDIUM_LOAD_F0,
+ MEDIUM_LOAD_F1,
+ MEDIUM_LOAD_G0,
+ MEDIUM_LOAD_G1,
+ HEAVY_LOAD_A0,
+ HEAVY_LOAD_A1,
+ HEAVY_LOAD_B0,
+ HEAVY_LOAD_B1,
+ HEAVY_LOAD_C0,
+ HEAVY_LOAD_C1,
+ HEAVY_LOAD_D0,
+ HEAVY_LOAD_D1,
+ HEAVY_LOAD_E0,
+ HEAVY_LOAD_E1,
+ HEAVY_LOAD_F0,
+ HEAVY_LOAD_F1,
+ HEAVY_LOAD_G0 ,
+ HEAVY_LOAD_G1,
+ STARBASE_IS_READY_A,
+ STARBASE_IS_READY_B,
+ STARBASE_IS_READY_C,
+ WHAT_KIND_OF_INFO,
+ WHICH_FUNCTION,
+ WHICH_HISTORY,
+ WHICH_MISSION,
+ OK_NO_NEED_INFO,
+ ABOUT_FUEL,
+ ABOUT_MODULES,
+ ABOUT_CREW0,
+ ABOUT_CREW1,
+ ABOUT_SHIPS,
+ ABOUT_RU,
+ ABOUT_MINERALS,
+ ABOUT_LIFE,
+ OK_ENOUGH_STARBASE,
+ OK_ENOUGH_MISSION,
+ GET_MINERALS,
+ ABOUT_ALIENS,
+ MUST_DEFEAT,
+ DEFEAT_LIKE_SO,
+ FIND_URQUAN,
+ FIGHT_URQUAN,
+ ALLY_LIKE_SO,
+ STRONG_LIKE_SO,
+ OK_ENOUGH_DEFEAT,
+ WHICH_ALIEN,
+ WHICH_WAR,
+ WHICH_ANCIENT,
+ OK_ENOUGH_HISTORY,
+ WHICH_ALLIANCE,
+ WHICH_HIERARCHY,
+ ABOUT_OTHER,
+ OK_ENOUGH_ALIENS,
+ ABOUT_SHOFIXTI,
+ ABOUT_YEHAT,
+ ABOUT_ARILOU,
+ ABOUT_CHENJESU,
+ ABOUT_MMRNMHRM,
+ ABOUT_SYREEN,
+ OK_ENOUGH_ALLIANCE,
+ ABOUT_URQUAN,
+ ABOUT_MYCON,
+ ABOUT_SPATHI,
+ ABOUT_UMGAH,
+ ABOUT_ANDROSYNTH,
+ ABOUT_VUX,
+ ABOUT_ILWRATH,
+ OK_ENOUGH_HIERARCHY,
+ ABOUT_PRECURSORS,
+ ABOUT_OLD_RACES,
+ ABOUT_ALIENS_ON_EARTH,
+ OK_ENOUGH_ANCIENT,
+ URQUAN_STARTED_WAR,
+ WAR_WAS_LIKE_SO,
+ LOST_WAR_BECAUSE,
+ AFTER_WAR,
+ OK_ENOUGH_WAR,
+ STARBASE_BULLETIN_TAIL,
+ BETWEEN_BULLETINS,
+ STARBASE_BULLETIN_1,
+ STARBASE_BULLETIN_2,
+ STARBASE_BULLETIN_3,
+ STARBASE_BULLETIN_4,
+ STARBASE_BULLETIN_5,
+ STARBASE_BULLETIN_6,
+ STARBASE_BULLETIN_7,
+ STARBASE_BULLETIN_8,
+ STARBASE_BULLETIN_9,
+ STARBASE_BULLETIN_10,
+ STARBASE_BULLETIN_11,
+ STARBASE_BULLETIN_12,
+ STARBASE_BULLETIN_13,
+ STARBASE_BULLETIN_14,
+ STARBASE_BULLETIN_15,
+ STARBASE_BULLETIN_16,
+ STARBASE_BULLETIN_18,
+ STARBASE_BULLETIN_19,
+ STARBASE_BULLETIN_22,
+ STARBASE_BULLETIN_27,
+ STARBASE_BULLETIN_28,
+ STARBASE_BULLETIN_29,
+ STARBASE_BULLETIN_30,
+ DEVICE_HEAD,
+ BETWEEN_DEVICES,
+ DEVICE_TAIL,
+ ABOUT_PORTAL,
+ ABOUT_TALKPET,
+ ABOUT_BOMB,
+ ABOUT_SUN,
+ ABOUT_MAIDENS,
+ ABOUT_SPHERE,
+ ABOUT_HELIX,
+ ABOUT_SPINDLE,
+ ABOUT_ULTRON_0,
+ ABOUT_ULTRON_1,
+ ABOUT_ULTRON_2,
+ ABOUT_ULTRON_3,
+ ABOUT_UCASTER,
+ ABOUT_BCASTER,
+ ABOUT_SHIELD,
+ ABOUT_EGGCASE_0,
+ ABOUT_SHUTTLE,
+ ABOUT_VUXBEAST0,
+ ABOUT_VUXBEAST1,
+ ABOUT_DESTRUCT,
+ ABOUT_WARPPOD,
+ ABOUT_ARTIFACT_2,
+ ABOUT_ARTIFACT_3,
+ LETS_SEE,
+ GO_GET_MINERALS,
+ IMPROVE_FLAGSHIP_WITH_RU,
+ GOT_OK_FLAGSHIP,
+ GO_ALLY_WITH_ALIENS,
+ MADE_SOME_ALLIES,
+ GET_SHIPS_BY_MINING_OR_ALLIANCE,
+ GOT_OK_FLEET,
+ BUY_COMBAT_SHIPS,
+ GO_LEARN_ABOUT_URQUAN,
+ MAKE_FLAGSHIP_AWESOME,
+ KNOW_ABOUT_SAMATRA,
+ GOT_AWESOME_FLAGSHIP,
+ GOT_BOMB,
+ FIND_WAY_TO_DESTROY_SAMATRA,
+ MUST_INCREASE_BOMB_STRENGTH,
+ MUST_ACQUIRE_AWESOME_FLEET,
+ MUST_ELIMINATE_URQUAN_GUARDS,
+ CHMMR_IMPROVED_BOMB,
+ GOT_AWESOME_FLEET,
+ GO_DESTROY_SAMATRA,
+ GOOD_LUCK_AGAIN,
+ IMPROVE_1,
+ IMPROVE_2,
+ NEED_THRUSTERS_1,
+ NEED_THRUSTERS_2,
+ NEED_TURN_1,
+ NEED_TURN_2,
+ NEED_GUNS_1,
+ NEED_GUNS_2,
+ NEED_CREW_1,
+ NEED_CREW_2,
+ NEED_FUEL_1,
+ NEED_FUEL_2,
+ NEED_STORAGE_1,
+ NEED_LANDERS_2,
+ NEED_LANDERS_1,
+ NEED_DYNAMOS_1,
+ NEED_DYNAMOS_2,
+ NEED_POINT,
+
+ have_minerals,
+ goodbye_commander,
+ repeat_bulletins,
+ need_info,
+ starbase_functions,
+ history,
+ our_mission,
+ no_need_info,
+ enough_starbase,
+ enough_mission,
+ tell_me_about_fuel0,
+ tell_me_about_fuel1,
+ tell_me_about_modules0,
+ tell_me_about_modules1,
+ tell_me_about_crew,
+ tell_me_about_ships,
+ tell_me_about_ru,
+ tell_me_about_minerals,
+ tell_me_about_life,
+ where_get_minerals,
+ what_about_aliens,
+ what_about_urquan,
+ how_defeat,
+ how_find_urquan,
+ how_fight_urquan,
+ how_ally,
+ enough_defeat,
+ alien_races,
+ the_war,
+ ancient_history,
+ enough_history,
+ what_about_alliance,
+ what_about_hierarchy,
+ what_about_other,
+ enough_aliens,
+ shofixti,
+ yehat,
+ arilou,
+ chenjesu,
+ mmrnmhrm,
+ syreen,
+ enough_alliance,
+ urquan,
+ mycon,
+ spathi,
+ umgah,
+ androsynth,
+ vux,
+ ilwrath,
+ enough_hierarchy,
+ precursors,
+ old_races,
+ aliens_on_earth,
+ enough_ancient,
+ what_started_war,
+ what_was_war_like,
+ why_lose_war,
+ what_after_war,
+ enough_war,
+ new_devices,
+ how_get_strong,
+ what_do_now,
+ YOUR_FLAGSHIP_PC,
+ YOUR_FLAGSHIP_3DO0,
+ YOUR_FLAGSHIP_3DO1,
+ YOUR_FLAGSHIP_3DO2,
+ SPACE,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/supox/Makeinfo b/src/uqm/comm/supox/Makeinfo
new file mode 100644
index 0000000..8745013
--- /dev/null
+++ b/src/uqm/comm/supox/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="supoxc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/supox/resinst.h b/src/uqm/comm/supox/resinst.h
new file mode 100644
index 0000000..03459ea
--- /dev/null
+++ b/src/uqm/comm/supox/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SUPOX_COLOR_MAP "comm.supox.colortable"
+#define SUPOX_CONVERSATION_PHRASES "comm.supox.dialogue"
+#define SUPOX_FONT "comm.supox.font"
+#define SUPOX_MUSIC "comm.supox.music"
+#define SUPOX_PMAP_ANIM "comm.supox.graphics"
diff --git a/src/uqm/comm/supox/strings.h b/src/uqm/comm/supox/strings.h
new file mode 100644
index 0000000..b3312f7
--- /dev/null
+++ b/src/uqm/comm/supox/strings.h
@@ -0,0 +1,124 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SUPOX_STRINGS_H
+#define SUPOX_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ NEUTRAL_SPACE_HELLO_1,
+ NEUTRAL_SPACE_HELLO_2,
+ NEUTRAL_HOMEWORLD_HELLO_1,
+ NEUTRAL_HOMEWORLD_HELLO_2,
+ HOSTILE_SPACE_HELLO_1,
+ HOSTILE_SPACE_HELLO_2,
+ ALLIED_HOMEWORLD_HELLO_1,
+ ALLIED_HOMEWORLD_HELLO_2,
+ ALLIED_HOMEWORLD_HELLO_3,
+ ALLIED_HOMEWORLD_HELLO_4,
+ i_am0,
+ i_am1,
+ WE_ARE_SUPOX,
+ my_ship0,
+ my_ship1,
+ OUR_SHIP,
+ from_alliance0,
+ from_alliance1,
+ FROM_SUPOX,
+ are_you_copying,
+ YEAH_SORRY,
+ why_copy,
+ SYMBIOTS,
+ tell_us_of_your_species,
+ OUR_SPECIES,
+ plants_arent_intelligent,
+ PROVES_WERE_SPECIAL,
+ anyone_around_here,
+ UTWIG_NEARBY,
+ what_relation_to_utwig,
+ UTWIG_ALLIES,
+ whats_wrong_with_utwig,
+ BROKE_ULTRON,
+ whats_ultron,
+ TAKE_ULTRON,
+ what_do_i_do_now,
+ FIX_IT,
+ thanks_now_we_eat_you,
+ HIDEOUS_MONSTERS,
+ got_fixed_ultron,
+ GOOD_GIVE_TO_UTWIG,
+ look_i_repaired_lots,
+ ALMOST_THERE,
+ look_i_slightly_repaired,
+ GREAT_DO_MORE,
+ where_get_repairs,
+ ANCIENT_RHYME,
+ bye_neutral,
+ GOODBYE_NEUTRAL,
+ ABOUT_BATTLE,
+ HELLO_BEFORE_KOHRAH_SPACE_1,
+ HELLO_BEFORE_KOHRAH_SPACE_2,
+ HELLO_DURING_KOHRAH_SPACE_1,
+ HELLO_DURING_KOHRAH_SPACE_2,
+ HELLO_AFTER_KOHRAH_SPACE_1,
+ HELLO_AFTER_KOHRAH_SPACE_2,
+ whats_up_after_space,
+ GENERAL_INFO_AFTER_SPACE_1,
+ GENERAL_INFO_AFTER_SPACE_2,
+ what_now_after_space,
+ DO_THIS_AFTER_SPACE,
+ bye_after_space,
+ GOODBYE_AFTER_SPACE,
+ whats_up_before_space,
+ GENERAL_INFO_BEFORE_SPACE_1,
+ GENERAL_INFO_BEFORE_SPACE_2,
+ what_now_before_space,
+ DO_THIS_BEFORE_SPACE,
+ bye_before_space,
+ GOODBYE_BEFORE_SPACE,
+ how_went_war,
+ how_goes_war,
+ BATTLE_HAPPENS_1,
+ BATTLE_HAPPENS_2,
+ FLEET_ON_WAY,
+ learn_new_info,
+ NO_NEW_INFO,
+ SAMATRA,
+ what_now_homeworld,
+ HOPE_KILL_EACH_OTHER,
+ UP_TO_YOU,
+ can_you_help,
+ HOW_HELP,
+ DONT_NEED,
+ HAVE_4_SHIPS,
+ give_info,
+ GOOD_HINTS,
+ how_is_ultron,
+ ULTRON_IS_GREAT,
+ bye_allied_homeworld,
+ GOODBYE_ALLIED_HOMEWORLD,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/supox/supoxc.c b/src/uqm/comm/supox/supoxc.c
new file mode 100644
index 0000000..e169cba
--- /dev/null
+++ b/src/uqm/comm/supox/supoxc.c
@@ -0,0 +1,708 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+
+
+static LOCDATA supox_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ SUPOX_PMAP_ANIM, /* AlienFrame */
+ SUPOX_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ SUPOX_COLOR_MAP, /* AlienColorMap */
+ SUPOX_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ SUPOX_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 4, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 4, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 10, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 9, /* StartIndex */
+ 10, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 19, /* StartIndex */
+ 10, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 29, /* StartIndex */
+ 13, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (R, bye_neutral))
+ NPCPhrase (GOODBYE_NEUTRAL);
+ else if (PLAYER_SAID (R, what_do_i_do_now))
+ NPCPhrase (FIX_IT);
+ else if (PLAYER_SAID (R, thanks_now_we_eat_you))
+ {
+ NPCPhrase (HIDEOUS_MONSTERS);
+
+ SET_GAME_STATE (SUPOX_HOSTILE, 1);
+ SET_GAME_STATE (SUPOX_HOME_VISITS, 0);
+ SET_GAME_STATE (SUPOX_VISITS, 0);
+ }
+ else if (PLAYER_SAID (R, bye_after_space))
+ NPCPhrase (GOODBYE_AFTER_SPACE);
+ else if (PLAYER_SAID (R, bye_before_space))
+ NPCPhrase (GOODBYE_BEFORE_SPACE);
+ else if (PLAYER_SAID (R, bye_allied_homeworld))
+ NPCPhrase (GOODBYE_ALLIED_HOMEWORLD);
+ else if (PLAYER_SAID (R, can_you_help))
+ {
+ NPCPhrase (HOW_HELP);
+ if (EscortFeasibilityStudy (SUPOX_SHIP) == 0)
+ NPCPhrase (DONT_NEED);
+ else
+ {
+ NPCPhrase (HAVE_4_SHIPS);
+
+ AlienTalkSegue ((COUNT)~0);
+ AddEscortShips (SUPOX_SHIP, 4);
+ }
+ }
+}
+
+static void AlliedHome (RESPONSE_REF R);
+
+static void
+AlliedHome (RESPONSE_REF R)
+{
+ BYTE NumVisits, News;
+
+ News = GET_GAME_STATE (SUPOX_WAR_NEWS);
+ NumVisits = GET_GAME_STATE (UTWIG_SUPOX_MISSION);
+ if (PLAYER_SAID (R, how_went_war))
+ {
+ NPCPhrase (ABOUT_BATTLE);
+
+ News |= (1 << 0);
+ }
+ else if (PLAYER_SAID (R, how_goes_war))
+ {
+ if (NumVisits == 1)
+ {
+ NPCPhrase (FLEET_ON_WAY);
+
+ SET_GAME_STATE (SUPOX_WAR_NEWS, 1);
+ }
+ else switch (GET_GAME_STATE (SUPOX_WAR_NEWS))
+ {
+ case 0:
+ NPCPhrase (BATTLE_HAPPENS_1);
+ News = 1;
+ break;
+ case 1:
+ NPCPhrase (BATTLE_HAPPENS_2);
+ News = 2;
+ break;
+ }
+
+ DISABLE_PHRASE (how_goes_war);
+ }
+ else if (PLAYER_SAID (R, learn_new_info))
+ {
+ if (NumVisits < 5)
+ NPCPhrase (NO_NEW_INFO);
+ else
+ {
+ NPCPhrase (SAMATRA);
+
+ News |= (1 << 1);
+ }
+
+ DISABLE_PHRASE (learn_new_info);
+ }
+ else if (PLAYER_SAID (R, what_now_homeworld))
+ {
+ if (NumVisits < 5)
+ NPCPhrase (UP_TO_YOU);
+ else
+ NPCPhrase (HOPE_KILL_EACH_OTHER);
+
+ DISABLE_PHRASE (what_now_homeworld);
+ }
+ else if (PLAYER_SAID (R, how_is_ultron))
+ {
+ NPCPhrase (ULTRON_IS_GREAT);
+
+ DISABLE_PHRASE (how_is_ultron);
+ }
+ SET_GAME_STATE (SUPOX_WAR_NEWS, News);
+
+ if (NumVisits >= 5)
+ {
+ if (!(News & (1 << 0)))
+ Response (how_went_war, AlliedHome);
+ }
+ else if (PHRASE_ENABLED (how_goes_war)
+ && ((NumVisits == 1 && News == 0)
+ || (NumVisits && News < 2)))
+ Response (how_goes_war, AlliedHome);
+ if (PHRASE_ENABLED (learn_new_info))
+ Response (learn_new_info, AlliedHome);
+ if (PHRASE_ENABLED (what_now_homeworld))
+ Response (what_now_homeworld, AlliedHome);
+ if (PHRASE_ENABLED (how_is_ultron))
+ Response (how_is_ultron, AlliedHome);
+ if (NumVisits == 0)
+ Response (can_you_help, ExitConversation);
+ Response (bye_allied_homeworld, ExitConversation);
+}
+
+static void
+BeforeKohrAh (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, whats_up_before_space))
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_BEFORE_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_BEFORE_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_before_space);
+ }
+ else if (PLAYER_SAID (R, what_now_before_space))
+ {
+ NPCPhrase (DO_THIS_BEFORE_SPACE);
+
+ DISABLE_PHRASE (what_now_before_space);
+ }
+
+ if (PHRASE_ENABLED (whats_up_before_space))
+ Response (whats_up_before_space, BeforeKohrAh);
+ if (PHRASE_ENABLED (what_now_before_space))
+ Response (what_now_before_space, BeforeKohrAh);
+ Response (bye_before_space, ExitConversation);
+}
+
+static void
+AfterKohrAh (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, whats_up_after_space))
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_AFTER_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_AFTER_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_after_space);
+ }
+ else if (PLAYER_SAID (R, what_now_after_space))
+ {
+ NPCPhrase (DO_THIS_AFTER_SPACE);
+
+ DISABLE_PHRASE (what_now_after_space);
+ }
+
+ if (PHRASE_ENABLED (whats_up_after_space))
+ Response (whats_up_after_space, AfterKohrAh);
+ if (PHRASE_ENABLED (what_now_after_space))
+ Response (what_now_after_space, AfterKohrAh);
+ Response (bye_after_space, ExitConversation);
+}
+
+static void
+NeutralSupox (RESPONSE_REF R)
+{
+ BYTE i, LastStack, NumVisits;
+ RESPONSE_REF pStr[3];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = 0;
+ if (PLAYER_SAID (R, i_am0))
+ {
+ NPCPhrase (WE_ARE_SUPOX);
+
+ SET_GAME_STATE (SUPOX_STACK1, 1);
+ DISABLE_PHRASE (i_am0);
+ }
+ else if (PLAYER_SAID (R, my_ship0))
+ {
+ NPCPhrase (OUR_SHIP);
+
+ SET_GAME_STATE (SUPOX_STACK1, 2);
+ DISABLE_PHRASE (my_ship0);
+ }
+ else if (PLAYER_SAID (R, from_alliance0))
+ {
+ NPCPhrase (FROM_SUPOX);
+
+ SET_GAME_STATE (SUPOX_STACK1, 3);
+ DISABLE_PHRASE (from_alliance0);
+ }
+ else if (PLAYER_SAID (R, are_you_copying))
+ {
+ NPCPhrase (YEAH_SORRY);
+
+ SET_GAME_STATE (SUPOX_STACK1, 4);
+ }
+ else if (PLAYER_SAID (R, why_copy))
+ {
+ NPCPhrase (SYMBIOTS);
+
+ SET_GAME_STATE (SUPOX_STACK1, 5);
+ }
+ else if (PLAYER_SAID (R, tell_us_of_your_species))
+ {
+ NPCPhrase (OUR_SPECIES);
+
+ LastStack = 1;
+ SET_GAME_STATE (SUPOX_STACK2, 1);
+ }
+ else if (PLAYER_SAID (R, plants_arent_intelligent))
+ {
+ NPCPhrase (PROVES_WERE_SPECIAL);
+
+ SET_GAME_STATE (SUPOX_STACK2, 2);
+ }
+ else if (PLAYER_SAID (R, anyone_around_here))
+ {
+ NPCPhrase (UTWIG_NEARBY);
+
+ LastStack = 2;
+ SET_GAME_STATE (SUPOX_WAR_NEWS, 1);
+ StartSphereTracking (UTWIG_SHIP);
+ }
+ else if (PLAYER_SAID (R, what_relation_to_utwig))
+ {
+ NPCPhrase (UTWIG_ALLIES);
+
+ LastStack = 2;
+ SET_GAME_STATE (SUPOX_WAR_NEWS, 1);
+ }
+ else if (PLAYER_SAID (R, whats_wrong_with_utwig))
+ {
+ NPCPhrase (BROKE_ULTRON);
+
+ LastStack = 2;
+ SET_GAME_STATE (SUPOX_WAR_NEWS, 2);
+ }
+ else if (PLAYER_SAID (R, whats_ultron))
+ {
+ NPCPhrase (TAKE_ULTRON);
+
+ SET_GAME_STATE (SUPOX_WAR_NEWS, 0);
+ SET_GAME_STATE (ULTRON_CONDITION, 1);
+
+ Response (what_do_i_do_now, ExitConversation);
+ Response (thanks_now_we_eat_you, ExitConversation);
+
+ return;
+ }
+ else if (PLAYER_SAID (R, got_fixed_ultron))
+ {
+ NPCPhrase (GOOD_GIVE_TO_UTWIG);
+
+ SET_GAME_STATE (SUPOX_ULTRON_HELP, 1);
+ }
+ else if (PLAYER_SAID (R, look_i_repaired_lots))
+ {
+ NPCPhrase (ALMOST_THERE);
+
+ SET_GAME_STATE (SUPOX_ULTRON_HELP, 1);
+ }
+ else if (PLAYER_SAID (R, look_i_slightly_repaired))
+ {
+ NPCPhrase (GREAT_DO_MORE);
+
+ SET_GAME_STATE (SUPOX_ULTRON_HELP, 1);
+ }
+ else if (PLAYER_SAID (R, where_get_repairs))
+ {
+ NPCPhrase (ANCIENT_RHYME);
+
+ SET_GAME_STATE (SUPOX_ULTRON_HELP, 1);
+ }
+
+ switch (GET_GAME_STATE (SUPOX_STACK2))
+ {
+ case 0:
+ pStr[1] = tell_us_of_your_species;
+ break;
+ case 1:
+ pStr[1] = plants_arent_intelligent;
+ break;
+ }
+ switch (GET_GAME_STATE (SUPOX_STACK1))
+ {
+ case 0:
+ construct_response (shared_phrase_buf,
+ i_am0,
+ GLOBAL_SIS (CommanderName),
+ i_am1,
+ (UNICODE*)NULL);
+ pStr[0] = i_am0;
+ pStr[1] = 0;
+ break;
+ case 1:
+ construct_response (shared_phrase_buf,
+ my_ship0,
+ GLOBAL_SIS (ShipName),
+ my_ship1,
+ (UNICODE*)NULL);
+ pStr[0] = my_ship0;
+ pStr[1] = 0;
+ break;
+ case 2:
+ {
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ from_alliance0,
+ buf,
+ from_alliance1,
+ (UNICODE*)NULL);
+ }
+ pStr[0] = from_alliance0;
+ pStr[1] = 0;
+ break;
+ case 3:
+ pStr[0] = are_you_copying;
+ pStr[1] = 0;
+ break;
+ case 4:
+ pStr[0] = why_copy;
+ pStr[1] = 0;
+ break;
+ }
+ NumVisits = GET_GAME_STATE (ULTRON_CONDITION);
+ if (NumVisits == 0)
+ {
+ switch (GET_GAME_STATE (SUPOX_WAR_NEWS))
+ {
+ case 0:
+ if (GET_GAME_STATE (UTWIG_VISITS)
+ || GET_GAME_STATE (UTWIG_HOME_VISITS)
+ || GET_GAME_STATE (BOMB_VISITS))
+ pStr[2] = what_relation_to_utwig;
+ else
+ pStr[2] = anyone_around_here;
+ break;
+ case 1:
+ pStr[2] = whats_wrong_with_utwig;
+ break;
+ case 2:
+ pStr[2] = whats_ultron;
+ break;
+ }
+ }
+ if (pStr[LastStack])
+ {
+ if (LastStack != 0 || GET_GAME_STATE (SUPOX_STACK1) > 2)
+ Response (pStr[LastStack], NeutralSupox);
+ else
+ DoResponsePhrase (pStr[LastStack], NeutralSupox, shared_phrase_buf);
+ }
+ for (i = 0; i < 3; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ {
+ if (i != 0 || GET_GAME_STATE (SUPOX_STACK1) > 2)
+ Response (pStr[i], NeutralSupox);
+ else
+ DoResponsePhrase (pStr[i], NeutralSupox, shared_phrase_buf);
+ }
+ }
+ if (!GET_GAME_STATE (SUPOX_ULTRON_HELP))
+ {
+ switch (NumVisits)
+ {
+ case 1:
+ Response (where_get_repairs, NeutralSupox);
+ break;
+ case 2:
+ Response (look_i_slightly_repaired, NeutralSupox);
+ break;
+ case 3:
+ Response (look_i_repaired_lots, NeutralSupox);
+ break;
+ case 4:
+ Response (got_fixed_ultron, NeutralSupox);
+ break;
+ }
+ }
+ Response (bye_neutral, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (GET_GAME_STATE (SUPOX_HOSTILE))
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_VISITS, NumVisits);
+
+ setSegue (Segue_peace);
+ }
+ else if (CheckAlliance (SUPOX_SHIP) == GOOD_GUY)
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_HOME_VISITS, NumVisits);
+
+ AlliedHome ((RESPONSE_REF)0);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_SUPOX_MISSION);
+ if (NumVisits == 1)
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_BEFORE_KOHRAH_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_BEFORE_KOHRAH_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_VISITS, NumVisits);
+
+ BeforeKohrAh ((RESPONSE_REF)0);
+ }
+ else if (NumVisits < 5)
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_DURING_KOHRAH_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_DURING_KOHRAH_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_AFTER_KOHRAH_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_AFTER_KOHRAH_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_VISITS, NumVisits);
+
+ AfterKohrAh ((RESPONSE_REF)0);
+ }
+ }
+ }
+ else
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (SUPOX_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SUPOX_VISITS, NumVisits);
+ }
+
+ NeutralSupox ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_supox (void)
+{
+ return (0);
+}
+
+static void
+post_supox_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_supox_comm (void)
+{
+ LOCDATA *retval;
+
+ supox_desc.init_encounter_func = Intro;
+ supox_desc.post_encounter_func = post_supox_enc;
+ supox_desc.uninit_encounter_func = uninit_supox;
+
+ supox_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ supox_desc.AlienTextBaseline.y = 0;
+ supox_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (!GET_GAME_STATE (SUPOX_HOSTILE)
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &supox_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/syreen/Makeinfo b/src/uqm/comm/syreen/Makeinfo
new file mode 100644
index 0000000..e2a265e
--- /dev/null
+++ b/src/uqm/comm/syreen/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="syreenc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/syreen/resinst.h b/src/uqm/comm/syreen/resinst.h
new file mode 100644
index 0000000..16f7b0b
--- /dev/null
+++ b/src/uqm/comm/syreen/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SYREEN_COLOR_MAP "comm.syreen.colortable"
+#define SYREEN_CONVERSATION_PHRASES "comm.syreen.dialogue"
+#define SYREEN_FONT "comm.syreen.font"
+#define SYREEN_MUSIC "comm.syreen.music"
+#define SYREEN_PMAP_ANIM "comm.syreen.graphics"
diff --git a/src/uqm/comm/syreen/strings.h b/src/uqm/comm/syreen/strings.h
new file mode 100644
index 0000000..b796e3d
--- /dev/null
+++ b/src/uqm/comm/syreen/strings.h
@@ -0,0 +1,158 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SYREEN_STRINGS_H
+#define SYREEN_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ HELLO_BEFORE_AMBUSH_1,
+ HELLO_BEFORE_AMBUSH_2,
+ HELLO_BEFORE_AMBUSH_3,
+ HELLO_BEFORE_AMBUSH_4,
+ we_are_vice_squad,
+ OK_VICE,
+ we_are_the_one_for_you_baby,
+ MAYBE_CAPTAIN,
+ we_are_vindicator0,
+ we_are_vindicator1,
+ we_are_vindicator2,
+ WELCOME_VINDICATOR0,
+ WELCOME_VINDICATOR1,
+ WELCOME_VINDICATOR2,
+ we_are_impressed,
+ SO_AM_I_CAPTAIN,
+ HOW_CAN_YOU_BE_HERE,
+ we_here_to_help,
+ NO_NEED_HELP,
+ we_need_help,
+ CANT_GIVE_HELP,
+ i_need_you,
+ OK_NEED,
+ i_need_touch_o_vision,
+ TOUCH_O_VISION,
+ know_about_deep_children,
+ WHAT_ABOUT_DEEP_CHILDREN,
+ mycons_involved,
+ WHAT_PROOF,
+ have_no_proof,
+ NEED_PROOF,
+ have_proof,
+ SEE_PROOF,
+ look_at_egg_sacks,
+ HORRIBLE_TRUTH,
+ what_doing_here,
+ OUR_NEW_WORLD,
+ what_about_war,
+ ABOUT_WAR,
+ help_us,
+ WONT_HELP,
+ what_about_history,
+ BEFORE_WAR,
+ what_about_homeworld,
+ ABOUT_HOMEWORLD,
+ what_happened,
+ DONT_KNOW_HOW,
+ what_about_outfit,
+ HOPE_YOU_LIKE_IT,
+ where_mates,
+ MATES_KILLED,
+ get_lonely,
+ MAKE_OUT_ALL_RIGHT,
+ bye,
+ GOODBYE,
+ MUST_ACT,
+ whats_next_step,
+ OPEN_VAULT,
+ where_is_it,
+ DONT_KNOW_WHERE,
+ been_there,
+ GREAT,
+ GIVE_SHUTTLE,
+ im_on_my_way,
+ doing_this_for_you,
+ if_i_die,
+ GOOD_LUCK,
+ OK_FOUND_VAULT,
+ what_now,
+ HERES_THE_PLAN,
+ whats_my_reward,
+ HERES_REWARD,
+ bye_after_vault,
+ GOODBYE_AFTER_VAULT,
+ HELLO_AFTER_AMBUSH_1,
+ HELLO_AFTER_AMBUSH_2,
+ HELLO_AFTER_AMBUSH_3,
+ HELLO_AFTER_AMBUSH_4,
+ what_now_after_ambush,
+ DO_THIS_AFTER_AMBUSH,
+ what_about_you,
+ ABOUT_ME,
+ whats_up_after_ambush,
+ GENERAL_INFO_AFTER_AMBUSH_1,
+ GENERAL_INFO_AFTER_AMBUSH_2,
+ GENERAL_INFO_AFTER_AMBUSH_3,
+ GENERAL_INFO_AFTER_AMBUSH_4,
+ bye_after_ambush,
+ GOODBYE_AFTER_AMBUSH,
+ FOUND_VAULT_YET_1,
+ FOUND_VAULT_YET_2,
+ vault_hint,
+ OK_HINT,
+ found_vault,
+ bye_before_vault,
+ GOODBYE_BEFORE_VAULT,
+ what_do_i_get_for_this,
+ GRATITUDE,
+ not_sure,
+ PLEASE,
+ READY_FOR_AMBUSH,
+ repeat_plan,
+ OK_REPEAT_PLAN,
+ bye_before_ambush,
+ GOODBYE_BEFORE_AMBUSH,
+ what_about_us,
+ ABOUT_US,
+ MORE_COMFORTABLE,
+ in_the_spirit,
+ OK_SPIRIT,
+ what_in_mind,
+ SOMETHING_LIKE_THIS,
+ hands_off,
+ OK_WONT_USE_HANDS,
+ why_lights_off,
+ LIGHTS_OFF_BECAUSE,
+ evil_monster,
+ NOT_EVIL_MONSTER,
+ disease,
+ JUST_RELAX,
+ what_happens_if_i_touch_this,
+ THIS_HAPPENS,
+ are_you_sure_this_is_ok,
+ YES_SURE,
+ boy_they_never_taught,
+ THEN_LET_ME_TEACH,
+ not_much_more_to_say,
+ THEN_STOP_TALKING,
+ LATER,
+ SEX_GOODBYE,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/syreen/syreenc.c b/src/uqm/comm/syreen/syreenc.c
new file mode 100644
index 0000000..8884ad6
--- /dev/null
+++ b/src/uqm/comm/syreen/syreenc.c
@@ -0,0 +1,878 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/setup.h"
+
+
+static LOCDATA syreen_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ SYREEN_PMAP_ANIM, /* AlienFrame */
+ SYREEN_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ SYREEN_COLOR_MAP, /* AlienColorMap */
+ SYREEN_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ SYREEN_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 15, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 5, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 7, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 9, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 11, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 13, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 15, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 12), /* BlockMask */
+ },
+ {
+ 17, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 19, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 13),
+ },
+ {
+ 21, /* StartIndex */
+ 6, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 27, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 14), /* BlockMask */
+ },
+ {
+ 31, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 37, /* StartIndex */
+ 4, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 41, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5), /* BlockMask */
+ },
+ {
+ 44, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 6, 0, /* FrameRate */
+ ONE_SECOND * 3, ONE_SECOND, /* RestartRate */
+ (1 << 7) | (1 << 14), /* BlockMask */
+ },
+ {
+ 48, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND,/* RestartRate */
+ (1 << 9) | (1 << 13), /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 4, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+FriendlyExit (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (R, bye))
+ NPCPhrase (GOODBYE);
+ else if (PLAYER_SAID (R, im_on_my_way)
+ || PLAYER_SAID (R, doing_this_for_you)
+ || PLAYER_SAID (R, if_i_die))
+ NPCPhrase (GOOD_LUCK);
+ else if (PLAYER_SAID (R, bye_before_vault))
+ NPCPhrase (GOODBYE_BEFORE_VAULT);
+ else if (PLAYER_SAID (R, bye_after_vault))
+ NPCPhrase (GOODBYE_AFTER_VAULT);
+ else if (PLAYER_SAID (R, bye_before_ambush))
+ NPCPhrase (GOODBYE_BEFORE_AMBUSH);
+ else if (PLAYER_SAID (R, bye_after_ambush))
+ NPCPhrase (GOODBYE_AFTER_AMBUSH);
+ else
+ {
+ if (PLAYER_SAID (R, hands_off))
+ NPCPhrase (OK_WONT_USE_HANDS);
+ else if (PLAYER_SAID (R, not_much_more_to_say))
+ NPCPhrase (THEN_STOP_TALKING);
+ NPCPhrase (LATER);
+ NPCPhrase (SEX_GOODBYE);
+
+ AlienTalkSegue (2);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 0)
+ ), ONE_SECOND / 2);
+ AlienTalkSegue ((COUNT)~0);
+
+ SET_GAME_STATE (PLAYER_HAD_SEX, 1);
+ SET_GAME_STATE (PLAYER_HAVING_SEX, 0);
+ }
+}
+
+static void
+Sex (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, in_the_spirit))
+ NPCPhrase (OK_SPIRIT);
+ else if (PLAYER_SAID (R, what_in_mind))
+ NPCPhrase (SOMETHING_LIKE_THIS);
+ else if (PLAYER_SAID (R, disease))
+ NPCPhrase (JUST_RELAX);
+ else if (PLAYER_SAID (R, what_happens_if_i_touch_this))
+ {
+ NPCPhrase (THIS_HAPPENS);
+
+ DISABLE_PHRASE (what_happens_if_i_touch_this);
+ }
+ else if (PLAYER_SAID (R, are_you_sure_this_is_ok))
+ {
+ NPCPhrase (YES_SURE);
+
+ DISABLE_PHRASE (are_you_sure_this_is_ok);
+ }
+ else if (PLAYER_SAID (R, boy_they_never_taught))
+ {
+ NPCPhrase (THEN_LET_ME_TEACH);
+
+ DISABLE_PHRASE (boy_they_never_taught);
+ }
+
+ if (!PHRASE_ENABLED (what_happens_if_i_touch_this)
+ && !PHRASE_ENABLED (are_you_sure_this_is_ok)
+ && !PHRASE_ENABLED (boy_they_never_taught))
+ Response (not_much_more_to_say, FriendlyExit);
+ else
+ {
+ if (PHRASE_ENABLED (what_happens_if_i_touch_this))
+ Response (what_happens_if_i_touch_this, Sex);
+ if (PHRASE_ENABLED (are_you_sure_this_is_ok))
+ Response (are_you_sure_this_is_ok, Sex);
+ if (PHRASE_ENABLED (boy_they_never_taught))
+ Response (boy_they_never_taught, Sex);
+ }
+}
+
+static void
+Foreplay (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_my_reward)
+ || PLAYER_SAID (R, what_about_us))
+ {
+ if (PLAYER_SAID (R, whats_my_reward))
+ NPCPhrase (HERES_REWARD);
+ else
+ NPCPhrase (ABOUT_US);
+ NPCPhrase (MORE_COMFORTABLE);
+ AlienTalkSegue (1);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1)
+ ), ONE_SECOND);
+ AlienTalkSegue ((COUNT)~0);
+
+ SET_GAME_STATE (PLAYER_HAVING_SEX, 1);
+ }
+ else if (PLAYER_SAID (R, why_lights_off))
+ {
+ NPCPhrase (LIGHTS_OFF_BECAUSE);
+
+ DISABLE_PHRASE (why_lights_off);
+ }
+ else if (PLAYER_SAID (R, evil_monster))
+ {
+ NPCPhrase (NOT_EVIL_MONSTER);
+
+ DISABLE_PHRASE (evil_monster);
+ }
+
+ if (PHRASE_ENABLED (why_lights_off))
+ Response (why_lights_off, Foreplay);
+ else if (PHRASE_ENABLED (evil_monster))
+ Response (evil_monster, Foreplay);
+ else
+ Response (disease, Sex);
+ Response (in_the_spirit, Sex);
+ Response (what_in_mind, Sex);
+ Response (hands_off, FriendlyExit);
+}
+
+static void
+AfterAmbush (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, what_now_after_ambush))
+ {
+ NPCPhrase (DO_THIS_AFTER_AMBUSH);
+
+ DISABLE_PHRASE (what_now_after_ambush);
+ }
+ else if (PLAYER_SAID (R, what_about_you))
+ {
+ NPCPhrase (ABOUT_ME);
+
+ DISABLE_PHRASE (what_about_you);
+ }
+ else if (PLAYER_SAID (R, whats_up_after_ambush))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (SYREEN_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_AFTER_AMBUSH_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_AFTER_AMBUSH_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_AFTER_AMBUSH_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_AFTER_AMBUSH_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (SYREEN_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_after_ambush);
+ }
+
+ if (PHRASE_ENABLED (what_about_you))
+ Response (what_about_you, AfterAmbush);
+ else if (!GET_GAME_STATE (PLAYER_HAD_SEX))
+ {
+ Response (what_about_us, Foreplay);
+ }
+ if (PHRASE_ENABLED (what_now_after_ambush))
+ Response (what_now_after_ambush, AfterAmbush);
+ if (PHRASE_ENABLED (whats_up_after_ambush))
+ Response (whats_up_after_ambush, AfterAmbush);
+ Response (bye_after_ambush, FriendlyExit);
+}
+
+static void
+AmbushReady (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, repeat_plan))
+ {
+ NPCPhrase (OK_REPEAT_PLAN);
+
+ DISABLE_PHRASE (repeat_plan);
+ }
+
+ if (PHRASE_ENABLED (repeat_plan))
+ Response (repeat_plan, AmbushReady);
+ Response (bye_before_ambush, FriendlyExit);
+}
+
+static void
+SyreenShuttle (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_next_step))
+ {
+ NPCPhrase (OPEN_VAULT);
+
+ DISABLE_PHRASE (whats_next_step);
+ }
+ else if (PLAYER_SAID (R, what_do_i_get_for_this))
+ {
+ NPCPhrase (GRATITUDE);
+
+ DISABLE_PHRASE (what_do_i_get_for_this);
+ }
+ else if (PLAYER_SAID (R, not_sure))
+ {
+ NPCPhrase (PLEASE);
+
+ DISABLE_PHRASE (not_sure);
+ }
+ else if (PLAYER_SAID (R, where_is_it))
+ {
+ NPCPhrase (DONT_KNOW_WHERE);
+ NPCPhrase (GIVE_SHUTTLE);
+
+ SET_GAME_STATE (SYREEN_SHUTTLE, 1);
+ SET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP, 1);
+
+ DISABLE_PHRASE (where_is_it);
+ }
+ else if (PLAYER_SAID (R, been_there))
+ {
+ NPCPhrase (GREAT);
+ NPCPhrase (GIVE_SHUTTLE);
+
+ SET_GAME_STATE (SYREEN_SHUTTLE, 1);
+ SET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP, 1);
+
+ DISABLE_PHRASE (been_there);
+ }
+
+ if (PHRASE_ENABLED (whats_next_step))
+ Response (whats_next_step, SyreenShuttle);
+ else
+ {
+ if (!GET_GAME_STATE (KNOW_SYREEN_VAULT))
+ {
+ if (PHRASE_ENABLED (where_is_it))
+ Response (where_is_it, SyreenShuttle);
+ }
+ else
+ {
+ if (PHRASE_ENABLED (been_there))
+ Response (been_there, SyreenShuttle);
+ }
+ if (!PHRASE_ENABLED (where_is_it)
+ || !PHRASE_ENABLED (been_there))
+ {
+ Response (im_on_my_way, FriendlyExit);
+ Response (doing_this_for_you, FriendlyExit);
+ Response (if_i_die, FriendlyExit);
+ }
+ }
+ if (PHRASE_ENABLED (what_do_i_get_for_this))
+ Response (what_do_i_get_for_this, SyreenShuttle);
+ if (PHRASE_ENABLED (not_sure))
+ Response (not_sure, SyreenShuttle);
+}
+
+static void
+NormalSyreen (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[4];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = pStr[3] = 0;
+ if (PLAYER_SAID (R, we_here_to_help))
+ NPCPhrase (NO_NEED_HELP);
+ else if (PLAYER_SAID (R, we_need_help))
+ NPCPhrase (CANT_GIVE_HELP);
+ else if (PLAYER_SAID (R, know_about_deep_children))
+ {
+ NPCPhrase (WHAT_ABOUT_DEEP_CHILDREN);
+
+ DISABLE_PHRASE (know_about_deep_children);
+ }
+ else if (PLAYER_SAID (R, mycons_involved))
+ {
+ NPCPhrase (WHAT_PROOF);
+
+ SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 3);
+ }
+ else if (PLAYER_SAID (R, have_no_proof))
+ {
+ NPCPhrase (NEED_PROOF);
+
+ SET_GAME_STATE (SYREEN_WANT_PROOF, 1);
+ }
+ else if (PLAYER_SAID (R, have_proof))
+ {
+ NPCPhrase (SEE_PROOF);
+
+ DISABLE_PHRASE (have_proof);
+ }
+ else if (PLAYER_SAID (R, what_doing_here))
+ {
+ NPCPhrase (OUR_NEW_WORLD);
+
+ SET_GAME_STATE (SYREEN_STACK0, 1);
+ LastStack = 1;
+ }
+ else if (PLAYER_SAID (R, what_about_war))
+ {
+ NPCPhrase (ABOUT_WAR);
+
+ SET_GAME_STATE (SYREEN_STACK0, 2);
+ LastStack = 1;
+ }
+ else if (PLAYER_SAID (R, help_us))
+ {
+ NPCPhrase (WONT_HELP);
+
+ SET_GAME_STATE (SYREEN_STACK0, 3);
+ }
+ else if (PLAYER_SAID (R, what_about_history))
+ {
+ NPCPhrase (BEFORE_WAR);
+
+ SET_GAME_STATE (SYREEN_STACK1, 1);
+ LastStack = 2;
+ }
+ else if (PLAYER_SAID (R, what_about_homeworld))
+ {
+ NPCPhrase (ABOUT_HOMEWORLD);
+
+ SET_GAME_STATE (SYREEN_STACK1, 2);
+ LastStack = 2;
+ }
+ else if (PLAYER_SAID (R, what_happened))
+ {
+ NPCPhrase (DONT_KNOW_HOW);
+
+ SET_GAME_STATE (KNOW_SYREEN_WORLD_SHATTERED, 1);
+ SET_GAME_STATE (SYREEN_STACK1, 3);
+ }
+ else if (PLAYER_SAID (R, what_about_outfit))
+ {
+ NPCPhrase (HOPE_YOU_LIKE_IT);
+
+ SET_GAME_STATE (SYREEN_STACK2, 1);
+ LastStack = 3;
+ }
+ else if (PLAYER_SAID (R, where_mates))
+ {
+ NPCPhrase (MATES_KILLED);
+
+ SET_GAME_STATE (SYREEN_STACK2, 2);
+ LastStack = 3;
+ }
+ else if (PLAYER_SAID (R, get_lonely))
+ {
+ NPCPhrase (MAKE_OUT_ALL_RIGHT);
+
+ SET_GAME_STATE (SYREEN_STACK2, 3);
+ }
+ else if (PLAYER_SAID (R, look_at_egg_sacks))
+ {
+ NPCPhrase (HORRIBLE_TRUTH);
+
+ setSegue (Segue_peace);
+ SET_GAME_STATE (SYREEN_HOME_VISITS, 0);
+ SET_GAME_STATE (SYREEN_KNOW_ABOUT_MYCON, 1);
+
+ SyreenShuttle ((RESPONSE_REF)0);
+ return;
+ }
+
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) < 3)
+ {
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) == 2
+ && GET_GAME_STATE (KNOW_SYREEN_WORLD_SHATTERED))
+ {
+ if (PHRASE_ENABLED (know_about_deep_children))
+ pStr[0] = know_about_deep_children;
+ else
+ pStr[0] = mycons_involved;
+ }
+ }
+ else
+ {
+ if (GET_GAME_STATE (EGG_CASE0_ON_SHIP)
+ || GET_GAME_STATE (EGG_CASE1_ON_SHIP)
+ || GET_GAME_STATE (EGG_CASE2_ON_SHIP))
+ {
+ if (PHRASE_ENABLED (have_proof))
+ pStr[0] = have_proof;
+ else
+ pStr[0] = look_at_egg_sacks;
+ }
+ else if (!GET_GAME_STATE (SYREEN_WANT_PROOF))
+ {
+ pStr[0] = have_no_proof;
+ }
+ }
+ switch (GET_GAME_STATE (SYREEN_STACK0))
+ {
+ case 0:
+ pStr[1] = what_doing_here;
+ break;
+ case 1:
+ pStr[1] = what_about_war;
+ break;
+ case 2:
+ pStr[1] = help_us;
+ break;
+ }
+ switch (GET_GAME_STATE (SYREEN_STACK1))
+ {
+ case 0:
+ pStr[2] = what_about_history;
+ break;
+ case 1:
+ pStr[2] = what_about_homeworld;
+ break;
+ case 2:
+ pStr[2] = what_happened;
+ break;
+ }
+ switch (GET_GAME_STATE (SYREEN_STACK2))
+ {
+ case 0:
+ pStr[3] = what_about_outfit;
+ break;
+ case 1:
+ pStr[3] = where_mates;
+ break;
+ case 2:
+ pStr[3] = get_lonely;
+ break;
+ }
+ if (pStr[LastStack])
+ Response (pStr[LastStack], NormalSyreen);
+ for (i = 0; i < 4; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ Response (pStr[i], NormalSyreen);
+ }
+ Response (bye, FriendlyExit);
+}
+
+static void
+InitialSyreen (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, we_are_vice_squad))
+ {
+ NPCPhrase (OK_VICE);
+ NPCPhrase (HOW_CAN_YOU_BE_HERE);
+ }
+ else if (PLAYER_SAID (R, we_are_the_one_for_you_baby))
+ {
+ NPCPhrase (MAYBE_CAPTAIN);
+ NPCPhrase (HOW_CAN_YOU_BE_HERE);
+ }
+ else if (PLAYER_SAID (R, we_are_vindicator0))
+ {
+ NPCPhrase (WELCOME_VINDICATOR0);
+ if (!usingSpeech)
+ {
+ NPCPhrase (GLOBAL_PLAYER_NAME);
+ NPCPhrase (WELCOME_VINDICATOR1);
+ NPCPhrase (GLOBAL_SHIP_NAME);
+ NPCPhrase (WELCOME_VINDICATOR2);
+ }
+ NPCPhrase (HOW_CAN_YOU_BE_HERE);
+ }
+ else if (PLAYER_SAID (R, we_are_impressed))
+ {
+ NPCPhrase (SO_AM_I_CAPTAIN);
+ NPCPhrase (HOW_CAN_YOU_BE_HERE);
+ }
+ else if (PLAYER_SAID (R, i_need_you))
+ {
+ NPCPhrase (OK_NEED);
+
+ DISABLE_PHRASE (i_need_you);
+ DISABLE_PHRASE (i_need_touch_o_vision);
+ }
+ else if (PLAYER_SAID (R, i_need_touch_o_vision))
+ {
+ NPCPhrase (TOUCH_O_VISION);
+
+ DISABLE_PHRASE (i_need_you);
+ DISABLE_PHRASE (i_need_touch_o_vision);
+ }
+
+ Response (we_here_to_help, NormalSyreen);
+ Response (we_need_help, NormalSyreen);
+ if (PHRASE_ENABLED (i_need_you))
+ Response (i_need_you, InitialSyreen);
+ if (PHRASE_ENABLED (i_need_touch_o_vision))
+ Response (i_need_touch_o_vision, InitialSyreen);
+}
+
+static void
+PlanAmbush (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ NPCPhrase (OK_FOUND_VAULT);
+
+ SET_GAME_STATE (MYCON_AMBUSH, 1);
+ // This is redundant but left here for clarity
+ SET_GAME_STATE (SYREEN_HOME_VISITS, 0);
+
+ Response (whats_my_reward, Foreplay);
+ Response (bye_after_vault, FriendlyExit);
+}
+
+static void
+SyreenVault (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, vault_hint))
+ {
+ NPCPhrase (OK_HINT);
+
+ DISABLE_PHRASE (vault_hint);
+ }
+
+ if (PHRASE_ENABLED (vault_hint))
+ {
+ Response (vault_hint, SyreenVault);
+ }
+ Response (bye_before_vault, FriendlyExit);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ NumVisits = GET_GAME_STATE (SYREEN_HOME_VISITS);
+ if (GET_GAME_STATE (MYCON_KNOW_AMBUSH))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_AFTER_AMBUSH_1);
+ SetRaceAllied (SYREEN_SHIP, TRUE);
+ break;
+ case 1:
+ NPCPhrase (HELLO_AFTER_AMBUSH_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_AFTER_AMBUSH_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_AFTER_AMBUSH_3);
+ --NumVisits;
+ break;
+ }
+
+ AfterAmbush ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (MYCON_AMBUSH))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (READY_FOR_AMBUSH);
+ --NumVisits;
+ break;
+ }
+
+ AmbushReady ((RESPONSE_REF)0);
+ }
+ else if (!GET_GAME_STATE (SYREEN_KNOW_ABOUT_MYCON))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_BEFORE_AMBUSH_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_BEFORE_AMBUSH_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_BEFORE_AMBUSH_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_BEFORE_AMBUSH_4);
+ --NumVisits;
+ break;
+ }
+
+ if (NumVisits > 1)
+ NormalSyreen ((RESPONSE_REF)0);
+ else
+ {
+ construct_response (shared_phrase_buf,
+ we_are_vindicator0,
+ GLOBAL_SIS (CommanderName),
+ we_are_vindicator1,
+ GLOBAL_SIS (ShipName),
+ we_are_vindicator2,
+ (UNICODE*)NULL);
+ Response (we_are_vice_squad, InitialSyreen);
+ Response (we_are_the_one_for_you_baby, InitialSyreen);
+ DoResponsePhrase (we_are_vindicator0, InitialSyreen, shared_phrase_buf);
+ Response (we_are_impressed, InitialSyreen);
+ }
+ }
+#ifdef NEVER
+ else if (!GET_GAME_STATE (SYREEN_SHUTTLE))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (MUST_ACT);
+ --NumVisits;
+ break;
+ }
+
+ SyreenShuttle ((RESPONSE_REF)0);
+ }
+#endif /* NEVER */
+ else if (GET_GAME_STATE (SHIP_VAULT_UNLOCKED))
+ {
+ PlanAmbush ((RESPONSE_REF)0);
+ // XXX: PlanAmbush() sets SYREEN_HOME_VISITS=0, but then this value
+ // is immediately reset to NumVisits just below. The intent was to
+ // reset the HELLO stack so that is what we will do here as well.
+ // Note that it is completely redundant because genvault.c resets
+ // SYREEN_HOME_VISITS when it sets SHIP_VAULT_UNLOCKED=1.
+ NumVisits = 0;
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (FOUND_VAULT_YET_1);
+ break;
+ case 1:
+ NPCPhrase (FOUND_VAULT_YET_2);
+ --NumVisits;
+ break;
+ }
+
+ SyreenVault ((RESPONSE_REF)0);
+ }
+ SET_GAME_STATE (SYREEN_HOME_VISITS, NumVisits);
+}
+
+static COUNT
+uninit_syreen (void)
+{
+ return (0);
+}
+
+static void
+post_syreen_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_syreen_comm (void)
+{
+ LOCDATA *retval;
+
+ syreen_desc.init_encounter_func = Intro;
+ syreen_desc.post_encounter_func = post_syreen_enc;
+ syreen_desc.uninit_encounter_func = uninit_syreen;
+
+ syreen_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ syreen_desc.AlienTextBaseline.y = 0;
+ syreen_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ setSegue (Segue_peace);
+ retval = &syreen_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/talkpet/Makeinfo b/src/uqm/comm/talkpet/Makeinfo
new file mode 100644
index 0000000..59f7d27
--- /dev/null
+++ b/src/uqm/comm/talkpet/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="talkpet.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/talkpet/resinst.h b/src/uqm/comm/talkpet/resinst.h
new file mode 100644
index 0000000..20398f9
--- /dev/null
+++ b/src/uqm/comm/talkpet/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define TALKING_PET_COLOR_MAP "comm.talkingpet.colortable"
+#define TALKING_PET_CONVERSATION_PHRASES "comm.talkingpet.dialogue"
+#define TALKING_PET_FONT "comm.talkingpet.font"
+#define TALKING_PET_MUSIC "comm.talkingpet.music"
+#define TALKING_PET_PMAP_ANIM "comm.talkingpet.graphics"
diff --git a/src/uqm/comm/talkpet/strings.h b/src/uqm/comm/talkpet/strings.h
new file mode 100644
index 0000000..01b959a
--- /dev/null
+++ b/src/uqm/comm/talkpet/strings.h
@@ -0,0 +1,140 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef TALKPET_STRINGS_H
+#define TALKPET_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ HELLO_AT_UMGAH,
+ what_are_you,
+ JUST_TALKING_PET,
+ talking_pets_dumb,
+ OH_NO_YOU_DONT,
+ what_do_to_umgah,
+ DID_NOTHING,
+ umgah_zombies,
+ WORKS_LIKE_THIS,
+ we_are_vindicator0,
+ we_are_vindicator1,
+ GOOD_FOR_YOU,
+ must_explain_presence,
+ EXPLAIN_NOTHING_MONKEY_BOY,
+ bye_at_umgah,
+ GOODBYE_AT_UMGAH,
+ HYPNOTIZE_AGAIN_1,
+ HYPNOTIZE_AGAIN_2,
+ HYPNOTIZE_AGAIN_3,
+ HYPNOTIZE_AGAIN_4,
+ HYPNO_TAIL,
+ CANT_COMPEL,
+ LETS_MAKE_A_DEAL,
+ what_kind_of_deal,
+ HELP_DEFEAT_URQUAN,
+ ok_lets_do_it,
+ COMING_ABOARD,
+ how_trust,
+ TRUST,
+ boneless_dweeb,
+ YOUR_BONELESS_DWEEB,
+ what_are_you_really,
+ POOR_DNYARRI,
+ hard_to_believe,
+ ITS_TRUE,
+ bullshit,
+ WORTH_A_TRY,
+ kill_you,
+ PLEASE_DONT,
+ must_kill,
+ DONT_KILL,
+ want_kill_1,
+ want_kill_2,
+ want_kill_3,
+ GLAD_YOU_WONT_KILL,
+ whats_up_onboard,
+ GENERAL_INFO_ONBOARD_1,
+ GENERAL_INFO_ONBOARD_2,
+ GENERAL_INFO_ONBOARD_3,
+ GENERAL_INFO_ONBOARD_4,
+ GENERAL_INFO_ONBOARD_5,
+ GENERAL_INFO_ONBOARD_6,
+ GENERAL_INFO_ONBOARD_7,
+ GENERAL_INFO_ONBOARD_8,
+ HELLO_AS_DEVICE_1,
+ HELLO_AS_DEVICE_2,
+ HELLO_AS_DEVICE_3,
+ HELLO_AS_DEVICE_4,
+ HELLO_AS_DEVICE_5,
+ HELLO_AS_DEVICE_6,
+ HELLO_AS_DEVICE_7,
+ HELLO_AS_DEVICE_8,
+ CYBORG_PEP_TALK,
+ HUMAN_PEP_TALK,
+ I_SENSE_MY_SLAVES,
+ HAVENT_GOT_EVERYTHING,
+ NEED_BOMB,
+ SOUP_UP_BOMB,
+ SOUP_UP_FLEET,
+ SOUP_UP_FLAGSHIP,
+ COMEBACK_WHEN_READY,
+ what_now,
+ DO_THIS,
+ compel_urquan,
+ HERE_WE_GO,
+ im_scared,
+ STUPID_FOP,
+ compel_that_ship,
+ SAVING_MY_POWER,
+ any_suggestions,
+ SUGGESTION_1,
+ SUGGESTION_2,
+ SUGGESTION_3,
+ SUGGESTION_4,
+ SUGGESTION_5,
+ SUGGESTION_6,
+ SUGGESTION_7,
+ SUGGESTION_8,
+ about_your_race,
+ WHAT_ABOUT_RACE,
+ you_lied,
+ SO_WHAT,
+ bye_onboard,
+ GOODBYE_ONBOARD,
+ what_about_physiology,
+ NO_TALK_ABOUT_SELF,
+ what_about_powers,
+ NOT_POWERS_BUT_FLOWERS,
+ yes_flowers,
+ GOOD_HUMAN,
+ wish_to_go_now,
+ EXCELLENT_IDEA,
+ what_about_your_history,
+ ABOUT_HISTORY,
+ sentient_milieu,
+ ABOUT_SENTIENT_MILIEU,
+ what_about_war,
+ ABOUT_WAR,
+ enough_info,
+ OK_ENOUGH_INFO,
+ UMGAH_ALL_GONE,
+ HELLO_AFTER_COMPEL_URQUAN,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/talkpet/talkpet.c b/src/uqm/comm/talkpet/talkpet.c
new file mode 100644
index 0000000..ce59011
--- /dev/null
+++ b/src/uqm/comm/talkpet/talkpet.c
@@ -0,0 +1,841 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+
+#define STROBE_RATE 10
+#define STROBE_LENGTH (ONE_SECOND * 3 / 2)
+#define NUM_STROBES (STROBE_LENGTH * STROBE_RATE / ONE_SECOND)
+
+static LOCDATA talkpet_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ TALKING_PET_PMAP_ANIM, /* AlienFrame */
+ TALKING_PET_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ TALKING_PET_COLOR_MAP, /* AlienColorMap */
+ TALKING_PET_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ TALKING_PET_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 17, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 7, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 10, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 13, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 16, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Blink right eye */
+ 18, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 13) /* BlockMask */
+ },
+ { /* Blink left eye */
+ 21, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 12) | (1 << 14), /* BlockMask */
+ },
+ {
+ 24, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 26, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 28, /* StartIndex */
+ 4, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 32, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 35, /* StartIndex */
+ 5, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 40, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 42, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5), /* BlockMask */
+ },
+ { /* Right eyebrow */
+ 48, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4), /* BlockMask */
+ },
+ { /* Left eyebrow */
+ 50, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5), /* BlockMask */
+ },
+ {
+ 52, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Mind control strobe (on-demand) */
+ 1, /* StartIndex */
+ NUM_STROBES * 2, /* NumFrames */
+ CIRCULAR_ANIM | COLORXFORM_ANIM | ONE_SHOT_ANIM
+ | ANIM_DISABLED, /* AnimFlags */
+ ONE_SECOND / (STROBE_RATE * 2), 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 6, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+ SET_GAME_STATE (SHIP_TO_COMPEL, 0);
+
+ if (PLAYER_SAID (R, compel_urquan))
+ {
+ NPCPhrase (HERE_WE_GO);
+
+ SET_GAME_STATE (URQUAN_MESSED_UP, 1);
+ }
+ else if (PLAYER_SAID (R, wish_to_go_now))
+ NPCPhrase (EXCELLENT_IDEA);
+ else if (PLAYER_SAID (R, bye_onboard))
+ NPCPhrase (GOODBYE_ONBOARD);
+ else if (PLAYER_SAID (R, compel_that_ship))
+ NPCPhrase (SAVING_MY_POWER);
+ else if (PLAYER_SAID (R, ok_lets_do_it)
+ || PLAYER_SAID (R, want_kill_1)
+ || PLAYER_SAID (R, want_kill_2)
+ || PLAYER_SAID (R, want_kill_3))
+ {
+ if (PLAYER_SAID (R, ok_lets_do_it))
+ NPCPhrase (COMING_ABOARD);
+ else
+ NPCPhrase (GLAD_YOU_WONT_KILL);
+
+ SET_GAME_STATE (TALKING_PET, 1);
+ SET_GAME_STATE (TALKING_PET_ON_SHIP, 1);
+ SET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES, 0);
+ SET_GAME_STATE (UMGAH_VISITS, 0);
+ SET_GAME_STATE (UMGAH_HOME_VISITS, 0);
+ SET_GAME_STATE (ARILOU_STACK_2, 0);
+ }
+}
+
+static void
+MindFuckUrquan (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, what_now))
+ {
+ NPCPhrase (DO_THIS);
+
+ DISABLE_PHRASE (what_now);
+ }
+ else if (PLAYER_SAID (R, im_scared))
+ {
+ NPCPhrase (STUPID_FOP);
+
+ DISABLE_PHRASE (im_scared);
+ }
+
+ if (PHRASE_ENABLED (what_now))
+ Response (what_now, MindFuckUrquan);
+ if (PHRASE_ENABLED (im_scared))
+ Response (im_scared, MindFuckUrquan);
+ Response (compel_urquan, ExitConversation);
+}
+
+static void PetDevice (RESPONSE_REF R);
+
+static void
+MindControlStrobe (void)
+{
+ // Enable the one-shot strobe animation
+ CommData.AlienAmbientArray[16].AnimFlags &= ~ANIM_DISABLED;
+}
+
+static void
+MindControl (RESPONSE_REF R)
+{
+ RESPONSE_FUNC RespFunc;
+
+ if (PLAYER_SAID (R, what_about_powers))
+ {
+ NPCPhrase (NOT_POWERS_BUT_FLOWERS);
+
+ RespFunc = (RESPONSE_FUNC)MindControl;
+ R = yes_flowers;
+ }
+ else /* if (R == yes_flowers) */
+ {
+ NPCPhrase (GOOD_HUMAN);
+
+ RespFunc = (RESPONSE_FUNC)ExitConversation;
+ R = wish_to_go_now;
+ }
+
+ AlienTalkSegue ((COUNT)~0);
+ MindControlStrobe ();
+
+ Response (R, RespFunc);
+}
+
+static void
+PetInfo (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, about_your_race))
+ NPCPhrase (WHAT_ABOUT_RACE);
+ else if (PLAYER_SAID (R, what_about_physiology))
+ {
+ NPCPhrase (NO_TALK_ABOUT_SELF);
+
+ DISABLE_PHRASE (what_about_physiology);
+ }
+ else if (PLAYER_SAID (R, what_about_your_history))
+ {
+ NPCPhrase (ABOUT_HISTORY);
+
+ DISABLE_PHRASE (what_about_your_history);
+ }
+ else if (PLAYER_SAID (R, sentient_milieu))
+ {
+ NPCPhrase (ABOUT_SENTIENT_MILIEU);
+
+ DISABLE_PHRASE (sentient_milieu);
+ }
+ else if (PLAYER_SAID (R, what_about_war))
+ {
+ NPCPhrase (ABOUT_WAR);
+
+ DISABLE_PHRASE (what_about_war);
+ }
+
+ if (PHRASE_ENABLED (what_about_physiology))
+ {
+ Response (what_about_physiology, PetInfo);
+ }
+ else
+ {
+ Response (what_about_powers, MindControl);
+ }
+ if (PHRASE_ENABLED (what_about_your_history))
+ Response (what_about_your_history, PetInfo);
+ else if (PHRASE_ENABLED (sentient_milieu))
+ Response (sentient_milieu, PetInfo);
+ else if (PHRASE_ENABLED (what_about_war))
+ Response (what_about_war, PetInfo);
+ Response (enough_info, PetDevice);
+}
+
+static void
+PetDevice (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, whats_up_onboard))
+ {
+ NumVisits = GET_GAME_STATE (TALKING_PET_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_ONBOARD_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_ONBOARD_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_ONBOARD_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_ONBOARD_4);
+ break;
+ case 4:
+ NPCPhrase (GENERAL_INFO_ONBOARD_5);
+ break;
+ case 5:
+ NPCPhrase (GENERAL_INFO_ONBOARD_6);
+ break;
+ case 6:
+ NPCPhrase (GENERAL_INFO_ONBOARD_7);
+ break;
+ case 7:
+ NPCPhrase (GENERAL_INFO_ONBOARD_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (TALKING_PET_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_onboard);
+ }
+ else if (PLAYER_SAID (R, any_suggestions))
+ {
+ NumVisits = GET_GAME_STATE (TALKING_PET_SUGGESTIONS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (SUGGESTION_1);
+ break;
+ case 1:
+ NPCPhrase (SUGGESTION_2);
+ break;
+ case 2:
+ NPCPhrase (SUGGESTION_3);
+ break;
+ case 3:
+ NPCPhrase (SUGGESTION_4);
+ break;
+ case 4:
+ NPCPhrase (SUGGESTION_5);
+ break;
+ case 5:
+ NPCPhrase (SUGGESTION_6);
+ break;
+ case 6:
+ NPCPhrase (SUGGESTION_7);
+ break;
+ case 7:
+ NPCPhrase (SUGGESTION_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (TALKING_PET_SUGGESTIONS, NumVisits);
+
+ DISABLE_PHRASE (any_suggestions);
+ }
+ else if (PLAYER_SAID (R, enough_info))
+ NPCPhrase (OK_ENOUGH_INFO);
+ else if (PLAYER_SAID (R, you_lied))
+ {
+ NPCPhrase (SO_WHAT);
+
+ SET_GAME_STATE (DNYARRI_LIED, 0);
+ }
+
+ if (GET_GAME_STATE (SHIP_TO_COMPEL))
+ {
+ Response (compel_that_ship, ExitConversation);
+ }
+ if (PHRASE_ENABLED (whats_up_onboard))
+ Response (whats_up_onboard, PetDevice);
+ if (PHRASE_ENABLED (any_suggestions))
+ Response (any_suggestions, PetDevice);
+ Response (about_your_race, PetInfo);
+ if (GET_GAME_STATE (DNYARRI_LIED) && GET_GAME_STATE (LEARNED_TALKING_PET))
+ Response (you_lied, PetDevice);
+ Response (bye_onboard, ExitConversation);
+}
+
+static void
+CompelPlayer (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[3];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = 0;
+ if (PLAYER_SAID (R, what_are_you))
+ {
+ NPCPhrase (JUST_TALKING_PET);
+
+ DISABLE_PHRASE (what_are_you);
+ }
+ else if (PLAYER_SAID (R, what_do_to_umgah))
+ {
+ NPCPhrase (DID_NOTHING);
+
+ DISABLE_PHRASE (what_do_to_umgah);
+ LastStack = 1;
+ }
+ else if (PLAYER_SAID (R, we_are_vindicator0))
+ {
+ NPCPhrase (GOOD_FOR_YOU);
+
+ DISABLE_PHRASE (we_are_vindicator0);
+ LastStack = 2;
+ }
+ else if (R != 0)
+ {
+ if (PLAYER_SAID (R, bye_at_umgah))
+ NPCPhrase (GOODBYE_AT_UMGAH);
+ else if (PLAYER_SAID (R, must_explain_presence))
+ NPCPhrase (EXPLAIN_NOTHING_MONKEY_BOY);
+ else if (PLAYER_SAID (R, umgah_zombies))
+ NPCPhrase (WORKS_LIKE_THIS);
+ else if (PLAYER_SAID (R, talking_pets_dumb))
+ NPCPhrase (OH_NO_YOU_DONT);
+
+ SET_GAME_STATE (KNOW_UMGAH_ZOMBIES, 1);
+ if (!GET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP))
+ {
+ SET_GAME_STATE (PLAYER_HYPNOTIZED, 1);
+ }
+ else
+ {
+ NPCPhrase (CANT_COMPEL);
+
+ setSegue (Segue_hostile);
+ }
+
+ return;
+ }
+
+ if (PHRASE_ENABLED (what_are_you))
+ pStr[0] = what_are_you;
+ else
+ pStr[0] = talking_pets_dumb;
+ if (GET_GAME_STATE (KNOW_UMGAH_ZOMBIES))
+ {
+ if (PHRASE_ENABLED (what_do_to_umgah))
+ pStr[1] = what_do_to_umgah;
+ else
+ pStr[1] = umgah_zombies;
+ }
+ if (PHRASE_ENABLED (we_are_vindicator0))
+ {
+ construct_response (
+ shared_phrase_buf,
+ we_are_vindicator0,
+ GLOBAL_SIS (ShipName),
+ we_are_vindicator1,
+ (UNICODE*)NULL);
+ pStr[2] = we_are_vindicator0;
+ }
+ else
+ pStr[2] = must_explain_presence;
+
+ if (pStr[LastStack])
+ {
+ if (pStr[LastStack] != we_are_vindicator0)
+ Response (pStr[LastStack], CompelPlayer);
+ else
+ DoResponsePhrase (pStr[LastStack], CompelPlayer, shared_phrase_buf);
+ }
+ for (i = 0; i < 3; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ {
+ if (pStr[i] != we_are_vindicator0)
+ Response (pStr[i], CompelPlayer);
+ else
+ DoResponsePhrase (pStr[i], CompelPlayer, shared_phrase_buf);
+ }
+ }
+ Response (bye_at_umgah, CompelPlayer);
+}
+
+static void PetDeal (RESPONSE_REF R);
+
+static void
+KillPet (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, must_kill))
+ {
+ NPCPhrase (DONT_KILL);
+ AlienTalkSegue ((COUNT)~0);
+
+ MindControlStrobe ();
+ }
+
+ Response (want_kill_1, ExitConversation);
+ Response (want_kill_2, ExitConversation);
+ Response (want_kill_3, ExitConversation);
+}
+
+static void
+PetDeal (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, what_kind_of_deal))
+ {
+ NPCPhrase (HELP_DEFEAT_URQUAN);
+
+ DISABLE_PHRASE (what_kind_of_deal);
+ }
+ else if (PLAYER_SAID (R, how_trust))
+ {
+ NPCPhrase (TRUST);
+
+ DISABLE_PHRASE (how_trust);
+ }
+ else if (PLAYER_SAID (R, boneless_dweeb))
+ {
+ NPCPhrase (YOUR_BONELESS_DWEEB);
+
+ DISABLE_PHRASE (boneless_dweeb);
+ }
+ else if (PLAYER_SAID (R, what_are_you_really))
+ {
+ NPCPhrase (POOR_DNYARRI);
+
+ DISABLE_PHRASE (what_are_you_really);
+ }
+ else if (PLAYER_SAID (R, hard_to_believe))
+ {
+ NPCPhrase (ITS_TRUE);
+
+ SET_GAME_STATE (DNYARRI_LIED, 1);
+ DISABLE_PHRASE (hard_to_believe);
+ }
+ else if (PLAYER_SAID (R, bullshit))
+ {
+ NPCPhrase (WORTH_A_TRY);
+
+ DISABLE_PHRASE (bullshit);
+ }
+ else if (PLAYER_SAID (R, kill_you))
+ {
+ NPCPhrase (PLEASE_DONT);
+
+ DISABLE_PHRASE (kill_you);
+ }
+
+ if (PHRASE_ENABLED (what_kind_of_deal))
+ Response (what_kind_of_deal, PetDeal);
+ else
+ {
+ if (PHRASE_ENABLED (how_trust))
+ Response (how_trust, PetDeal);
+ else if (PHRASE_ENABLED (boneless_dweeb))
+ Response (boneless_dweeb, PetDeal);
+ Response (ok_lets_do_it, ExitConversation);
+ }
+ if (PHRASE_ENABLED (what_are_you_really))
+ Response (what_are_you_really, PetDeal);
+ else
+ {
+ if (PHRASE_ENABLED (hard_to_believe) && !GET_GAME_STATE (LEARNED_TALKING_PET))
+ Response (hard_to_believe, PetDeal);
+ else if (PHRASE_ENABLED (bullshit) && GET_GAME_STATE (LEARNED_TALKING_PET))
+ Response (bullshit, PetDeal);
+ }
+ if (PHRASE_ENABLED (kill_you))
+ Response (kill_you, PetDeal);
+ else if (PHRASE_ENABLED (must_kill))
+ {
+ Response (must_kill, KillPet);
+ }
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ {
+ SET_GAME_STATE (SHIP_TO_COMPEL, 0);
+ setSegue (Segue_hostile);
+ if (!(GLOBAL (glob_flags) & CYBORG_ENABLED))
+ {
+ NPCPhrase (HUMAN_PEP_TALK);
+ }
+ else
+ {
+ NPCPhrase (CYBORG_PEP_TALK);
+ }
+ }
+ else if (GET_GAME_STATE (READY_TO_CONFUSE_URQUAN))
+ {
+ SET_GAME_STATE (SHIP_TO_COMPEL, 0);
+ SET_GAME_STATE (READY_TO_CONFUSE_URQUAN, 0);
+ SET_GAME_STATE (AWARE_OF_SAMATRA, 1);
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) != 3)
+ {
+ NPCPhrase (HAVENT_GOT_EVERYTHING);
+ if (!GET_GAME_STATE (UTWIG_BOMB_ON_SHIP))
+ NPCPhrase (NEED_BOMB);
+ else
+ NPCPhrase (SOUP_UP_BOMB);
+
+ setSegue (Segue_peace);
+ }
+ else if (GET_GAME_STATE (URQUAN_MESSED_UP))
+ {
+ NPCPhrase (HELLO_AFTER_COMPEL_URQUAN);
+
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ NPCPhrase (I_SENSE_MY_SLAVES);
+
+ MindFuckUrquan ((RESPONSE_REF)0);
+ }
+ }
+ else if (GET_GAME_STATE (TALKING_PET_ON_SHIP))
+ {
+ NumVisits = GET_GAME_STATE (TALKING_PET_VISITS);
+
+ // You can acquire the Talking Pet without having Taalo Shield after
+ // the Kohr-Ah wipe out the Umgah. In that case, the Pet will join
+ // you willingly, but his complaints about the Taalo shield as in
+ // HELLO_AS_DEVICE_1 do not make any sense.
+ if (!GET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP) && NumVisits == 0)
+ ++NumVisits; // skip HELLO_AS_DEVICE_1
+
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_AS_DEVICE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_AS_DEVICE_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_AS_DEVICE_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_AS_DEVICE_4);
+ break;
+ case 4:
+ NPCPhrase (HELLO_AS_DEVICE_5);
+ break;
+ case 5:
+ NPCPhrase (HELLO_AS_DEVICE_6);
+ break;
+ case 6:
+ NPCPhrase (HELLO_AS_DEVICE_7);
+ break;
+ case 7:
+ NPCPhrase (HELLO_AS_DEVICE_8);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (TALKING_PET_VISITS, NumVisits);
+
+ PetDevice ((RESPONSE_REF)0);
+ }
+ else if (GetHeadLink (&GLOBAL (npc_built_ship_q)))
+ {
+ NumVisits = GET_GAME_STATE (TALKING_PET_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ SET_GAME_STATE (UMGAH_VISITS, 0);
+ NPCPhrase (HELLO_AT_UMGAH);
+ break;
+ case 1:
+ NPCPhrase (HYPNOTIZE_AGAIN_1);
+ break;
+ case 2:
+ NPCPhrase (HYPNOTIZE_AGAIN_2);
+ break;
+ case 3:
+ NPCPhrase (HYPNOTIZE_AGAIN_3);
+ break;
+ case 4:
+ NPCPhrase (HYPNOTIZE_AGAIN_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (TALKING_PET_HOME_VISITS, NumVisits);
+
+ if (NumVisits == 1)
+ {
+ CompelPlayer ((RESPONSE_REF)0);
+ }
+ else if (!GET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP))
+ {
+ SET_GAME_STATE (PLAYER_HYPNOTIZED, 1);
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ NPCPhrase (CANT_COMPEL);
+
+ setSegue (Segue_hostile);
+ }
+ }
+ else
+ {
+ if (StartSphereTracking (UMGAH_SHIP))
+ {
+ NPCPhrase (LETS_MAKE_A_DEAL);
+ }
+ else
+ {
+ NPCPhrase (UMGAH_ALL_GONE);
+
+ if (GET_GAME_STATE (TALKING_PET_HOME_VISITS) == 0
+ || !GET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP))
+ { // The how_trust-TRUST exchange only makes sense when the
+ // player visited the Talking Pet before so he tried to
+ // kill the player *and* the player has the Taalo shield.
+ DISABLE_PHRASE (how_trust);
+ }
+ }
+
+ PetDeal ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_talkpet (void)
+{
+ return (0);
+}
+
+static void
+post_talkpet_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_talkpet_comm (void)
+{
+ LOCDATA *retval;
+
+ talkpet_desc.init_encounter_func = Intro;
+ talkpet_desc.post_encounter_func = post_talkpet_enc;
+ talkpet_desc.uninit_encounter_func = uninit_talkpet;
+
+ talkpet_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ talkpet_desc.AlienTextBaseline.y = 0;
+ talkpet_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) != IN_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+
+ retval = &talkpet_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/thradd/Makeinfo b/src/uqm/comm/thradd/Makeinfo
new file mode 100644
index 0000000..d492800
--- /dev/null
+++ b/src/uqm/comm/thradd/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="thraddc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/thradd/resinst.h b/src/uqm/comm/thradd/resinst.h
new file mode 100644
index 0000000..977f175
--- /dev/null
+++ b/src/uqm/comm/thradd/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define THRADD_COLOR_MAP "comm.thraddash.colortable"
+#define THRADD_CONVERSATION_PHRASES "comm.thraddash.dialogue"
+#define THRADD_FONT "comm.thraddash.font"
+#define THRADD_MUSIC "comm.thraddash.music"
+#define THRADD_PMAP_ANIM "comm.thraddash.graphics"
diff --git a/src/uqm/comm/thradd/strings.h b/src/uqm/comm/thradd/strings.h
new file mode 100644
index 0000000..20e4701
--- /dev/null
+++ b/src/uqm/comm/thradd/strings.h
@@ -0,0 +1,181 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef THRADD_STRINGS_H
+#define THRADD_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ HOSTILE_SPACE_HELLO_1,
+ HOSTILE_SPACE_HELLO_2,
+ HOSTILE_SPACE_HELLO_3,
+ HOSTILE_SPACE_HELLO_4,
+ HOSTILE_HOMEWORLD_HELLO_1,
+ HOSTILE_HOMEWORLD_HELLO_2,
+ HOSTILE_HOMEWORLD_HELLO_3,
+ HOSTILE_HOMEWORLD_HELLO_4,
+ whats_up_hostile_1,
+ whats_up_hostile_2,
+ GENERAL_INFO_HOSTILE_1,
+ GENERAL_INFO_HOSTILE_2,
+ GENERAL_INFO_HOSTILE_3,
+ GENERAL_INFO_HOSTILE_4,
+ what_about_you_1,
+ ABOUT_US_1,
+ what_about_you_2,
+ ABOUT_US_2,
+ what_about_urquan_1,
+ ABOUT_URQUAN_1,
+ what_about_urquan_2,
+ ABOUT_URQUAN_2,
+ got_idea,
+ GOOD_IDEA,
+ WE_GO_TO_IMPRESS_URQUAN_1,
+ WE_GO_TO_IMPRESS_URQUAN_2,
+ WE_IMPRESSING_URQUAN_1,
+ WE_IMPRESSING_URQUAN_2,
+ WE_IMPRESSED_URQUAN_1,
+ WE_IMPRESSED_URQUAN_2,
+ HOSTILE_HELIX_HELLO_1,
+ HOSTILE_HELIX_HELLO_2,
+ submit_1,
+ NO_SUBMIT_1,
+ submit_2,
+ NO_SUBMIT_2,
+ be_friends_1,
+ NO_FRIENDS_1,
+ be_friends_2,
+ NO_FRIENDS_2,
+ how_impressed_urquan_1,
+ IMPRESSED_LIKE_SO_1,
+ how_impressed_urquan_2,
+ IMPRESSED_LIKE_SO_2,
+ bye_hostile_1,
+ GOODBYE_HOSTILE_1,
+ bye_hostile_2,
+ GOODBYE_HOSTILE_2,
+ why_you_here_hostile,
+ NONE_OF_YOUR_CONCERN,
+ demand_to_land,
+ NO_DEMAND,
+ what_about_this_world,
+ BLUE_HELIX,
+ whats_helix_hostile,
+ HELIX_IS_HOSTILE,
+ i_need_to_land_lie,
+ CAUGHT_LIE,
+ bye_hostile_helix,
+ GOODBYE_HOSTILE_HELIX,
+ DIE_THIEF_1,
+ DIE_THIEF_2,
+ AMAZING_PERFORMANCE,
+ IMPRESSIVE_PERFORMANCE,
+ ADEQUATE_PERFORMANCE,
+ HELLO_POLITE_1,
+ HELLO_POLITE_2,
+ HELLO_POLITE_3,
+ HELLO_POLITE_4,
+ HELLO_RHYME_1,
+ HELLO_RHYME_2,
+ HELLO_RHYME_3,
+ HELLO_RHYME_4,
+ HELLO_PIG_LATIN_1,
+ HELLO_PIG_LATIN_2,
+ HELLO_PIG_LATIN_3,
+ HELLO_PIG_LATIN_4,
+ HELLO_LIKE_YOU_1,
+ HELLO_LIKE_YOU_2,
+ HELLO_LIKE_YOU_3,
+ HELLO_LIKE_YOU_4,
+ WELCOME_SPACE0,
+ WELCOME_SPACE1,
+ WELCOME_HOMEWORLD0,
+ WELCOME_HOMEWORLD1,
+ WELCOME_HELIX0,
+ WELCOME_HELIX1,
+ why_you_here_ally,
+ GUARDING_HELIX_ALLY,
+ whats_helix_ally,
+ HELIX_IS_ALLY,
+ may_i_land,
+ SURE_LAND,
+ whats_up_ally,
+ GENERAL_INFO_ALLY_1,
+ GENERAL_INFO_ALLY_2,
+ GENERAL_INFO_ALLY_3,
+ GENERAL_INFO_ALLY_4,
+ HOW_SHOULD_WE_ACT,
+ friendly,
+ OK_FRIENDLY,
+ wacky,
+ OK_WACKY,
+ just_like_us,
+ OK_JUST_LIKE_YOU,
+ WORK_TO_DO,
+ contemplative,
+ OK_CONTEMPLATIVE,
+ how_goes_culture,
+ CONTEMP_GOES_1,
+ CONTEMP_GOES_2,
+ FRIENDLY_GOES_1,
+ FRIENDLY_GOES_2,
+ WACKY_GOES_1,
+ WACKY_GOES_2,
+ LIKE_YOU_GOES_1,
+ LIKE_YOU_GOES_2,
+ bye_ally,
+ GOODBYE_ALLY_1,
+ GOODBYE_ALLY_2,
+ GOODBYE_ALLY_3,
+ GOODBYE_ALLY_4,
+ be_polite,
+ OK_POLITE,
+ speak_pig_latin,
+ OK_PIG_LATIN,
+ use_rhymes,
+ OK_RHYMES,
+ just_the_way_we_do,
+ OK_WAY_YOU_DO,
+ WHAT_NAME_FOR_CULTURE,
+ alliance_name,
+ OK_ALLIANCE_NAME,
+ NAME_TAIL,
+ you_decide,
+ OK_CULTURE_20,
+ fat,
+ OK_FAT,
+ the_slave_empire0,
+ the_slave_empire1,
+ OK_SLAVE,
+ FAT_JERKS,
+ CULTURE,
+ SLAVE_EMPIRE,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ HAVING_FUN_WITH_ILWRATH_1,
+ HAVING_FUN_WITH_ILWRATH_2,
+ GO_AWAY_FIGHTING_ILWRATH_1,
+ GO_AWAY_FIGHTING_ILWRATH_2,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/thradd/thraddc.c b/src/uqm/comm/thradd/thraddc.c
new file mode 100644
index 0000000..1be8a1e
--- /dev/null
+++ b/src/uqm/comm/thradd/thraddc.c
@@ -0,0 +1,954 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/gameev.h"
+
+
+static LOCDATA thradd_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ THRADD_PMAP_ANIM, /* AlienFrame */
+ THRADD_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ THRADD_COLOR_MAP, /* AlienColorMap */
+ THRADD_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ THRADD_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 8, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 8, /* StartIndex */
+ 4, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* RestartRate */
+ (1 << 4), /* BlockMask */
+ },
+ {
+ 12, /* StartIndex */
+ 9, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 21, /* StartIndex */
+ 6, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 27, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4), /* BlockMask */
+ },
+ {
+ 30, /* StartIndex */
+ 12, /* NumFrames */
+ CIRCULAR_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND, /* RestartRate */
+ (1 << 0) | (1 << 3) | (1 << 5),
+ },
+ {
+ 42, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4) | (1 << 6), /* BlockMask */
+ },
+ {
+ 47, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5), /* BlockMask */
+ },
+ {
+ 52, /* StartIndex */
+ 4, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND / 20, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 7, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static int
+GetCultureName (void)
+{
+ int culture = 0;
+
+ switch (GET_GAME_STATE (THRADD_CULTURE))
+ {
+ case 1:
+ culture = CULTURE;
+ break;
+ case 2:
+ culture = FAT_JERKS;
+ break;
+ case 3:
+ culture = SLAVE_EMPIRE;
+ break;
+ default:
+ assert (0 && "Unknown culture");
+ }
+
+ return (culture);
+}
+
+static void
+PolitePhrase (BYTE which_phrase)
+{
+ switch (which_phrase)
+ {
+ case 0:
+ NPCPhrase (HELLO_POLITE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_POLITE_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_POLITE_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_POLITE_4);
+ break;
+ }
+}
+
+static void
+RhymePhrase (BYTE which_phrase)
+{
+ switch (which_phrase)
+ {
+ case 0:
+ NPCPhrase (HELLO_RHYME_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_RHYME_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_RHYME_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_RHYME_4);
+ break;
+ }
+}
+
+static void
+PigLatinPhrase (BYTE which_phrase)
+{
+ switch (which_phrase)
+ {
+ case 0:
+ NPCPhrase (HELLO_PIG_LATIN_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_PIG_LATIN_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_PIG_LATIN_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_PIG_LATIN_4);
+ break;
+ }
+}
+
+static void
+LikeYouPhrase (BYTE which_phrase)
+{
+ switch (which_phrase)
+ {
+ case 0:
+ NPCPhrase (HELLO_LIKE_YOU_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_LIKE_YOU_2);
+ break;
+ case 2:
+ NPCPhrase (HELLO_LIKE_YOU_3);
+ break;
+ case 3:
+ NPCPhrase (HELLO_LIKE_YOU_4);
+ break;
+ }
+}
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, bye_hostile_2))
+ NPCPhrase (GOODBYE_HOSTILE_2);
+ else if (PLAYER_SAID (R, bye_hostile_1))
+ {
+ NPCPhrase (GOODBYE_HOSTILE_1);
+
+ SET_GAME_STATE (THRADD_HOSTILE_STACK_5, 1);
+ }
+ else if (PLAYER_SAID (R, submit_1))
+ {
+ NPCPhrase (NO_SUBMIT_1);
+
+ SET_GAME_STATE (THRADD_HOSTILE_STACK_2, 1);
+ }
+ else if (PLAYER_SAID (R, submit_2))
+ NPCPhrase (NO_SUBMIT_2);
+ else if (PLAYER_SAID (R, got_idea))
+ {
+ NPCPhrase (GOOD_IDEA);
+
+ setSegue (Segue_peace);
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, ADVANCE_THRADD_MISSION);
+ SET_GAME_STATE (THRADD_STACK_1, 5);
+ }
+ else if (PLAYER_SAID (R, bye_hostile_helix))
+ NPCPhrase (GOODBYE_HOSTILE_HELIX);
+ else if (PLAYER_SAID (R, bye_ally))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (THRADD_STACK_1);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GOODBYE_ALLY_1);
+ break;
+ case 1:
+ NPCPhrase (GOODBYE_ALLY_2);
+ break;
+ case 2:
+ NPCPhrase (GOODBYE_ALLY_3);
+ break;
+ case 3:
+ NPCPhrase (GOODBYE_ALLY_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (THRADD_STACK_1, NumVisits);
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, may_i_land))
+ {
+ NPCPhrase (SURE_LAND);
+
+ SET_GAME_STATE (HELIX_UNPROTECTED, 1);
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, demand_to_land))
+ NPCPhrase (NO_DEMAND);
+ else if (PLAYER_SAID (R, i_need_to_land_lie))
+ NPCPhrase (CAUGHT_LIE);
+ else
+ {
+ if (PLAYER_SAID (R, contemplative))
+ {
+ NPCPhrase (OK_CONTEMPLATIVE);
+
+ SET_GAME_STATE (THRADD_DEMEANOR, 0);
+ }
+ else if (PLAYER_SAID (R, friendly))
+ {
+ NPCPhrase (OK_FRIENDLY);
+
+ SET_GAME_STATE (THRADD_DEMEANOR, 1);
+ }
+ else if (PLAYER_SAID (R, wacky))
+ {
+ NPCPhrase (OK_WACKY);
+
+ SET_GAME_STATE (THRADD_DEMEANOR, 2);
+ }
+ else if (PLAYER_SAID (R, just_like_us))
+ {
+ NPCPhrase (OK_JUST_LIKE_YOU);
+
+ SET_GAME_STATE (THRADD_DEMEANOR, 3);
+ }
+ NPCPhrase (WORK_TO_DO);
+
+ setSegue (Segue_peace);
+ }
+}
+
+static void
+ThraddAllies (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, why_you_here_ally))
+ {
+ NPCPhrase (GUARDING_HELIX_ALLY);
+
+ DISABLE_PHRASE (why_you_here_ally);
+ }
+ else if (PLAYER_SAID (R, whats_helix_ally))
+ {
+ NPCPhrase (HELIX_IS_ALLY);
+
+ DISABLE_PHRASE (whats_helix_ally);
+ }
+ else if (PLAYER_SAID (R, whats_up_ally))
+ {
+ NumVisits = GET_GAME_STATE (THRADD_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_ALLY_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_ALLY_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_ALLY_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_ALLY_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (THRADD_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_ally);
+ }
+ else if (PLAYER_SAID (R, how_goes_culture))
+ {
+ NumVisits = GET_GAME_STATE (THRADD_DEMEANOR);
+ switch (NumVisits & ((1 << 2) - 1))
+ {
+ case 0:
+ if (!(NumVisits & ~((1 << 2) - 1)))
+ NPCPhrase (CONTEMP_GOES_1);
+ else
+ NPCPhrase (CONTEMP_GOES_2);
+ break;
+ case 1:
+ if (!(NumVisits & ~((1 << 2) - 1)))
+ NPCPhrase (FRIENDLY_GOES_1);
+ else
+ NPCPhrase (FRIENDLY_GOES_2);
+ break;
+ case 2:
+ if (!(NumVisits & ~((1 << 2) - 1)))
+ NPCPhrase (WACKY_GOES_1);
+ else
+ NPCPhrase (WACKY_GOES_2);
+ break;
+ case 3:
+ if (!(NumVisits & ~((1 << 2) - 1)))
+ NPCPhrase (LIKE_YOU_GOES_1);
+ else
+ NPCPhrase (LIKE_YOU_GOES_2);
+ break;
+ }
+ NumVisits |= 1 << 2;
+ SET_GAME_STATE (THRADD_DEMEANOR, NumVisits);
+
+ DISABLE_PHRASE (how_goes_culture);
+ }
+
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ if (PHRASE_ENABLED (why_you_here_ally))
+ Response (why_you_here_ally, ThraddAllies);
+ else
+ {
+ if (PHRASE_ENABLED (whats_helix_ally))
+ Response (whats_helix_ally, ThraddAllies);
+ Response (may_i_land, ExitConversation);
+ }
+ }
+ if (PHRASE_ENABLED (whats_up_ally))
+ Response (whats_up_ally, ThraddAllies);
+ if (PHRASE_ENABLED (how_goes_culture))
+ Response (how_goes_culture, ThraddAllies);
+ Response (bye_ally, ExitConversation);
+}
+
+static void
+ThraddDemeanor (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, you_decide))
+ {
+ NPCPhrase (OK_CULTURE_20);
+
+ SET_GAME_STATE (THRADD_CULTURE, 1);
+ }
+ else if (PLAYER_SAID (R, fat))
+ {
+ NPCPhrase (OK_FAT);
+
+ SET_GAME_STATE (THRADD_CULTURE, 2);
+ }
+ else if (PLAYER_SAID (R, the_slave_empire0))
+ {
+ SET_GAME_STATE (THRADD_CULTURE, 3);
+
+ NPCPhrase (OK_SLAVE);
+ }
+
+ NPCPhrase (HOW_SHOULD_WE_ACT);
+ Response (contemplative, ExitConversation);
+ Response (friendly, ExitConversation);
+ Response (wacky, ExitConversation);
+ Response (just_like_us, ExitConversation);
+}
+
+static void
+ThraddCulture (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, be_polite))
+ {
+ NPCPhrase (OK_POLITE);
+
+ SET_GAME_STATE (THRADD_INTRO, 0);
+ }
+ else if (PLAYER_SAID (R, use_rhymes))
+ {
+ NPCPhrase (OK_RHYMES);
+
+ SET_GAME_STATE (THRADD_INTRO, 1);
+ }
+ else if (PLAYER_SAID (R, speak_pig_latin))
+ {
+ NPCPhrase (OK_PIG_LATIN);
+
+ SET_GAME_STATE (THRADD_INTRO, 2);
+ }
+ else if (PLAYER_SAID (R, just_the_way_we_do))
+ {
+ NPCPhrase (OK_WAY_YOU_DO);
+
+ SET_GAME_STATE (THRADD_INTRO, 3);
+ }
+ NPCPhrase (WHAT_NAME_FOR_CULTURE);
+
+ construct_response (
+ shared_phrase_buf,
+ the_slave_empire0,
+ GLOBAL_SIS (CommanderName),
+ the_slave_empire1,
+ (UNICODE*)NULL);
+
+ Response (you_decide, ThraddDemeanor);
+ Response (fat, ThraddDemeanor);
+ DoResponsePhrase (the_slave_empire0, ThraddDemeanor, shared_phrase_buf);
+}
+
+static void
+ThraddWorship (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ SET_GAME_STATE (THRADD_VISITS, 0);
+ SET_GAME_STATE (THRADD_MANNER, 1);
+ SET_GAME_STATE (THRADD_STACK_1, 0);
+ SetRaceAllied (THRADDASH_SHIP, TRUE);
+
+ Response (be_polite, ThraddCulture);
+ Response (speak_pig_latin, ThraddCulture);
+ Response (use_rhymes, ThraddCulture);
+ Response (just_the_way_we_do, ThraddCulture);
+}
+
+static void
+HelixWorld (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, why_you_here_hostile))
+ {
+ NPCPhrase (NONE_OF_YOUR_CONCERN);
+
+ SET_GAME_STATE (THRADD_CULTURE, 1);
+ }
+ else if (PLAYER_SAID (R, what_about_this_world))
+ {
+ NPCPhrase (BLUE_HELIX);
+
+ SET_GAME_STATE (THRADD_INTRO, 1);
+ }
+ else if (PLAYER_SAID (R, whats_helix_hostile))
+ {
+ NPCPhrase (HELIX_IS_HOSTILE);
+
+ SET_GAME_STATE (THRADD_INTRO, 2);
+ }
+ else if (PLAYER_SAID (R, i_need_to_land_lie))
+ {
+ NPCPhrase (CAUGHT_LIE);
+
+ SET_GAME_STATE (THRADD_DEMEANOR, 1);
+ }
+
+ if (!GET_GAME_STATE (THRADD_CULTURE))
+ Response (why_you_here_hostile, HelixWorld);
+ else
+ {
+ Response (demand_to_land, ExitConversation);
+ }
+ switch (GET_GAME_STATE (THRADD_INTRO))
+ {
+ case 0:
+ Response (what_about_this_world, HelixWorld);
+ break;
+ case 1:
+ Response (whats_helix_hostile, HelixWorld);
+ break;
+ }
+ if (!GET_GAME_STATE (THRADD_DEMEANOR))
+ {
+ Response (i_need_to_land_lie, ExitConversation);
+ }
+ Response (bye_hostile_helix, ExitConversation);
+}
+
+static void
+ThraddHostile (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_up_hostile_1))
+ {
+ NPCPhrase (GENERAL_INFO_HOSTILE_1);
+
+ SET_GAME_STATE (THRADD_INFO, 1);
+ DISABLE_PHRASE (whats_up_hostile_2);
+ }
+ else if (PLAYER_SAID (R, whats_up_hostile_2))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (THRADD_INFO);
+ switch (NumVisits++)
+ {
+ case 1:
+ NPCPhrase (GENERAL_INFO_HOSTILE_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_HOSTILE_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_HOSTILE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (THRADD_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_hostile_2);
+ }
+ else if (PLAYER_SAID (R, what_about_you_1))
+ {
+ NPCPhrase (ABOUT_US_1);
+
+ SET_GAME_STATE (THRADD_STACK_1, 1);
+ }
+ else if (PLAYER_SAID (R, what_about_you_2))
+ {
+ NPCPhrase (ABOUT_US_2);
+
+ SET_GAME_STATE (THRADD_STACK_1, 2);
+ }
+ else if (PLAYER_SAID (R, what_about_urquan_1))
+ {
+ NPCPhrase (ABOUT_URQUAN_1);
+
+ SET_GAME_STATE (THRADD_STACK_1, 3);
+ }
+ else if (PLAYER_SAID (R, what_about_urquan_2))
+ {
+ NPCPhrase (ABOUT_URQUAN_2);
+
+ SET_GAME_STATE (THRADD_STACK_1, 4);
+ }
+ else if (PLAYER_SAID (R, be_friends_1))
+ {
+ NPCPhrase (NO_FRIENDS_1);
+
+ SET_GAME_STATE (THRADD_HOSTILE_STACK_3, 1);
+ }
+ else if (PLAYER_SAID (R, be_friends_2))
+ {
+ NPCPhrase (NO_FRIENDS_2);
+ DISABLE_PHRASE (be_friends_2);
+ }
+ else if (PLAYER_SAID (R, how_impressed_urquan_1))
+ {
+ NPCPhrase (IMPRESSED_LIKE_SO_1);
+
+ SET_GAME_STATE (THRADD_HOSTILE_STACK_4, 1);
+ }
+ else if (PLAYER_SAID (R, how_impressed_urquan_2))
+ {
+ NPCPhrase (IMPRESSED_LIKE_SO_2);
+
+ SET_GAME_STATE (THRADD_MISSION, 5);
+ }
+
+ if (GET_GAME_STATE (THRADD_INFO) == 0)
+ Response (whats_up_hostile_1, ThraddHostile);
+ else if (PHRASE_ENABLED (whats_up_hostile_2))
+ Response (whats_up_hostile_2, ThraddHostile);
+ switch (GET_GAME_STATE (THRADD_STACK_1))
+ {
+ case 0:
+ Response (what_about_you_1, ThraddHostile);
+ break;
+ case 1:
+ Response (what_about_you_2, ThraddHostile);
+ break;
+ case 2:
+ Response (what_about_urquan_1, ThraddHostile);
+ break;
+ case 3:
+ Response (what_about_urquan_2, ThraddHostile);
+ break;
+ case 4:
+ if (!GET_GAME_STATE (KOHR_AH_FRENZY))
+ Response (got_idea, ExitConversation);
+ else
+ {
+ SET_GAME_STATE (THRADD_STACK_1, 5);
+ }
+ break;
+ }
+ if (GET_GAME_STATE (THRADD_HOSTILE_STACK_2) == 0)
+ Response (submit_1, ExitConversation);
+ else
+ Response (submit_2, ExitConversation);
+ if (GET_GAME_STATE (THRADD_HOSTILE_STACK_3) == 0)
+ Response (be_friends_1, ThraddHostile);
+ else if (PHRASE_ENABLED (be_friends_2))
+ Response (be_friends_2, ThraddHostile);
+ if (GET_GAME_STATE (THRADD_MISSION) == 4)
+ {
+ if (GET_GAME_STATE (THRADD_HOSTILE_STACK_4) == 0)
+ Response (how_impressed_urquan_1, ThraddHostile);
+ else
+ Response (how_impressed_urquan_2, ThraddHostile);
+ }
+ if (GET_GAME_STATE (THRADD_HOSTILE_STACK_5) == 0)
+ Response (bye_hostile_1, ExitConversation);
+ else
+ Response (bye_hostile_2, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (GET_GAME_STATE (AQUA_HELIX))
+ {
+ NumVisits = GET_GAME_STATE (HELIX_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (DIE_THIEF_1);
+ break;
+ case 1:
+ NPCPhrase (DIE_THIEF_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (HELIX_VISITS, NumVisits);
+
+ setSegue (Segue_hostile);
+ }
+ else if (GET_GAME_STATE (ILWRATH_FIGHT_THRADDASH))
+ {
+ NumVisits = GET_GAME_STATE (THRADD_VISITS);
+ if (GET_GAME_STATE (THRADD_MANNER))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HAVING_FUN_WITH_ILWRATH_1);
+ break;
+ case 1:
+ NPCPhrase (HAVING_FUN_WITH_ILWRATH_2);
+ --NumVisits;
+ break;
+ }
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GO_AWAY_FIGHTING_ILWRATH_1);
+ break;
+ case 1:
+ NPCPhrase (GO_AWAY_FIGHTING_ILWRATH_2);
+ --NumVisits;
+ break;
+ }
+ }
+ SET_GAME_STATE (THRADD_VISITS, NumVisits);
+
+ setSegue (Segue_peace);
+ }
+ else if (GET_GAME_STATE (THRADD_MANNER))
+ {
+ RESPONSE_REF pStr0, pStr1;
+
+ NumVisits = GET_GAME_STATE (THRADD_VISITS);
+ switch (GET_GAME_STATE (THRADD_INTRO))
+ {
+ case 0:
+ PolitePhrase (NumVisits);
+ break;
+ case 1:
+ RhymePhrase (NumVisits);
+ break;
+ case 2:
+ PigLatinPhrase (NumVisits);
+ break;
+ case 3:
+ LikeYouPhrase (NumVisits);
+ break;
+ }
+ if (++NumVisits < 4)
+ {
+ SET_GAME_STATE (THRADD_VISITS, NumVisits);
+ }
+
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ pStr0 = WELCOME_HELIX0;
+ pStr1 = WELCOME_HELIX1;
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ pStr0 = WELCOME_HOMEWORLD0;
+ pStr1 = WELCOME_HOMEWORLD1;
+ }
+ else
+ {
+ pStr0 = WELCOME_SPACE0;
+ pStr1 = WELCOME_SPACE1;
+ }
+ NPCPhrase (pStr0);
+ NPCPhrase (GetCultureName ());
+ NPCPhrase (pStr1);
+
+ ThraddAllies ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ NumVisits = GET_GAME_STATE (HELIX_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_HELIX_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_HELIX_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (HELIX_VISITS, NumVisits);
+
+ HelixWorld ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (THRADDASH_BODY_COUNT) >= THRADDASH_BODY_THRESHOLD)
+ {
+ NPCPhrase (AMAZING_PERFORMANCE);
+
+ ThraddWorship ((RESPONSE_REF)0);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (THRADDASH_BODY_COUNT);
+ if (NumVisits >= 16
+ && GET_GAME_STATE (THRADD_BODY_LEVEL) == 1)
+ {
+ SET_GAME_STATE (THRADD_BODY_LEVEL, 2);
+ NPCPhrase (IMPRESSIVE_PERFORMANCE);
+ }
+ else if (NumVisits >= 8
+ && GET_GAME_STATE (THRADD_BODY_LEVEL) == 0)
+ {
+ SET_GAME_STATE (THRADD_BODY_LEVEL, 1);
+ NPCPhrase (ADEQUATE_PERFORMANCE);
+ }
+
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (THRADD_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (HOSTILE_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (HOSTILE_HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (THRADD_HOME_VISITS, NumVisits);
+ }
+ else if ((NumVisits = GET_GAME_STATE (THRADD_MISSION)) == 0
+ || NumVisits > 3)
+ {
+ NumVisits = GET_GAME_STATE (THRADD_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_SPACE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (HOSTILE_SPACE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (HOSTILE_SPACE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (THRADD_VISITS, NumVisits);
+ }
+ else
+ {
+ switch (NumVisits)
+ {
+ case 1:
+ if (GET_GAME_STATE (THRADD_MISSION_VISITS) == 0)
+ NPCPhrase (WE_GO_TO_IMPRESS_URQUAN_1);
+ else
+ NPCPhrase (WE_GO_TO_IMPRESS_URQUAN_2);
+ break;
+ case 2:
+ if (GET_GAME_STATE (THRADD_MISSION_VISITS) == 0)
+ NPCPhrase (WE_IMPRESSING_URQUAN_1);
+ else
+ NPCPhrase (WE_IMPRESSING_URQUAN_2);
+ break;
+ case 3:
+ if (GET_GAME_STATE (THRADD_MISSION_VISITS) == 0)
+ NPCPhrase (WE_IMPRESSED_URQUAN_1);
+ else
+ NPCPhrase (WE_IMPRESSED_URQUAN_2);
+ break;
+ }
+ SET_GAME_STATE (THRADD_MISSION_VISITS, 1);
+ }
+
+ ThraddHostile ((RESPONSE_REF)0);
+ }
+ }
+}
+
+static COUNT
+uninit_thradd (void)
+{
+ return (0);
+}
+
+static void
+post_thradd_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_thradd_comm (void)
+{
+ LOCDATA *retval;
+
+ thradd_desc.init_encounter_func = Intro;
+ thradd_desc.post_encounter_func = post_thradd_enc;
+ thradd_desc.uninit_encounter_func = uninit_thradd;
+
+ thradd_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ thradd_desc.AlienTextBaseline.y = 0;
+ thradd_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (GET_GAME_STATE (THRADD_MANNER)
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &thradd_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/umgah/Makeinfo b/src/uqm/comm/umgah/Makeinfo
new file mode 100644
index 0000000..9cad008
--- /dev/null
+++ b/src/uqm/comm/umgah/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="umgahc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/umgah/resinst.h b/src/uqm/comm/umgah/resinst.h
new file mode 100644
index 0000000..7b7479d
--- /dev/null
+++ b/src/uqm/comm/umgah/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define UMGAH_COLOR_MAP "comm.umgah.colortable"
+#define UMGAH_CONVERSATION_PHRASES "comm.umgah.dialogue"
+#define UMGAH_FONT "comm.umgah.font"
+#define UMGAH_MUSIC "comm.umgah.music"
+#define UMGAH_PMAP_ANIM "comm.umgah.graphics"
diff --git a/src/uqm/comm/umgah/strings.h b/src/uqm/comm/umgah/strings.h
new file mode 100644
index 0000000..f7dcfed
--- /dev/null
+++ b/src/uqm/comm/umgah/strings.h
@@ -0,0 +1,114 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UMGAH_STRINGS_H
+#define UMGAH_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ HWLD_PRE_ZOMBIE_HELLO_1,
+ HWLD_PRE_ZOMBIE_HELLO_2,
+ HWLD_PRE_ZOMBIE_HELLO_3,
+ HWLD_PRE_ZOMBIE_HELLO_4,
+ SPACE_PRE_ZOMBIE_HELLO_1,
+ SPACE_PRE_ZOMBIE_HELLO_2,
+ SPACE_PRE_ZOMBIE_HELLO_3,
+ SPACE_PRE_ZOMBIE_HELLO_4,
+ UNKNOWN_ZOMBIE_HELLO_1,
+ UNKNOWN_ZOMBIE_HELLO_2,
+ UNKNOWN_ZOMBIE_HELLO_3,
+ UNKNOWN_ZOMBIE_HELLO_4,
+ DESTROY_INTERFERER_1,
+ DESTROY_INTERFERER_2,
+ DESTROY_INTERFERER_3,
+ DESTROY_INTERFERER_4,
+ REVEALED_ZOMBIE_HELLO_1,
+ REVEALED_ZOMBIE_HELLO_2,
+ REVEALED_ZOMBIE_HELLO_3,
+ REVEALED_ZOMBIE_HELLO_4,
+ HOSTILE_HELLO_1,
+ HOSTILE_HELLO_2,
+ HOSTILE_HELLO_3,
+ HOSTILE_HELLO_4,
+ REWARD_AT_HOMEWORLD_1,
+ REWARD_AT_HOMEWORLD_2,
+ POST_ZOMBIE_HWLD_HELLO,
+ owe_me_big_time,
+ our_largesse,
+ GIVE_LIFEDATA,
+ THANKS,
+ what_do_with_tpet,
+ TRICK_URQUAN,
+ any_jokes,
+ SURE,
+ what_before_tpet,
+ TRKD_SPATHI_AND_ILWRATH,
+ where_caster,
+ SPATHI_TOOK_THEM,
+ so_what_for_now,
+ DO_THIS_NOW,
+ bye_post_zombie,
+ FUNNY_IDEA,
+ whats_up_pre_zombie,
+ GENERAL_INFO_PRE_ZOMBIE,
+ evil_blobbies_give_up,
+ NOT_EVIL_BLOBBIES,
+ evil_blobbies_must_die,
+ OH_NO_WE_WONT,
+ can_we_be_friends,
+ SURE_FRIENDS,
+ want_to_defeat_urquan,
+ FINE_BY_US,
+ bye_pre_zombie,
+ GOODBYE_PRE_ZOMBIE,
+ threat,
+ NO_THREAT,
+ whats_up_zombies,
+ GENERAL_INFO_ZOMBIE,
+ how_goes_tpet,
+ WHAT_TPET,
+ you_told_us,
+ SADLY_IT_DIED,
+ dont_believe,
+ THEN_DIE,
+ bye_unknown,
+ GOODBYE_UNKNOWN,
+ evil_blobbies,
+ YES_VERY_EVIL,
+ give_up_or_die,
+ NOT_GIVE_UP,
+ we_vindicator0,
+ we_vindicator1,
+ we_vindicator2,
+ GOOD_FOR_YOU_1,
+ come_in_peace,
+ GOOD_FOR_YOU_2,
+ know_any_jokes,
+ JOKE_1,
+ better_joke,
+ JOKE_2,
+ not_very_funny,
+ YES_WE_ARE,
+ what_about_tpet,
+ arilou_told_us,
+ bye_zombie,
+ GOODBYE_ZOMBIE,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/umgah/umgahc.c b/src/uqm/comm/umgah/umgahc.c
new file mode 100644
index 0000000..d9debd5
--- /dev/null
+++ b/src/uqm/comm/umgah/umgahc.c
@@ -0,0 +1,729 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+
+
+static LOCDATA umgah_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ UMGAH_PMAP_ANIM, /* AlienFrame */
+ UMGAH_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ UMGAH_COLOR_MAP, /* AlienColorMap */
+ UMGAH_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ UMGAH_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 16, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 5, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5),
+ },
+ {
+ 8, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 6),
+ },
+ {
+ 11, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 7),
+ },
+ {
+ 13, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 8),
+ },
+ {
+ 15, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 9),
+ },
+ {
+ 17, /* StartIndex */
+ 3, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 7 / 60, 0, /* FrameRate */
+ ONE_SECOND * 3, ONE_SECOND * 3, /* RestartRate */
+ (1 << 0),
+ },
+ {
+ 20, /* StartIndex */
+ 3, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 7 / 60, 0, /* FrameRate */
+ ONE_SECOND * 3, ONE_SECOND * 3, /* RestartRate */
+ (1 << 1),
+ },
+ {
+ 23, /* StartIndex */
+ 2, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 7 / 60, 0, /* FrameRate */
+ ONE_SECOND * 3, ONE_SECOND * 3, /* RestartRate */
+ (1 << 2),
+ },
+ {
+ 25, /* StartIndex */
+ 2, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 7 / 60, 0, /* FrameRate */
+ ONE_SECOND * 3, ONE_SECOND * 3, /* RestartRate */
+ (1 << 3),
+ },
+ {
+ 27, /* StartIndex */
+ 2, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 7 / 60, 0, /* FrameRate */
+ ONE_SECOND * 3, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4),
+ },
+ {
+ 29, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 32, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 35, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 40, /* StartIndex */
+ 6, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND / 10, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 46, /* StartIndex */
+ 2, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 5, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 15), /* BlockMask */
+ },
+ {
+ 48, /* StartIndex */
+ 2, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 5, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 14), /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 4, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+CombatIsInevitable (RESPONSE_REF R)
+{
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, bye_zombie))
+ {
+ NPCPhrase (GOODBYE_ZOMBIE);
+
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, bye_pre_zombie))
+ NPCPhrase (GOODBYE_PRE_ZOMBIE);
+ else if (PLAYER_SAID (R, can_we_be_friends))
+ {
+ NPCPhrase (SURE_FRIENDS);
+
+ SET_GAME_STATE (UMGAH_MENTIONED_TRICKS, 1);
+ }
+ else if (PLAYER_SAID (R, evil_blobbies_give_up))
+ {
+ NPCPhrase (NOT_EVIL_BLOBBIES);
+
+ SET_GAME_STATE (UMGAH_EVIL_BLOBBIES, 1);
+ }
+ else if (PLAYER_SAID (R, evil_blobbies_must_die))
+ NPCPhrase (OH_NO_WE_WONT);
+ else if (PLAYER_SAID (R, threat))
+ NPCPhrase (NO_THREAT);
+ else if (PLAYER_SAID (R, dont_believe))
+ {
+ NPCPhrase (THEN_DIE);
+
+ SET_GAME_STATE (KNOW_UMGAH_ZOMBIES, 1);
+ SET_GAME_STATE (UMGAH_VISITS, 0);
+ }
+ else if (PLAYER_SAID (R, bye_unknown))
+ {
+ NPCPhrase (GOODBYE_UNKNOWN);
+
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, bye_post_zombie))
+ {
+ NPCPhrase (FUNNY_IDEA);
+
+ AlienTalkSegue ((COUNT)~0);
+ AddEscortShips (UMGAH_SHIP, 4);
+ SET_GAME_STATE (UMGAH_HOSTILE, 1);
+ }
+}
+
+static void
+Zombies (RESPONSE_REF R)
+{
+ if (GET_GAME_STATE (MET_NORMAL_UMGAH))
+ {
+ if (PLAYER_SAID (R, whats_up_zombies))
+ {
+ NPCPhrase (GENERAL_INFO_ZOMBIE);
+
+ DISABLE_PHRASE (whats_up_zombies);
+ }
+ else if (PLAYER_SAID (R, how_goes_tpet))
+ {
+ NPCPhrase (WHAT_TPET);
+
+ DISABLE_PHRASE (how_goes_tpet);
+ }
+ else if (PLAYER_SAID (R, you_told_us))
+ {
+ NPCPhrase (SADLY_IT_DIED);
+
+ DISABLE_PHRASE (you_told_us);
+ }
+
+ if (PHRASE_ENABLED (whats_up_zombies) && PHRASE_ENABLED (how_goes_tpet))
+ Response (whats_up_zombies, Zombies);
+ if (PHRASE_ENABLED (how_goes_tpet))
+ Response (how_goes_tpet, Zombies);
+ else if (PHRASE_ENABLED (you_told_us))
+ Response (you_told_us, Zombies);
+ else
+ {
+ Response (dont_believe, CombatIsInevitable);
+ }
+ if (PHRASE_ENABLED (whats_up_zombies) && !PHRASE_ENABLED (how_goes_tpet))
+ Response (whats_up_zombies, Zombies);
+ Response (threat, CombatIsInevitable);
+ Response (bye_unknown, CombatIsInevitable);
+ }
+ else
+ {
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[4];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = pStr[3] = 0;
+ if (PLAYER_SAID (R, evil_blobbies))
+ {
+ NPCPhrase (YES_VERY_EVIL);
+
+ DISABLE_PHRASE (evil_blobbies);
+ LastStack = 0;
+ }
+ else if (PLAYER_SAID (R, we_vindicator0))
+ {
+ NPCPhrase (GOOD_FOR_YOU_1);
+
+ DISABLE_PHRASE (we_vindicator0);
+ LastStack = 1;
+ }
+ else if (PLAYER_SAID (R, come_in_peace))
+ {
+ NPCPhrase (GOOD_FOR_YOU_2);
+
+ DISABLE_PHRASE (come_in_peace);
+ LastStack = 1;
+ }
+ else if (PLAYER_SAID (R, know_any_jokes))
+ {
+ NPCPhrase (JOKE_1);
+
+ DISABLE_PHRASE (know_any_jokes);
+ LastStack = 2;
+ }
+ else if (PLAYER_SAID (R, better_joke))
+ {
+ NPCPhrase (JOKE_2);
+
+ DISABLE_PHRASE (better_joke);
+ LastStack = 2;
+ }
+ else if (PLAYER_SAID (R, not_very_funny))
+ {
+ NPCPhrase (YES_WE_ARE);
+
+ DISABLE_PHRASE (not_very_funny);
+ LastStack = 2;
+ }
+ else if (PLAYER_SAID (R, what_about_tpet))
+ {
+ NPCPhrase (WHAT_TPET);
+
+ DISABLE_PHRASE (what_about_tpet);
+ LastStack = 3;
+ }
+ else if (PLAYER_SAID (R, give_up_or_die))
+ {
+ NPCPhrase (NOT_GIVE_UP);
+
+ setSegue (Segue_hostile);
+ return;
+ }
+ else if (PLAYER_SAID (R, arilou_told_us))
+ {
+ NPCPhrase (THEN_DIE);
+
+ setSegue (Segue_hostile);
+ SET_GAME_STATE (KNOW_UMGAH_ZOMBIES, 1);
+ SET_GAME_STATE (UMGAH_VISITS, 0);
+ return;
+ }
+
+ if (PHRASE_ENABLED (evil_blobbies))
+ pStr[0] = evil_blobbies;
+ else
+ pStr[0] = give_up_or_die;
+
+ if (PHRASE_ENABLED (we_vindicator0))
+ {
+ construct_response (shared_phrase_buf,
+ we_vindicator0,
+ GLOBAL_SIS (CommanderName),
+ we_vindicator1,
+ GLOBAL_SIS (ShipName),
+ we_vindicator2,
+ (UNICODE*)NULL);
+ pStr[1] = we_vindicator0;
+ }
+ else if (PHRASE_ENABLED (come_in_peace))
+ pStr[1] = come_in_peace;
+
+ if (PHRASE_ENABLED (know_any_jokes))
+ pStr[2] = know_any_jokes;
+ else if (PHRASE_ENABLED (better_joke))
+ pStr[2] = better_joke;
+ else if (PHRASE_ENABLED (not_very_funny))
+ pStr[2] = not_very_funny;
+
+ if (PHRASE_ENABLED (what_about_tpet))
+ pStr[3] = what_about_tpet;
+ else
+ pStr[3] = arilou_told_us;
+
+ if (pStr[LastStack])
+ {
+ if (pStr[LastStack] != we_vindicator0)
+ Response (pStr[LastStack], Zombies);
+ else
+ DoResponsePhrase (pStr[LastStack], Zombies, shared_phrase_buf);
+ }
+ for (i = 0; i < 4; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ {
+ if (pStr[i] != we_vindicator0)
+ Response (pStr[i], Zombies);
+ else
+ DoResponsePhrase (pStr[i], Zombies, shared_phrase_buf);
+ }
+ }
+ Response (bye_zombie, CombatIsInevitable);
+ }
+}
+
+static void
+NormalUmgah (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, whats_up_pre_zombie))
+ {
+ NPCPhrase (GENERAL_INFO_PRE_ZOMBIE);
+
+ DISABLE_PHRASE (whats_up_pre_zombie);
+ }
+ else if (PLAYER_SAID (R, want_to_defeat_urquan))
+ {
+ NPCPhrase (FINE_BY_US);
+
+ DISABLE_PHRASE (want_to_defeat_urquan);
+ }
+
+ if (!GET_GAME_STATE (UMGAH_EVIL_BLOBBIES))
+ Response (evil_blobbies_give_up, CombatIsInevitable);
+ else
+ Response (evil_blobbies_must_die, CombatIsInevitable);
+ if (PHRASE_ENABLED (whats_up_pre_zombie))
+ Response (whats_up_pre_zombie, NormalUmgah);
+ if (PHRASE_ENABLED (want_to_defeat_urquan))
+ Response (want_to_defeat_urquan, NormalUmgah);
+ switch (GET_GAME_STATE (UMGAH_MENTIONED_TRICKS))
+ {
+ case 0:
+ Response (can_we_be_friends, CombatIsInevitable);
+ break;
+ }
+ Response (bye_pre_zombie, CombatIsInevitable);
+}
+
+static void
+UmgahReward (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, what_before_tpet))
+ {
+ NPCPhrase (TRKD_SPATHI_AND_ILWRATH);
+
+ DISABLE_PHRASE (what_before_tpet);
+ }
+ else if (PLAYER_SAID (R, where_caster))
+ {
+ NPCPhrase (SPATHI_TOOK_THEM);
+
+ DISABLE_PHRASE (where_caster);
+ }
+ else if (PLAYER_SAID (R, owe_me_big_time))
+ {
+ NPCPhrase (THANKS);
+
+ GLOBAL_SIS (TotalBioMass) += 1000 / BIO_CREDIT_VALUE;
+ DISABLE_PHRASE (owe_me_big_time);
+ DISABLE_PHRASE (our_largesse);
+ }
+ else if (PLAYER_SAID (R, our_largesse))
+ {
+ NPCPhrase (GIVE_LIFEDATA);
+
+ GLOBAL_SIS (TotalBioMass) += 1000 / BIO_CREDIT_VALUE;
+ DISABLE_PHRASE (our_largesse);
+ DISABLE_PHRASE (owe_me_big_time);
+ }
+ else if (PLAYER_SAID (R, what_do_with_tpet))
+ {
+ NPCPhrase (TRICK_URQUAN);
+
+ DISABLE_PHRASE (what_do_with_tpet);
+ }
+ else if (PLAYER_SAID (R, any_jokes))
+ {
+ NPCPhrase (SURE);
+
+ DISABLE_PHRASE (any_jokes);
+ }
+ else if (PLAYER_SAID (R, so_what_for_now))
+ {
+ NPCPhrase (DO_THIS_NOW);
+
+ DISABLE_PHRASE (so_what_for_now);
+ }
+
+ if (!GET_GAME_STATE (MET_NORMAL_UMGAH))
+ {
+ if (PHRASE_ENABLED (what_before_tpet))
+ Response (what_before_tpet, UmgahReward);
+ else if (PHRASE_ENABLED (where_caster))
+ Response (where_caster, UmgahReward);
+ }
+ if (PHRASE_ENABLED (owe_me_big_time))
+ {
+ Response (owe_me_big_time, UmgahReward);
+ Response (our_largesse, UmgahReward);
+ }
+ if (PHRASE_ENABLED (what_do_with_tpet))
+ Response (what_do_with_tpet, UmgahReward);
+ else if (PHRASE_ENABLED (any_jokes) && GET_GAME_STATE (UMGAH_MENTIONED_TRICKS) < 2)
+ Response (any_jokes, UmgahReward);
+ if (PHRASE_ENABLED (so_what_for_now))
+ Response (so_what_for_now, UmgahReward);
+ Response (bye_post_zombie, CombatIsInevitable);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (GET_GAME_STATE (UMGAH_HOSTILE))
+ {
+ NumVisits = GET_GAME_STATE (UMGAH_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (HOSTILE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (HOSTILE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UMGAH_VISITS, NumVisits);
+
+ setSegue (Segue_hostile);
+ }
+ else if (GET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES))
+ {
+ NumVisits = GET_GAME_STATE (UMGAH_VISITS);
+ if (GET_GAME_STATE (TALKING_PET_VISITS))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (DESTROY_INTERFERER_1);
+ break;
+ case 1:
+ NPCPhrase (DESTROY_INTERFERER_2);
+ break;
+ case 2:
+ NPCPhrase (DESTROY_INTERFERER_3);
+ break;
+ case 3:
+ NPCPhrase (DESTROY_INTERFERER_4);
+ --NumVisits;
+ break;
+ }
+
+ setSegue (Segue_hostile);
+ }
+ else if (GET_GAME_STATE (KNOW_UMGAH_ZOMBIES))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (REVEALED_ZOMBIE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (REVEALED_ZOMBIE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (REVEALED_ZOMBIE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (REVEALED_ZOMBIE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+
+ setSegue (Segue_hostile);
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (UNKNOWN_ZOMBIE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (UNKNOWN_ZOMBIE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (UNKNOWN_ZOMBIE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (UNKNOWN_ZOMBIE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+
+ Zombies ((RESPONSE_REF)0);
+ }
+ SET_GAME_STATE (UMGAH_VISITS, NumVisits);
+ }
+ else if (!GET_GAME_STATE (TALKING_PET))
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (UMGAH_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HWLD_PRE_ZOMBIE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HWLD_PRE_ZOMBIE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (HWLD_PRE_ZOMBIE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (HWLD_PRE_ZOMBIE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UMGAH_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (UMGAH_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (SPACE_PRE_ZOMBIE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (SPACE_PRE_ZOMBIE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (SPACE_PRE_ZOMBIE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (SPACE_PRE_ZOMBIE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UMGAH_VISITS, NumVisits);
+ }
+
+ NormalUmgah ((RESPONSE_REF)0);
+ }
+ else
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NPCPhrase (POST_ZOMBIE_HWLD_HELLO);
+
+ UmgahReward ((RESPONSE_REF)0);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (UMGAH_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (REWARD_AT_HOMEWORLD_1);
+ break;
+ case 1:
+ NPCPhrase (REWARD_AT_HOMEWORLD_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UMGAH_VISITS, NumVisits);
+
+ setSegue (Segue_peace);
+ }
+ }
+}
+
+static COUNT
+uninit_umgah (void)
+{
+ return (0);
+}
+
+static void
+post_umgah_enc (void)
+{
+ if (!GET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES))
+ {
+ SET_GAME_STATE (MET_NORMAL_UMGAH, 1);
+ }
+}
+
+LOCDATA*
+init_umgah_comm (void)
+{
+ LOCDATA *retval;
+
+ umgah_desc.init_encounter_func = Intro;
+ umgah_desc.post_encounter_func = post_umgah_enc;
+ umgah_desc.uninit_encounter_func = uninit_umgah;
+
+ umgah_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ umgah_desc.AlienTextBaseline.y = 0;
+ umgah_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if ((GET_GAME_STATE (TALKING_PET) && !GET_GAME_STATE (UMGAH_HOSTILE))
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &umgah_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/urquan/Makeinfo b/src/uqm/comm/urquan/Makeinfo
new file mode 100644
index 0000000..7b756fa
--- /dev/null
+++ b/src/uqm/comm/urquan/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="urquanc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/urquan/resinst.h b/src/uqm/comm/urquan/resinst.h
new file mode 100644
index 0000000..5b69860
--- /dev/null
+++ b/src/uqm/comm/urquan/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define URQUAN_COLOR_MAP "comm.urquan.colortable"
+#define URQUAN_CONVERSATION_PHRASES "comm.urquan.dialogue"
+#define URQUAN_FONT "comm.urquan.font"
+#define URQUAN_MUSIC "comm.urquan.music"
+#define URQUAN_PMAP_ANIM "comm.urquan.graphics"
diff --git a/src/uqm/comm/urquan/strings.h b/src/uqm/comm/urquan/strings.h
new file mode 100644
index 0000000..18ca86c
--- /dev/null
+++ b/src/uqm/comm/urquan/strings.h
@@ -0,0 +1,101 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#ifndef URQUAN_STRINGS_H
+#define URQUAN_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ HELLO_SAMATRA,
+ SENSE_EVIL,
+ INIT_URQUAN_WAKE_UP,
+ where_am_i,
+ YOU_ARE_HERE,
+ why_does_my_head_hurt,
+ HURTS_BECAUSE,
+ what_about_2_weeks,
+ ABOUT_2_WEEKS,
+ compulsion,
+ WHAT_COMPULSION,
+ wascally_little_guy,
+ WHAT_IT_LOOK_LIKE,
+ terran_amphibian,
+ talking_pet_on_steroids,
+ BAD_NEWS,
+ turd_and_toad,
+ WHAT_IS_TURD_AND_TOAD,
+ alien_mind_control,
+ WHAT_FELT_LIKE,
+ possessed_by_devil,
+ STUPID_DEVIL,
+ falling_asleep,
+ someone_else_controlled,
+ SOUNDS_FAMILIAR,
+ before_coffee,
+ EXPLAIN,
+ why_explain,
+ MUST_EXPLAIN,
+ bye_init_hypno,
+ GOODBYE_AND_DIE_INIT_HYPNO,
+ SUBSEQUENT_URQUAN_WAKE_UP,
+ uh_oh,
+ NO_UH_OH,
+ stop_meeting,
+ NO_STOP_MEETING,
+ bye_sub_hypno,
+ GOODBYE_AND_DIE_SUB_HYPNO,
+ CAUGHT_YA,
+ INIT_FLEE_HUMAN,
+ SUBSEQUENT_FLEE_HUMAN,
+ why_flee,
+ FLEE_BECAUSE,
+ what_happens_now,
+ HAPPENS_NOW,
+ what_about_you,
+ ABOUT_US,
+ bye_wars_over,
+ GOODBYE_WARS_OVER,
+ SEND_MESSAGE,
+ INIT_HELLO,
+ SUBSEQUENT_HELLO_1,
+ SUBSEQUENT_HELLO_2,
+ SUBSEQUENT_HELLO_3,
+ SUBSEQUENT_HELLO_4,
+ you_must_surrender,
+ NOPE,
+ i_surrender,
+ DISOBEDIENCE_PUNISHED,
+ i_wont_surrender,
+ BAD_CHOICE,
+ i_will_surrender,
+ GOOD_CHOICE,
+ key_phrase,
+ URQUAN_STORY,
+ like_to_leave,
+ INDEPENDENCE_IS_BAD,
+ whats_up_1,
+ GENERAL_INFO_1,
+ whats_up_2,
+ GENERAL_INFO_2,
+ whats_up_3,
+ GENERAL_INFO_3,
+ whats_up_4,
+ GENERAL_INFO_4,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/urquan/urquanc.c b/src/uqm/comm/urquan/urquanc.c
new file mode 100644
index 0000000..efdf710
--- /dev/null
+++ b/src/uqm/comm/urquan/urquanc.c
@@ -0,0 +1,555 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+static LOCDATA urquan_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ URQUAN_PMAP_ANIM, /* AlienFrame */
+ URQUAN_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ URQUAN_COLOR_MAP, /* AlienColorMap */
+ URQUAN_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ URQUAN_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 7, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 7, /* StartIndex */
+ 6, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 10, 0, /* FrameRate */
+ ONE_SECOND / 10, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 13, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 16, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 19, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 29, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 36, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 1, /* StartIndex */
+ 2, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 6, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 2, /* StartIndex */
+ 5, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+CombatIsInevitable (RESPONSE_REF R)
+{
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, you_must_surrender))
+ NPCPhrase (NOPE);
+ else if (PLAYER_SAID (R, whats_up_1)
+ || PLAYER_SAID (R, whats_up_2)
+ || PLAYER_SAID (R, whats_up_3)
+ || PLAYER_SAID (R, whats_up_4))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (URQUAN_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (URQUAN_INFO, NumVisits);
+ }
+ else if (PLAYER_SAID (R, i_wont_surrender))
+ NPCPhrase (BAD_CHOICE);
+ else if (PLAYER_SAID (R, i_will_surrender))
+ {
+ NPCPhrase (GOOD_CHOICE);
+
+ setSegue (Segue_defeat);
+ }
+ else if (PLAYER_SAID (R, like_to_leave))
+ NPCPhrase (INDEPENDENCE_IS_BAD);
+ else if (PLAYER_SAID (R, bye_wars_over))
+ {
+ NPCPhrase (GOODBYE_WARS_OVER);
+
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, bye_sub_hypno))
+ NPCPhrase (GOODBYE_AND_DIE_SUB_HYPNO);
+ else if (PLAYER_SAID (R, bye_init_hypno))
+ {
+ NPCPhrase (GOODBYE_AND_DIE_INIT_HYPNO);
+
+ SET_GAME_STATE (URQUAN_HYPNO_VISITS, 1);
+ }
+ else if (PLAYER_SAID (R, terran_amphibian)
+ || PLAYER_SAID (R, talking_pet_on_steroids))
+ {
+ NPCPhrase (BAD_NEWS);
+
+ setSegue (Segue_peace);
+ SET_GAME_STATE (URQUAN_HYPNO_VISITS, 1);
+ }
+ else if (PLAYER_SAID (R, falling_asleep)
+ || PLAYER_SAID (R, someone_else_controlled))
+ {
+ NPCPhrase (SOUNDS_FAMILIAR);
+
+ setSegue (Segue_peace);
+ SET_GAME_STATE (URQUAN_HYPNO_VISITS, 1);
+ }
+}
+
+static void
+DescribePet (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, wascally_little_guy))
+ NPCPhrase (WHAT_IT_LOOK_LIKE);
+ else if (PLAYER_SAID (R, turd_and_toad))
+ {
+ NPCPhrase (WHAT_IS_TURD_AND_TOAD);
+
+ DISABLE_PHRASE (turd_and_toad);
+ }
+
+ Response (terran_amphibian, CombatIsInevitable);
+ Response (talking_pet_on_steroids, CombatIsInevitable);
+ if (PHRASE_ENABLED (turd_and_toad))
+ {
+ Response (turd_and_toad, DescribePet);
+ }
+}
+
+static void
+DescribeCompulsion (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, alien_mind_control))
+ NPCPhrase (WHAT_FELT_LIKE);
+ else if (PLAYER_SAID (R, before_coffee))
+ {
+ NPCPhrase (EXPLAIN);
+
+ DISABLE_PHRASE (before_coffee);
+ }
+
+ Response (falling_asleep, CombatIsInevitable);
+ Response (someone_else_controlled, CombatIsInevitable);
+ if (PHRASE_ENABLED (before_coffee))
+ {
+ Response (before_coffee, DescribeCompulsion);
+ }
+}
+
+static void
+MentionCompulsion (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, compulsion))
+ {
+ NPCPhrase (WHAT_COMPULSION);
+
+ SET_GAME_STATE (MENTIONED_PET_COMPULSION, 1);
+ }
+ else if (PLAYER_SAID (R, possessed_by_devil))
+ {
+ NPCPhrase (STUPID_DEVIL);
+
+ DISABLE_PHRASE (possessed_by_devil);
+ }
+ else if (PLAYER_SAID (R, why_explain))
+ {
+ NPCPhrase (MUST_EXPLAIN);
+
+ DISABLE_PHRASE (why_explain);
+ }
+
+ Response (wascally_little_guy, DescribePet);
+ Response (alien_mind_control, DescribeCompulsion);
+ if (PHRASE_ENABLED (possessed_by_devil))
+ Response (possessed_by_devil, MentionCompulsion);
+ if (PHRASE_ENABLED (why_explain))
+ Response (why_explain, MentionCompulsion);
+}
+
+static void
+UrquanHypno (RESPONSE_REF R)
+{
+ if (GET_GAME_STATE (URQUAN_HYPNO_VISITS) == 0)
+ {
+ if (R == 0)
+ NPCPhrase (INIT_URQUAN_WAKE_UP);
+ else if (PLAYER_SAID (R, where_am_i))
+ {
+ NPCPhrase (YOU_ARE_HERE);
+
+ DISABLE_PHRASE (where_am_i);
+ }
+ else if (PLAYER_SAID (R, why_does_my_head_hurt))
+ {
+ NPCPhrase (HURTS_BECAUSE);
+
+ DISABLE_PHRASE (why_does_my_head_hurt);
+ }
+ else if (PLAYER_SAID (R, what_about_2_weeks))
+ {
+ NPCPhrase (ABOUT_2_WEEKS);
+
+ DISABLE_PHRASE (what_about_2_weeks);
+ }
+
+ if (PHRASE_ENABLED (where_am_i))
+ Response (where_am_i, UrquanHypno);
+ if (PHRASE_ENABLED (why_does_my_head_hurt))
+ Response (why_does_my_head_hurt, UrquanHypno);
+ if (PHRASE_ENABLED (what_about_2_weeks))
+ Response (what_about_2_weeks, UrquanHypno);
+ Response (compulsion, MentionCompulsion);
+ Response (bye_init_hypno, CombatIsInevitable);
+ }
+ else
+ {
+ if (R == 0)
+ NPCPhrase (SUBSEQUENT_URQUAN_WAKE_UP);
+ else if (PLAYER_SAID (R, uh_oh))
+ {
+ NPCPhrase (NO_UH_OH);
+
+ DISABLE_PHRASE (uh_oh);
+ }
+ else if (PLAYER_SAID (R, stop_meeting))
+ {
+ NPCPhrase (NO_STOP_MEETING);
+
+ DISABLE_PHRASE (stop_meeting);
+ }
+
+ if (PHRASE_ENABLED (uh_oh))
+ Response (uh_oh, UrquanHypno);
+ if (PHRASE_ENABLED (stop_meeting))
+ Response (stop_meeting, UrquanHypno);
+ if (!GET_GAME_STATE (MENTIONED_PET_COMPULSION))
+ {
+ Response (compulsion, MentionCompulsion);
+ }
+ Response (bye_sub_hypno, CombatIsInevitable);
+ }
+}
+
+static void
+NormalUrquan (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, i_surrender))
+ {
+ NPCPhrase (DISOBEDIENCE_PUNISHED);
+
+ Response (i_wont_surrender, CombatIsInevitable);
+ Response (i_will_surrender, CombatIsInevitable);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, key_phrase))
+ {
+ NPCPhrase (URQUAN_STORY);
+
+ SET_GAME_STATE (KNOW_URQUAN_STORY, 2);
+ }
+
+ Response (you_must_surrender, CombatIsInevitable);
+ if (GET_GAME_STATE (KNOW_URQUAN_STORY) == 1)
+ {
+ Response (key_phrase, NormalUrquan);
+ }
+ switch (GET_GAME_STATE (URQUAN_INFO))
+ {
+ case 0:
+ Response (whats_up_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (whats_up_2, CombatIsInevitable);
+ break;
+ case 2:
+ Response (whats_up_3, CombatIsInevitable);
+ break;
+ case 3:
+ Response (whats_up_4, CombatIsInevitable);
+ break;
+ }
+ Response (i_surrender, NormalUrquan);
+ Response (like_to_leave, CombatIsInevitable);
+ }
+}
+
+static void
+LoserUrquan (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, why_flee))
+ {
+ NPCPhrase (FLEE_BECAUSE);
+
+ DISABLE_PHRASE (why_flee);
+ }
+ else if (PLAYER_SAID (R, what_happens_now))
+ {
+ NPCPhrase (HAPPENS_NOW);
+
+ DISABLE_PHRASE (what_happens_now);
+ }
+ else if (PLAYER_SAID (R, what_about_you))
+ {
+ NPCPhrase (ABOUT_US);
+
+ DISABLE_PHRASE (what_about_you);
+ }
+
+ if (PHRASE_ENABLED (why_flee))
+ Response (why_flee, LoserUrquan);
+ if (PHRASE_ENABLED (what_happens_now))
+ Response (what_happens_now, LoserUrquan);
+ if (PHRASE_ENABLED (what_about_you))
+ Response (what_about_you, LoserUrquan);
+ Response (bye_wars_over, CombatIsInevitable);
+}
+
+static void
+Intro (void)
+{
+ DWORD GrpOffs;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ GrpOffs = GET_GAME_STATE_32 (URQUAN_PROBE_GRPOFFS0);
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && GLOBAL (BattleGroupRef)
+ && GLOBAL (BattleGroupRef) == GrpOffs)
+ {
+ NPCPhrase (SEND_MESSAGE);
+ SET_GAME_STATE (PROBE_MESSAGE_DELIVERED, 1);
+ }
+ else if (GET_GAME_STATE (PLAYER_HYPNOTIZED))
+ {
+ SetCommIntroMode (CIM_FADE_IN_SCREEN, ONE_SECOND * 5);
+ UrquanHypno ((RESPONSE_REF)0);
+ }
+ else
+ {
+ BYTE NumVisits;
+
+ if (!GET_GAME_STATE (URQUAN_SENSES_EVIL)
+ && GET_GAME_STATE (TALKING_PET_ON_SHIP))
+ {
+ NPCPhrase (SENSE_EVIL);
+ SET_GAME_STATE (URQUAN_SENSES_EVIL, 1);
+ }
+
+ GrpOffs = GET_GAME_STATE_32 (COLONY_GRPOFFS0);
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && GLOBAL (BattleGroupRef)
+ && GLOBAL (BattleGroupRef) == GrpOffs)
+ {
+ NPCPhrase (CAUGHT_YA);
+
+ setSegue (Segue_hostile);
+ return;
+ }
+
+ GrpOffs = GET_GAME_STATE_32 (SAMATRA_GRPOFFS0);
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && GLOBAL (BattleGroupRef)
+ && GLOBAL (BattleGroupRef) == GrpOffs)
+ {
+ NPCPhrase (HELLO_SAMATRA);
+
+ SET_GAME_STATE (AWARE_OF_SAMATRA, 1);
+ setSegue (Segue_hostile);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (URQUAN_VISITS);
+ if (!GET_GAME_STATE (KOHR_AH_FRENZY))
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_HELLO);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_HELLO_1);
+ break;
+ case 2:
+ NPCPhrase (SUBSEQUENT_HELLO_2);
+ break;
+ case 3:
+ NPCPhrase (SUBSEQUENT_HELLO_3);
+ break;
+ case 4:
+ NPCPhrase (SUBSEQUENT_HELLO_4);
+ --NumVisits;
+ break;
+ }
+
+ NormalUrquan ((RESPONSE_REF)0);
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (INIT_FLEE_HUMAN);
+ LoserUrquan ((RESPONSE_REF)0);
+ break;
+ case 1:
+ NPCPhrase (SUBSEQUENT_FLEE_HUMAN);
+ setSegue (Segue_peace);
+ --NumVisits;
+ break;
+ }
+ }
+ SET_GAME_STATE (URQUAN_VISITS, NumVisits);
+ }
+ }
+}
+
+static COUNT
+uninit_urquan (void)
+{
+ return (0);
+}
+
+static void
+post_urquan_enc (void)
+{
+ SET_GAME_STATE (PLAYER_HYPNOTIZED, 0);
+}
+
+LOCDATA*
+init_urquan_comm (void)
+{
+ LOCDATA *retval;
+
+ DWORD GrpOffs;
+
+ urquan_desc.init_encounter_func = Intro;
+ urquan_desc.post_encounter_func = post_urquan_enc;
+ urquan_desc.uninit_encounter_func = uninit_urquan;
+
+ urquan_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ urquan_desc.AlienTextBaseline.y = 0;
+ urquan_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ GrpOffs = GET_GAME_STATE_32 (URQUAN_PROBE_GRPOFFS0);
+ if (GET_GAME_STATE (PLAYER_HYPNOTIZED)
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE
+ || (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && GLOBAL (BattleGroupRef)
+ && GLOBAL (BattleGroupRef) == GrpOffs))
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &urquan_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/utwig/Makeinfo b/src/uqm/comm/utwig/Makeinfo
new file mode 100644
index 0000000..432250e
--- /dev/null
+++ b/src/uqm/comm/utwig/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="utwigc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/utwig/resinst.h b/src/uqm/comm/utwig/resinst.h
new file mode 100644
index 0000000..5176ecb
--- /dev/null
+++ b/src/uqm/comm/utwig/resinst.h
@@ -0,0 +1,10 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define UTWIG_COLOR_MAP "comm.utwig.colortable"
+#define UTWIG_CONVERSATION_PHRASES "comm.utwig.dialogue"
+#define UTWIG_FONT "comm.utwig.font"
+#define UTWIG_MUSIC "comm.utwig.music"
+#define UTWIG_PMAP_ANIM "comm.utwig.graphics"
+#define UTWIG_ULTRON_MUSIC "comm.utwig.ultron.music"
diff --git a/src/uqm/comm/utwig/strings.h b/src/uqm/comm/utwig/strings.h
new file mode 100644
index 0000000..bb6f693
--- /dev/null
+++ b/src/uqm/comm/utwig/strings.h
@@ -0,0 +1,144 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UTWIG_STRINGS_H
+#define UTWIG_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ NEUTRAL_SPACE_HELLO_1,
+ NEUTRAL_SPACE_HELLO_2,
+ HOSTILE_SPACE_HELLO_1,
+ HOSTILE_SPACE_HELLO_2,
+ BOMB_WORLD_HELLO_1,
+ BOMB_WORLD_HELLO_2,
+ HOSTILE_BOMB_HELLO_1,
+ HOSTILE_BOMB_HELLO_2,
+ NEUTRAL_HOMEWORLD_HELLO_1,
+ NEUTRAL_HOMEWORLD_HELLO_2,
+ NEUTRAL_HOMEWORLD_HELLO_3,
+ NEUTRAL_HOMEWORLD_HELLO_4,
+ HOSTILE_HOMEWORLD_HELLO_1,
+ HOSTILE_HOMEWORLD_HELLO_2,
+ why_you_here,
+ WE_GUARD_BOMB,
+ what_about_bomb,
+ ABOUT_BOMB,
+ give_us_bomb_or_die,
+ GUARDS_WARN,
+ demand_bomb,
+ GUARDS_FIGHT,
+ may_we_have_bomb,
+ NO_BOMB,
+ please,
+ SORRY_NO_BOMB,
+ whats_up_bomb,
+ GENERAL_INFO_BOMB_1,
+ GENERAL_INFO_BOMB_2,
+ bye_bomb,
+ GOODBYE_BOMB,
+ hey_wait_got_ultron,
+ TAUNT_US_BUT_WE_LOOK,
+ TRICKED_US_1,
+ TRICKED_US_2,
+ we_are_vindicator0,
+ we_are_vindicator1,
+ we_are_vindicator2,
+ WOULD_BE_HAPPY_BUT,
+ why_sad,
+ ULTRON_BROKE,
+ what_ultron,
+ GLORIOUS_ULTRON,
+ dont_be_babies,
+ MOCK_OUR_PAIN,
+ real_sorry_about_ultron,
+ APPRECIATE_SYMPATHY,
+ what_about_you_1,
+ ABOUT_US_1,
+ what_about_you_2,
+ ABOUT_US_2,
+ what_about_you_3,
+ ABOUT_US_3,
+ what_about_urquan_1,
+ ABOUT_URQUAN_1,
+ what_about_urquan_2,
+ ABOUT_URQUAN_2,
+ got_ultron,
+ DONT_WANT_TO_LOOK,
+ SICK_TRICK_1,
+ SICK_TRICK_2,
+ bye_neutral,
+ GOODBYE_NEUTRAL,
+ TOO_LATE,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ HAPPY_DAYS,
+ OK_ATTACK_KOHRAH,
+ whats_up_after_space,
+ GENERAL_INFO_AFTER_SPACE_1,
+ GENERAL_INFO_AFTER_SPACE_2,
+ what_now_after_space,
+ DO_THIS_AFTER_SPACE,
+ bye_after_space,
+ GOODBYE_AFTER_SPACE,
+ whats_up_before_space,
+ GENERAL_INFO_BEFORE_SPACE_1,
+ GENERAL_INFO_BEFORE_SPACE_2,
+ what_now_before_space,
+ DO_THIS_BEFORE_SPACE,
+ bye_before_space,
+ GOODBYE_BEFORE_SPACE,
+ how_went_war,
+ ABOUT_BATTLE,
+ how_goes_war,
+ BATTLE_HAPPENS_1,
+ BATTLE_HAPPENS_2,
+ FLEET_ON_WAY,
+ learn_new_info,
+ NO_NEW_INFO,
+ SAMATRA,
+ what_now_homeworld,
+ HOPE_KILL_EACH_OTHER,
+ how_is_ultron,
+ ULTRON_IS_GREAT,
+ bye_allied_homeworld,
+ GOODBYE_ALLIED_HOMEWORLD,
+ ALLIED_HOMEWORLD_HELLO_1,
+ ALLIED_HOMEWORLD_HELLO_2,
+ ALLIED_HOMEWORLD_HELLO_3,
+ ALLIED_HOMEWORLD_HELLO_4,
+ HELLO_BEFORE_KOHRAH_SPACE_1,
+ HELLO_BEFORE_KOHRAH_SPACE_2,
+ HELLO_DURING_KOHRAH_SPACE_1,
+ HELLO_DURING_KOHRAH_SPACE_2,
+ HELLO_AFTER_KOHRAH_SPACE_1,
+ HELLO_AFTER_KOHRAH_SPACE_2,
+ UP_TO_YOU,
+ can_you_help,
+ HOW_HELP,
+ DONT_NEED,
+ HAVE_4_SHIPS,
+ NO_ULTRON_AT_BOMB,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/utwig/utwigc.c b/src/uqm/comm/utwig/utwigc.c
new file mode 100644
index 0000000..53bacbb
--- /dev/null
+++ b/src/uqm/comm/utwig/utwigc.c
@@ -0,0 +1,996 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/gameev.h"
+
+
+static LOCDATA utwig_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ UTWIG_PMAP_ANIM, /* AlienFrame */
+ UTWIG_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* SIS_TEXT_WIDTH - 16, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_MIDDLE, /* AlienTextValign */
+ UTWIG_COLOR_MAP, /* AlienColorMap */
+ UTWIG_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ UTWIG_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 16, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 4, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 7, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 2), /* BlockMask */
+ },
+ {
+ 11, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ (1 << 1), /* BlockMask */
+ },
+ {
+ 13, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 12, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 18, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 20, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 22, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 25, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 27, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 30, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 32, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 34, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 36, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 38, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 40, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 42, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND * 2 / 15, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND * 10, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 20, ONE_SECOND / 20, /* FrameRate */
+ ONE_SECOND * 7 / 60, ONE_SECOND / 2, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (R, bye_neutral))
+ NPCPhrase (GOODBYE_NEUTRAL);
+ else if (PLAYER_SAID (R, bye_after_space))
+ NPCPhrase (GOODBYE_AFTER_SPACE);
+ else if (PLAYER_SAID (R, bye_before_space))
+ NPCPhrase (GOODBYE_BEFORE_SPACE);
+ else if (PLAYER_SAID (R, bye_allied_homeworld))
+ NPCPhrase (GOODBYE_ALLIED_HOMEWORLD);
+ else if (PLAYER_SAID (R, bye_bomb))
+ NPCPhrase (GOODBYE_BOMB);
+ else if (PLAYER_SAID (R, demand_bomb))
+ {
+ NPCPhrase (GUARDS_FIGHT);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, got_ultron)
+ || PLAYER_SAID (R, hey_wait_got_ultron))
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ NPCPhrase (NO_ULTRON_AT_BOMB);
+
+ SET_GAME_STATE (REFUSED_ULTRON_AT_BOMB, 1);
+ }
+ else
+ {
+ if (PLAYER_SAID (R, got_ultron))
+ NPCPhrase (DONT_WANT_TO_LOOK);
+ else
+ NPCPhrase (TAUNT_US_BUT_WE_LOOK);
+ if (GET_GAME_STATE (ULTRON_CONDITION) < 4)
+ {
+ switch (GET_GAME_STATE (UTWIG_INFO))
+ {
+ case 0:
+ if (PLAYER_SAID (R, got_ultron))
+ NPCPhrase (SICK_TRICK_1);
+ else
+ {
+ NPCPhrase (TRICKED_US_1);
+
+ setSegue (Segue_hostile);
+ }
+ break;
+ case 1:
+ if (PLAYER_SAID (R, got_ultron))
+ NPCPhrase (SICK_TRICK_2);
+ else
+ {
+ NPCPhrase (TRICKED_US_2);
+
+ setSegue (Segue_hostile);
+ }
+ break;
+ }
+ SET_GAME_STATE (UTWIG_INFO, 1);
+ }
+ else
+ {
+ NPCPhrase (HAPPY_DAYS);
+ if (GET_GAME_STATE (KOHR_AH_FRENZY))
+ NPCPhrase (TOO_LATE);
+ else
+ {
+ NPCPhrase (OK_ATTACK_KOHRAH);
+
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, ADVANCE_UTWIG_SUPOX_MISSION);
+ }
+
+ SET_GAME_STATE (UTWIG_HAVE_ULTRON, 1);
+ SET_GAME_STATE (ULTRON_CONDITION, 5);
+
+ SET_GAME_STATE (UTWIG_VISITS, 0);
+ SET_GAME_STATE (SUPOX_VISITS, 0);
+ SET_GAME_STATE (UTWIG_HOME_VISITS, 0);
+ SET_GAME_STATE (SUPOX_HOME_VISITS, 0);
+ SET_GAME_STATE (BOMB_VISITS, 0);
+
+ SET_GAME_STATE (SUPOX_INFO, 0);
+ SET_GAME_STATE (UTWIG_INFO, 0);
+ SET_GAME_STATE (SUPOX_WAR_NEWS, 0);
+ SET_GAME_STATE (UTWIG_WAR_NEWS, 0);
+ SET_GAME_STATE (SUPOX_HOSTILE, 0);
+ SET_GAME_STATE (UTWIG_HOSTILE, 0);
+
+ SetRaceAllied (UTWIG_SHIP, TRUE);
+ SetRaceAllied (SUPOX_SHIP, TRUE);
+ }
+ }
+ }
+ else if (PLAYER_SAID (R, can_you_help))
+ {
+ NPCPhrase (HOW_HELP);
+ if (EscortFeasibilityStudy (UTWIG_SHIP) == 0)
+ NPCPhrase (DONT_NEED);
+ else
+ {
+ NPCPhrase (HAVE_4_SHIPS);
+
+ AlienTalkSegue ((COUNT)~0);
+ AddEscortShips (UTWIG_SHIP, 4);
+ }
+ }
+}
+
+static void AlliedHome (RESPONSE_REF R);
+
+static void
+AlliedHome (RESPONSE_REF R)
+{
+ BYTE NumVisits, News;
+
+ News = GET_GAME_STATE (UTWIG_WAR_NEWS);
+ NumVisits = GET_GAME_STATE (UTWIG_SUPOX_MISSION);
+ if (PLAYER_SAID (R, how_went_war))
+ {
+ NPCPhrase (ABOUT_BATTLE);
+
+ News |= (1 << 0);
+ }
+ else if (PLAYER_SAID (R, how_goes_war))
+ {
+ if (NumVisits == 1)
+ {
+ NPCPhrase (FLEET_ON_WAY);
+
+ SET_GAME_STATE (UTWIG_WAR_NEWS, 1);
+ }
+ else switch (GET_GAME_STATE (UTWIG_WAR_NEWS))
+ {
+ case 0:
+ NPCPhrase (BATTLE_HAPPENS_1);
+ News = 1;
+ break;
+ case 1:
+ NPCPhrase (BATTLE_HAPPENS_2);
+ News = 2;
+ break;
+ }
+
+ DISABLE_PHRASE (how_goes_war);
+ }
+ else if (PLAYER_SAID (R, learn_new_info))
+ {
+ if (NumVisits < 5)
+ NPCPhrase (NO_NEW_INFO);
+ else
+ {
+ NPCPhrase (SAMATRA);
+
+ News |= (1 << 1);
+ }
+
+ DISABLE_PHRASE (learn_new_info);
+ }
+ else if (PLAYER_SAID (R, what_now_homeworld))
+ {
+ if (NumVisits < 5)
+ NPCPhrase (UP_TO_YOU);
+ else
+ NPCPhrase (HOPE_KILL_EACH_OTHER);
+
+ DISABLE_PHRASE (what_now_homeworld);
+ }
+ else if (PLAYER_SAID (R, how_is_ultron))
+ {
+ NPCPhrase (ULTRON_IS_GREAT);
+
+ DISABLE_PHRASE (how_is_ultron);
+ }
+ SET_GAME_STATE (UTWIG_WAR_NEWS, News);
+
+ if (NumVisits >= 5)
+ {
+ if (!(News & (1 << 0)))
+ Response (how_went_war, AlliedHome);
+ }
+ else if (PHRASE_ENABLED (how_goes_war)
+ && ((NumVisits == 1 && News == 0)
+ || (NumVisits && News < 2)))
+ Response (how_goes_war, AlliedHome);
+ if (PHRASE_ENABLED (learn_new_info))
+ Response (learn_new_info, AlliedHome);
+ if (PHRASE_ENABLED (what_now_homeworld))
+ Response (what_now_homeworld, AlliedHome);
+ if (PHRASE_ENABLED (how_is_ultron))
+ Response (how_is_ultron, AlliedHome);
+ if (NumVisits == 0 && EscortFeasibilityStudy (UTWIG_SHIP) != 0)
+ Response (can_you_help, ExitConversation);
+ Response (bye_allied_homeworld, ExitConversation);
+}
+
+static void
+BeforeKohrAh (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, whats_up_before_space))
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_BEFORE_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_BEFORE_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_before_space);
+ }
+ else if (PLAYER_SAID (R, what_now_before_space))
+ {
+ NPCPhrase (DO_THIS_BEFORE_SPACE);
+
+ DISABLE_PHRASE (what_now_before_space);
+ }
+
+ if (PHRASE_ENABLED (whats_up_before_space))
+ Response (whats_up_before_space, BeforeKohrAh);
+ if (PHRASE_ENABLED (what_now_before_space))
+ Response (what_now_before_space, BeforeKohrAh);
+ Response (bye_before_space, ExitConversation);
+}
+
+static void
+AfterKohrAh (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, whats_up_after_space))
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_AFTER_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_AFTER_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_after_space);
+ }
+ else if (PLAYER_SAID (R, what_now_after_space))
+ {
+ NPCPhrase (DO_THIS_AFTER_SPACE);
+
+ DISABLE_PHRASE (what_now_after_space);
+ }
+
+ if (PHRASE_ENABLED (whats_up_after_space))
+ Response (whats_up_after_space, AfterKohrAh);
+ if (PHRASE_ENABLED (what_now_after_space))
+ Response (what_now_after_space, AfterKohrAh);
+ Response (bye_after_space, ExitConversation);
+}
+
+static void
+NeutralUtwig (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[4];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = pStr[3] = 0;
+ if (PLAYER_SAID (R, we_are_vindicator0))
+ {
+ NPCPhrase (WOULD_BE_HAPPY_BUT);
+
+ SET_GAME_STATE (UTWIG_STACK1, 1);
+ }
+ else if (PLAYER_SAID (R, why_sad))
+ {
+ NPCPhrase (ULTRON_BROKE);
+
+ SET_GAME_STATE (UTWIG_STACK1, 2);
+ }
+ else if (PLAYER_SAID (R, what_ultron))
+ {
+ NPCPhrase (GLORIOUS_ULTRON);
+
+ SET_GAME_STATE (UTWIG_STACK1, 3);
+ }
+ else if (PLAYER_SAID (R, dont_be_babies))
+ {
+ NPCPhrase (MOCK_OUR_PAIN);
+
+ setSegue (Segue_hostile);
+ SET_GAME_STATE (UTWIG_STACK1, 4);
+ SET_GAME_STATE (UTWIG_HOSTILE, 1);
+ SET_GAME_STATE (UTWIG_INFO, 0);
+ SET_GAME_STATE (UTWIG_HOME_VISITS, 0);
+ SET_GAME_STATE (UTWIG_VISITS, 0);
+ SET_GAME_STATE (BOMB_VISITS, 0);
+ return;
+ }
+ else if (PLAYER_SAID (R, real_sorry_about_ultron))
+ {
+ NPCPhrase (APPRECIATE_SYMPATHY);
+
+ SET_GAME_STATE (UTWIG_STACK1, 4);
+ return;
+ }
+ else if (PLAYER_SAID (R, what_about_you_1))
+ {
+ NPCPhrase (ABOUT_US_1);
+
+ LastStack = 2;
+ SET_GAME_STATE (UTWIG_WAR_NEWS, 1);
+ }
+ else if (PLAYER_SAID (R, what_about_you_2))
+ {
+ NPCPhrase (ABOUT_US_2);
+
+ LastStack = 2;
+ StartSphereTracking (SUPOX_SHIP);
+ SET_GAME_STATE (UTWIG_WAR_NEWS, 2);
+ }
+ else if (PLAYER_SAID (R, what_about_you_3))
+ {
+ NPCPhrase (ABOUT_US_3);
+
+ SET_GAME_STATE (UTWIG_WAR_NEWS, 3);
+ }
+ else if (PLAYER_SAID (R, what_about_urquan_1))
+ {
+ NPCPhrase (ABOUT_URQUAN_1);
+
+ LastStack = 3;
+ SET_GAME_STATE (UTWIG_STACK2, 1);
+ }
+ else if (PLAYER_SAID (R, what_about_urquan_2))
+ {
+ NPCPhrase (ABOUT_URQUAN_2);
+
+ SET_GAME_STATE (UTWIG_STACK2, 2);
+ }
+
+ switch (GET_GAME_STATE (UTWIG_STACK1))
+ {
+ case 0:
+ {
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ we_are_vindicator0,
+ GLOBAL_SIS (CommanderName),
+ we_are_vindicator1,
+ buf,
+ we_are_vindicator2,
+ (UNICODE*)NULL);
+ }
+ pStr[0] = we_are_vindicator0;
+ break;
+ case 1:
+ pStr[0] = why_sad;
+ break;
+ case 2:
+ pStr[0] = what_ultron;
+ break;
+ case 3:
+ pStr[0] = dont_be_babies;
+ pStr[1] = real_sorry_about_ultron;
+ break;
+ }
+ switch (GET_GAME_STATE (UTWIG_WAR_NEWS))
+ {
+ case 0:
+ pStr[2] = what_about_you_1;
+ break;
+ case 1:
+ pStr[2] = what_about_you_2;
+ break;
+ case 2:
+ pStr[2] = what_about_you_3;
+ break;
+ }
+ switch (GET_GAME_STATE (UTWIG_STACK2))
+ {
+ case 0:
+ pStr[2] = what_about_urquan_1;
+ break;
+ case 1:
+ pStr[2] = what_about_urquan_2;
+ break;
+ }
+
+ if (pStr[LastStack])
+ {
+ if (pStr[LastStack] != we_are_vindicator0)
+ Response (pStr[LastStack], NeutralUtwig);
+ else
+ DoResponsePhrase (pStr[LastStack], NeutralUtwig, shared_phrase_buf);
+ }
+ for (i = 0; i < 4; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ {
+ if (pStr[i] != we_are_vindicator0)
+ Response (pStr[i], NeutralUtwig);
+ else
+ DoResponsePhrase (pStr[i], NeutralUtwig, shared_phrase_buf);
+ }
+ }
+ if (GET_GAME_STATE (ULTRON_CONDITION))
+ Response (got_ultron, ExitConversation);
+ Response (bye_neutral, ExitConversation);
+}
+
+static void
+BombWorld (RESPONSE_REF R)
+{
+ BYTE LastStack;
+ RESPONSE_REF pStr[2];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = 0;
+ if (PLAYER_SAID (R, why_you_here))
+ {
+ NPCPhrase (WE_GUARD_BOMB);
+
+ SET_GAME_STATE (BOMB_STACK1, 1);
+ }
+ else if (PLAYER_SAID (R, what_about_bomb))
+ {
+ NPCPhrase (ABOUT_BOMB);
+
+ SET_GAME_STATE (BOMB_STACK1, 2);
+ }
+ else if (PLAYER_SAID (R, give_us_bomb_or_die))
+ {
+ NPCPhrase (GUARDS_WARN);
+
+ SET_GAME_STATE (BOMB_STACK1, 3);
+ }
+ else if (PLAYER_SAID (R, demand_bomb))
+ {
+ NPCPhrase (GUARDS_FIGHT);
+
+ setSegue (Segue_hostile);
+ SET_GAME_STATE (UTWIG_HOSTILE, 1);
+ SET_GAME_STATE (UTWIG_INFO, 0);
+ SET_GAME_STATE (UTWIG_HOME_VISITS, 0);
+ SET_GAME_STATE (UTWIG_VISITS, 0);
+ SET_GAME_STATE (BOMB_VISITS, 0);
+ return;
+ }
+ else if (PLAYER_SAID (R, may_we_have_bomb))
+ {
+ NPCPhrase (NO_BOMB);
+
+ LastStack = 1;
+ SET_GAME_STATE (BOMB_STACK2, 1);
+ }
+ else if (PLAYER_SAID (R, please))
+ {
+ NPCPhrase (SORRY_NO_BOMB);
+
+ SET_GAME_STATE (BOMB_STACK2, 2);
+ }
+ else if (PLAYER_SAID (R, whats_up_bomb))
+ {
+ if (GET_GAME_STATE (BOMB_INFO))
+ NPCPhrase (GENERAL_INFO_BOMB_2);
+ else
+ {
+ NPCPhrase (GENERAL_INFO_BOMB_1);
+
+ SET_GAME_STATE (BOMB_INFO, 1);
+ }
+
+ DISABLE_PHRASE (whats_up_bomb);
+ }
+
+ switch (GET_GAME_STATE (BOMB_STACK2))
+ {
+ case 0:
+ pStr[1] = may_we_have_bomb;
+ break;
+ case 1:
+ pStr[1] = please;
+ break;
+ }
+ switch (GET_GAME_STATE (BOMB_STACK1))
+ {
+ case 0:
+ pStr[0] = why_you_here;
+ pStr[1] = 0;
+ break;
+ case 1:
+ pStr[0] = what_about_bomb;
+ pStr[1] = 0;
+ break;
+ case 2:
+ pStr[0] = give_us_bomb_or_die;
+ break;
+ case 3:
+ pStr[0] = demand_bomb;
+ break;
+ }
+
+ if (pStr[LastStack])
+ Response (pStr[LastStack], BombWorld);
+ LastStack ^= 1;
+ if (pStr[LastStack])
+ Response (pStr[LastStack], BombWorld);
+
+ if (PHRASE_ENABLED (whats_up_bomb) && (GET_GAME_STATE (BOMB_STACK1) > 1))
+ Response (whats_up_bomb, BombWorld);
+
+ if (GET_GAME_STATE (ULTRON_CONDITION)
+ && !GET_GAME_STATE (REFUSED_ULTRON_AT_BOMB))
+ Response (got_ultron, ExitConversation);
+
+ if (GET_GAME_STATE (BOMB_INFO))
+ {
+ Response (bye_bomb, ExitConversation);
+ }
+ else
+ {
+ Response (bye_neutral, ExitConversation);
+ }
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (GET_GAME_STATE (UTWIG_HOSTILE))
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ NumVisits = GET_GAME_STATE (BOMB_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_BOMB_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_BOMB_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (BOMB_VISITS, NumVisits);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_HOMEWORLD_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOSTILE_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOSTILE_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_VISITS, NumVisits);
+ }
+
+ if (!GET_GAME_STATE (ULTRON_CONDITION)
+ || (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6)))
+ {
+ setSegue (Segue_hostile);
+ }
+ else
+ {
+ Response (hey_wait_got_ultron, ExitConversation);
+ }
+ }
+ else if (CheckAlliance (UTWIG_SHIP) == GOOD_GUY)
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (ALLIED_HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_HOME_VISITS, NumVisits);
+
+ AlliedHome ((RESPONSE_REF)0);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_SUPOX_MISSION);
+ if (NumVisits == 1)
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_BEFORE_KOHRAH_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_BEFORE_KOHRAH_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_VISITS, NumVisits);
+
+ BeforeKohrAh ((RESPONSE_REF)0);
+ }
+ else if (NumVisits < 5)
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_DURING_KOHRAH_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_DURING_KOHRAH_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HELLO_AFTER_KOHRAH_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (HELLO_AFTER_KOHRAH_SPACE_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_VISITS, NumVisits);
+
+ AfterKohrAh ((RESPONSE_REF)0);
+ }
+ }
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ NumVisits = GET_GAME_STATE (BOMB_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (BOMB_WORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (BOMB_WORLD_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (BOMB_VISITS, NumVisits);
+
+ BombWorld ((RESPONSE_REF)0);
+ }
+ else
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (NEUTRAL_HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (UTWIG_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (NEUTRAL_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (UTWIG_VISITS, NumVisits);
+ }
+
+ NeutralUtwig ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_utwig (void)
+{
+ return (0);
+}
+
+static void
+post_utwig_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_utwig_comm (void)
+{
+ LOCDATA *retval;
+
+ utwig_desc.init_encounter_func = Intro;
+ utwig_desc.post_encounter_func = post_utwig_enc;
+ utwig_desc.uninit_encounter_func = uninit_utwig;
+
+ utwig_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1);
+ utwig_desc.AlienTextBaseline.y = 70;
+ utwig_desc.AlienTextWidth = SIS_TEXT_WIDTH - 16;
+
+ if (GET_GAME_STATE (UTWIG_HAVE_ULTRON))
+ { // use alternate 'Happy Utwig!' track
+ utwig_desc.AlienAltSongRes = UTWIG_ULTRON_MUSIC;
+ utwig_desc.AlienSongFlags |= LDASF_USE_ALTERNATE;
+ }
+ else
+ { // regular track -- let's make sure
+ utwig_desc.AlienSongFlags &= ~LDASF_USE_ALTERNATE;
+ }
+
+ if (GET_GAME_STATE (UTWIG_HAVE_ULTRON)
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &utwig_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/vux/Makeinfo b/src/uqm/comm/vux/Makeinfo
new file mode 100644
index 0000000..da9446f
--- /dev/null
+++ b/src/uqm/comm/vux/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="vuxc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/vux/resinst.h b/src/uqm/comm/vux/resinst.h
new file mode 100644
index 0000000..5004523
--- /dev/null
+++ b/src/uqm/comm/vux/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define VUX_COLOR_MAP "comm.vux.colortable"
+#define VUX_CONVERSATION_PHRASES "comm.vux.dialogue"
+#define VUX_FONT "comm.vux.font"
+#define VUX_MUSIC "comm.vux.music"
+#define VUX_PMAP_ANIM "comm.vux.graphics"
diff --git a/src/uqm/comm/vux/strings.h b/src/uqm/comm/vux/strings.h
new file mode 100644
index 0000000..5bf58b7
--- /dev/null
+++ b/src/uqm/comm/vux/strings.h
@@ -0,0 +1,129 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef VUX_STRINGS_H
+#define VUX_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ ZEX_HELLO_1,
+ ZEX_HELLO_2,
+ ZEX_HELLO_3,
+ ZEX_HELLO_4,
+ FIGHT_OR_TRADE_1,
+ FIGHT_OR_TRADE_2,
+ what_you_do_here,
+ MY_MENAGERIE,
+ what_about_menagerie,
+ NEED_NEW_CREATURE,
+ what_about_creature,
+ ABOUT_CREATURE,
+ about_creature_again,
+ CREATURE_AGAIN,
+ i_have_beast,
+ GIVE_BEAST,
+ ok_take_beast,
+ FOOL_AIEE0,
+ FOOL_AIEE1,
+ why_trust_1,
+ TRUST_1,
+ why_trust_2,
+ TRUST_2,
+ why_trust_3,
+ TRUST_3,
+ why_dont_you_attack,
+ LIKE_YOU,
+ why_like_me,
+ LIKE_BECAUSE,
+ are_you_a_pervert,
+ CALL_ME_WHAT_YOU_WISH,
+ take_by_force,
+ PRECURSOR_DEVICE,
+ regardless,
+ THEN_FIGHT,
+ you_lied,
+ YUP_LIED,
+ kill_you,
+ FIGHT_AGAIN,
+ bye_zex,
+ GOODBYE_ZEX,
+ HOMEWORLD_HELLO_1,
+ HOMEWORLD_HELLO_2,
+ HOMEWORLD_HELLO_3,
+ HOMEWORLD_HELLO_4,
+ SPACE_HELLO_1,
+ SPACE_HELLO_2,
+ SPACE_HELLO_3,
+ SPACE_HELLO_4,
+ kill_you_squids_1,
+ kill_you_squids_2,
+ kill_you_squids_3,
+ kill_you_squids_4,
+ WE_FIGHT,
+ why_so_mean,
+ URQUAN_SLAVES,
+ deeper_reason,
+ OLD_INSULT,
+ if_we_apologize,
+ PROBABLY_NOT,
+ try_any_way,
+ NOPE,
+ APOLOGIZE_IN_SPACE,
+ apology_1,
+ NOT_ACCEPTED_1,
+ apology_2,
+ NOT_ACCEPTED_2,
+ apology_3,
+ NOT_ACCEPTED_3,
+ apology_4,
+ NOT_ACCEPTED_4,
+ apology_5,
+ NOT_ACCEPTED_5,
+ apology_6,
+ NOT_ACCEPTED_6,
+ apology_7,
+ NOT_ACCEPTED_7,
+ apology_8,
+ NOT_ACCEPTED_8,
+ apology_9,
+ NOT_ACCEPTED_9,
+ apology_10,
+ TRUTH,
+ whats_up_hostile,
+ GENERAL_INFO_HOSTILE_1,
+ GENERAL_INFO_HOSTILE_2,
+ GENERAL_INFO_HOSTILE_3,
+ GENERAL_INFO_HOSTILE_4,
+ cant_we_be_friends_1,
+ NEVER_UGLY_HUMANS_1,
+ cant_we_be_friends_2,
+ NEVER_UGLY_HUMANS_2,
+ cant_we_be_friends_3,
+ NEVER_UGLY_HUMANS_3,
+ cant_we_be_friends_4,
+ NEVER_UGLY_HUMANS_4,
+ bye_hostile_space,
+ GOODBYE_AND_DIE_HOSTILE_SPACE_1,
+ GOODBYE_AND_DIE_HOSTILE_SPACE_2,
+ GOODBYE_AND_DIE_HOSTILE_SPACE_3,
+ GOODBYE_AND_DIE_HOSTILE_SPACE_4,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/vux/vuxc.c b/src/uqm/comm/vux/vuxc.c
new file mode 100644
index 0000000..7f7419c
--- /dev/null
+++ b/src/uqm/comm/vux/vuxc.c
@@ -0,0 +1,796 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+static LOCDATA vux_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ VUX_PMAP_ANIM, /* AlienFrame */
+ VUX_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* (SIS_TEXT_WIDTH - 16) >> 1, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_TOP, /* AlienTextValign */
+ VUX_COLOR_MAP, /* AlienColorMap */
+ VUX_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ VUX_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 17, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ {
+ 12, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 15, /* StartIndex */
+ 5, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 20, /* StartIndex */
+ 14, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND / 30, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 34, /* StartIndex */
+ 7, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 41, /* StartIndex */
+ 6, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 47, /* StartIndex */
+ 11, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 58, /* StartIndex */
+ 3, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 61, /* StartIndex */
+ 4, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 65, /* StartIndex */
+ 4, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 69, /* StartIndex */
+ 2, /* NumFrames */
+ RANDOM_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 71, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 74, /* StartIndex */
+ 6, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 80, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* RestartRate */
+ (1 << 14), /* BlockMask */
+ },
+ {
+ 85, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 90, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* RestartRate */
+ (1 << 12), /* BlockMask */
+ },
+ {
+ 95, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 5, ONE_SECOND * 5,/* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 99, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 5, ONE_SECOND * 5,/* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 11, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+CombatIsInevitable (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, ok_take_beast))
+ {
+ NPCPhrase (FOOL_AIEE0);
+ NPCPhrase (FOOL_AIEE1);
+
+ AlienTalkSegue (1);
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap, 1)
+ ), ONE_SECOND / 4);
+ AlienTalkSegue ((COUNT)~0);
+
+ SET_GAME_STATE (VUX_BEAST_ON_SHIP, 0);
+ SET_GAME_STATE (ZEX_IS_DEAD, 1);
+ setSegue (Segue_peace);
+ }
+ else if (PLAYER_SAID (R, try_any_way))
+ {
+ NPCPhrase (NOPE);
+
+ SET_GAME_STATE (VUX_STACK_1, 4);
+ }
+ else if (PLAYER_SAID (R, kill_you_squids_1)
+ || PLAYER_SAID (R, kill_you_squids_2)
+ || PLAYER_SAID (R, kill_you_squids_3)
+ || PLAYER_SAID (R, kill_you_squids_4))
+ {
+ NPCPhrase (WE_FIGHT);
+
+ NumVisits = GET_GAME_STATE (VUX_STACK_2) + 1;
+ if (NumVisits <= 3)
+ {
+ SET_GAME_STATE (VUX_STACK_2, NumVisits);
+ }
+ }
+ else if (PLAYER_SAID (R, cant_we_be_friends_1)
+ || PLAYER_SAID (R, cant_we_be_friends_2)
+ || PLAYER_SAID (R, cant_we_be_friends_3)
+ || PLAYER_SAID (R, cant_we_be_friends_4))
+ {
+ NumVisits = GET_GAME_STATE (VUX_STACK_3);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (NEVER_UGLY_HUMANS_1);
+ break;
+ case 1:
+ NPCPhrase (NEVER_UGLY_HUMANS_2);
+ break;
+ case 2:
+ NPCPhrase (NEVER_UGLY_HUMANS_3);
+ break;
+ case 3:
+ NPCPhrase (NEVER_UGLY_HUMANS_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (VUX_STACK_3, NumVisits);
+ }
+ else if (PLAYER_SAID (R, bye_hostile_space))
+ {
+ NumVisits = GET_GAME_STATE (VUX_STACK_4);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GOODBYE_AND_DIE_HOSTILE_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GOODBYE_AND_DIE_HOSTILE_SPACE_2);
+ break;
+ case 2:
+ NPCPhrase (GOODBYE_AND_DIE_HOSTILE_SPACE_3);
+ break;
+ case 3:
+ NPCPhrase (GOODBYE_AND_DIE_HOSTILE_SPACE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (VUX_STACK_4, NumVisits);
+ }
+ else if (PLAYER_SAID (R, bye_zex))
+ {
+ NPCPhrase (GOODBYE_ZEX);
+
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (VUX_STACK_1);
+ switch (NumVisits++)
+ {
+ case 4:
+ NPCPhrase (NOT_ACCEPTED_1);
+ break;
+ case 5:
+ NPCPhrase (NOT_ACCEPTED_2);
+ break;
+ case 6:
+ NPCPhrase (NOT_ACCEPTED_3);
+ break;
+ case 7:
+ NPCPhrase (NOT_ACCEPTED_4);
+ break;
+ case 8:
+ NPCPhrase (NOT_ACCEPTED_5);
+ break;
+ case 9:
+ NPCPhrase (NOT_ACCEPTED_6);
+ break;
+ case 10:
+ NPCPhrase (NOT_ACCEPTED_7);
+ break;
+ case 11:
+ NPCPhrase (NOT_ACCEPTED_8);
+ break;
+ case 12:
+ NPCPhrase (NOT_ACCEPTED_9);
+ break;
+ case 13:
+ NPCPhrase (TRUTH);
+ break;
+ }
+ SET_GAME_STATE (VUX_STACK_1, NumVisits);
+ }
+}
+
+static void
+Menagerie (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[3];
+
+ if (PLAYER_SAID (R, i_have_beast)
+ || PLAYER_SAID (R, why_trust_1)
+ || PLAYER_SAID (R, why_trust_2)
+ || PLAYER_SAID (R, why_trust_3))
+ {
+ if (PLAYER_SAID (R, i_have_beast))
+ NPCPhrase (GIVE_BEAST);
+ else if (PLAYER_SAID (R, why_trust_1))
+ {
+ NPCPhrase (TRUST_1);
+
+ DISABLE_PHRASE (why_trust_1);
+ }
+ else if (PLAYER_SAID (R, why_trust_2))
+ {
+ NPCPhrase (TRUST_2);
+
+ DISABLE_PHRASE (why_trust_2);
+ }
+ else if (PLAYER_SAID (R, why_trust_3))
+ {
+ NPCPhrase (TRUST_3);
+
+ DISABLE_PHRASE (why_trust_3);
+ }
+
+ if (PHRASE_ENABLED (why_trust_1))
+ Response (why_trust_1, Menagerie);
+ else if (PHRASE_ENABLED (why_trust_2))
+ Response (why_trust_2, Menagerie);
+ else if (PHRASE_ENABLED (why_trust_3))
+ Response (why_trust_3, Menagerie);
+ Response (ok_take_beast, CombatIsInevitable);
+ }
+ else if (PLAYER_SAID (R, kill_you))
+ {
+ NPCPhrase (FIGHT_AGAIN);
+
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, regardless))
+ {
+ NPCPhrase (THEN_FIGHT);
+
+ setSegue (Segue_hostile);
+ SET_GAME_STATE (ZEX_STACK_3, 2);
+ SET_GAME_STATE (ZEX_VISITS, 0);
+ }
+ else
+ {
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = 0;
+ if (R == 0)
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (ZEX_VISITS);
+ if (GET_GAME_STATE (ZEX_STACK_3) >= 2)
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (FIGHT_OR_TRADE_1);
+ break;
+ case 1:
+ NPCPhrase (FIGHT_OR_TRADE_2);
+ --NumVisits;
+ break;
+ }
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ZEX_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ZEX_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (ZEX_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (ZEX_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ }
+ SET_GAME_STATE (ZEX_VISITS, NumVisits);
+ }
+ else if (PLAYER_SAID (R, what_you_do_here))
+ {
+ NPCPhrase (MY_MENAGERIE);
+
+ SET_GAME_STATE (ZEX_STACK_1, 1);
+ }
+ else if (PLAYER_SAID (R, what_about_menagerie))
+ {
+ NPCPhrase (NEED_NEW_CREATURE);
+
+ SET_GAME_STATE (ZEX_STACK_1, 2);
+ }
+ else if (PLAYER_SAID (R, what_about_creature))
+ {
+ NPCPhrase (ABOUT_CREATURE);
+
+ SET_GAME_STATE (KNOW_ZEX_WANTS_MONSTER, 1);
+ SET_GAME_STATE (ZEX_STACK_1, 3);
+
+ R = about_creature_again;
+ DISABLE_PHRASE (what_about_creature);
+ }
+ else if (PLAYER_SAID (R, about_creature_again))
+ {
+ NPCPhrase (CREATURE_AGAIN);
+
+ DISABLE_PHRASE (about_creature_again);
+ }
+ else if (PLAYER_SAID (R, why_dont_you_attack))
+ {
+ NPCPhrase (LIKE_YOU);
+
+ LastStack = 1;
+ SET_GAME_STATE (ZEX_STACK_2, 1);
+ }
+ else if (PLAYER_SAID (R, why_like_me))
+ {
+ NPCPhrase (LIKE_BECAUSE);
+
+ LastStack = 1;
+ SET_GAME_STATE (ZEX_STACK_2, 2);
+ }
+ else if (PLAYER_SAID (R, are_you_a_pervert))
+ {
+ NPCPhrase (CALL_ME_WHAT_YOU_WISH);
+
+ SET_GAME_STATE (ZEX_STACK_2, 3);
+ }
+ else if (PLAYER_SAID (R, take_by_force))
+ {
+ NPCPhrase (PRECURSOR_DEVICE);
+
+ LastStack = 2;
+ SET_GAME_STATE (ZEX_STACK_3, 1);
+ }
+ else if (PLAYER_SAID (R, you_lied))
+ {
+ NPCPhrase (YUP_LIED);
+
+ LastStack = 2;
+ SET_GAME_STATE (ZEX_STACK_3, 3);
+ }
+
+ if (GET_GAME_STATE (KNOW_ZEX_WANTS_MONSTER)
+ && GET_GAME_STATE (VUX_BEAST_ON_SHIP))
+ pStr[0] = i_have_beast;
+ else
+ {
+ switch (GET_GAME_STATE (ZEX_STACK_1))
+ {
+ case 0:
+ pStr[0] = what_you_do_here;
+ break;
+ case 1:
+ pStr[0] = what_about_menagerie;
+ break;
+ case 2:
+ pStr[0] = what_about_creature;
+ break;
+ case 3:
+ if (PHRASE_ENABLED (about_creature_again))
+ pStr[0] = about_creature_again;
+ break;
+ }
+ }
+ switch (GET_GAME_STATE (ZEX_STACK_2))
+ {
+ case 0:
+ pStr[1] = why_dont_you_attack;
+ break;
+ case 1:
+ pStr[1] = why_like_me;
+ break;
+ case 2:
+ pStr[1] = are_you_a_pervert;
+ break;
+ }
+ switch (GET_GAME_STATE (ZEX_STACK_3))
+ {
+ case 0:
+ pStr[2] = take_by_force;
+ break;
+ case 1:
+ pStr[2] = regardless;
+ break;
+ case 2:
+ pStr[2] = you_lied;
+ break;
+ case 3:
+ pStr[2] = kill_you;
+ break;
+ }
+
+ if (pStr[LastStack])
+ Response (pStr[LastStack], Menagerie);
+ for (i = 0; i < 3; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ Response (pStr[i], Menagerie);
+ }
+ Response (bye_zex, CombatIsInevitable);
+ }
+}
+
+static void
+NormalVux (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, why_so_mean))
+ {
+ NPCPhrase (URQUAN_SLAVES);
+
+ SET_GAME_STATE (VUX_STACK_1, 1);
+ }
+ else if (PLAYER_SAID (R, deeper_reason))
+ {
+ NPCPhrase (OLD_INSULT);
+
+ SET_GAME_STATE (VUX_STACK_1, 2);
+ }
+ else if (PLAYER_SAID (R, if_we_apologize))
+ {
+ NPCPhrase (PROBABLY_NOT);
+
+ SET_GAME_STATE (VUX_STACK_1, 3);
+ }
+ else if (PLAYER_SAID (R, whats_up_hostile))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (VUX_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_HOSTILE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_HOSTILE_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_HOSTILE_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_HOSTILE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (VUX_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_hostile);
+ }
+
+ switch (GET_GAME_STATE (VUX_STACK_1))
+ {
+ case 0:
+ Response (why_so_mean, NormalVux);
+ break;
+ case 1:
+ Response (deeper_reason, NormalVux);
+ break;
+ case 2:
+ Response (if_we_apologize, NormalVux);
+ break;
+ case 3:
+ Response (try_any_way, CombatIsInevitable);
+ break;
+ case 4:
+ Response (apology_1, CombatIsInevitable);
+ break;
+ case 5:
+ Response (apology_2, CombatIsInevitable);
+ break;
+ case 6:
+ Response (apology_3, CombatIsInevitable);
+ break;
+ case 7:
+ Response (apology_4, CombatIsInevitable);
+ break;
+ case 8:
+ Response (apology_5, CombatIsInevitable);
+ break;
+ case 9:
+ Response (apology_6, CombatIsInevitable);
+ break;
+ case 10:
+ Response (apology_7, CombatIsInevitable);
+ break;
+ case 11:
+ Response (apology_8, CombatIsInevitable);
+ break;
+ case 12:
+ Response (apology_9, CombatIsInevitable);
+ break;
+ case 13:
+ Response (apology_10, CombatIsInevitable);
+ break;
+ }
+
+ switch (GET_GAME_STATE (VUX_STACK_2))
+ {
+ case 0:
+ Response (kill_you_squids_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (kill_you_squids_2, CombatIsInevitable);
+ break;
+ case 2:
+ Response (kill_you_squids_3, CombatIsInevitable);
+ break;
+ case 3:
+ Response (kill_you_squids_4, CombatIsInevitable);
+ break;
+ }
+
+ if (PHRASE_ENABLED (whats_up_hostile))
+ {
+ Response (whats_up_hostile, NormalVux);
+ }
+
+ if (GET_GAME_STATE (VUX_STACK_1) > 13)
+ {
+ switch (GET_GAME_STATE (VUX_STACK_3))
+ {
+ case 0:
+ Response (cant_we_be_friends_1, CombatIsInevitable);
+ break;
+ case 1:
+ Response (cant_we_be_friends_2, CombatIsInevitable);
+ break;
+ case 2:
+ Response (cant_we_be_friends_3, CombatIsInevitable);
+ break;
+ case 3:
+ Response (cant_we_be_friends_4, CombatIsInevitable);
+ break;
+ }
+ }
+
+ Response (bye_hostile_space, CombatIsInevitable);
+}
+
+static void
+Intro (void)
+{
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ {
+ Menagerie ((RESPONSE_REF)0);
+ }
+ else
+ {
+ BYTE NumVisits;
+
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (VUX_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOMEWORLD_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (HOMEWORLD_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (HOMEWORLD_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (VUX_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (VUX_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (SPACE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (SPACE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (SPACE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (VUX_VISITS, NumVisits);
+ }
+
+ NormalVux ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_vux (void)
+{
+ return (0);
+}
+
+static void
+post_vux_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_vux_comm (void)
+{
+ LOCDATA *retval;
+
+ vux_desc.init_encounter_func = Intro;
+ vux_desc.post_encounter_func = post_vux_enc;
+ vux_desc.uninit_encounter_func = uninit_vux;
+
+ vux_desc.AlienTextBaseline.x = TEXT_X_OFFS + (SIS_TEXT_WIDTH >> 1)
+ + (SIS_TEXT_WIDTH >> 2);
+ vux_desc.AlienTextBaseline.y = 0;
+ vux_desc.AlienTextWidth = (SIS_TEXT_WIDTH - 16) >> 1;
+
+ if ((GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 6))
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+ retval = &vux_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/yehat/Makeinfo b/src/uqm/comm/yehat/Makeinfo
new file mode 100644
index 0000000..f38674b
--- /dev/null
+++ b/src/uqm/comm/yehat/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="yehatc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/yehat/resinst.h b/src/uqm/comm/yehat/resinst.h
new file mode 100644
index 0000000..3b0b203
--- /dev/null
+++ b/src/uqm/comm/yehat/resinst.h
@@ -0,0 +1,11 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define REBEL_CONVERSATION_PHRASES "comm.yehat.rebel.dialogue"
+#define REBEL_MUSIC "comm.yehat.rebel.music"
+#define YEHAT_COLOR_MAP "comm.yehat.colortable"
+#define YEHAT_CONVERSATION_PHRASES "comm.yehat.dialogue"
+#define YEHAT_FONT "comm.yehat.font"
+#define YEHAT_MUSIC "comm.yehat.music"
+#define YEHAT_PMAP_ANIM "comm.yehat.graphics"
diff --git a/src/uqm/comm/yehat/strings.h b/src/uqm/comm/yehat/strings.h
new file mode 100644
index 0000000..9bea2e0
--- /dev/null
+++ b/src/uqm/comm/yehat/strings.h
@@ -0,0 +1,102 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef YEHAT_STRINGS_H
+#define YEHAT_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ HOMEWORLD_HELLO_1,
+ HOMEWORLD_HELLO_2,
+ whats_up_homeworld,
+ GENERAL_INFO_HOMEWORLD_1,
+ GENERAL_INFO_HOMEWORLD_2,
+ i_demand_you_ally_homeworld0,
+ i_demand_you_ally_homeworld1,
+ i_demand_you_ally_homeworld2,
+ i_demand_you_ally_homeworld3,
+ ENEMY_MUST_DIE,
+ at_least_help_us_homeworld,
+ NO_HELP_ENEMY,
+ give_info,
+ NO_INFO_FOR_ENEMY,
+ what_about_pkunk_royalist,
+ PKUNK_ABSORBED_ROYALIST,
+ HATE_PKUNK_ROYALIST,
+ bye_homeworld,
+ GOODBYE_AND_DIE_HOMEWORLD,
+ SPACE_HELLO_1,
+ SPACE_HELLO_2,
+ SPACE_HELLO_3,
+ SPACE_HELLO_4,
+ whats_up_space_1,
+ GENERAL_INFO_SPACE_1,
+ whats_up_space_2,
+ GENERAL_INFO_SPACE_2,
+ whats_up_space_3,
+ GENERAL_INFO_SPACE_3,
+ whats_up_space_4,
+ GENERAL_INFO_SPACE_4,
+ i_demand_you_ally_space0,
+ i_demand_you_ally_space1,
+ i_demand_you_ally_space2,
+ i_demand_you_ally_space3,
+ WE_CANNOT_1,
+ obligation,
+ WE_CANNOT_2,
+ at_least_help_us_space,
+ SORRY_CANNOT,
+ dishonor,
+ HERES_A_HINT,
+ what_about_pkunk_space,
+ PKUNK_ABSORBED_SPACE,
+ HATE_PKUNK_SPACE,
+ bye_space,
+ GO_IN_PEACE,
+ GOODBYE_AND_DIE_SPACE,
+ shofixti_alive_1,
+ shofixti_alive_2,
+ SEND_HIM_OVER_1,
+ SEND_HIM_OVER_2,
+ not_here,
+ not_send,
+ JUST_A_TRICK_1,
+ JUST_A_TRICK_2,
+ ok_send,
+ WE_REVOLT,
+ ROYALIST_SPACE_HELLO_1,
+ ROYALIST_SPACE_HELLO_2,
+ ROYALIST_HOMEWORLD_HELLO_1,
+ ROYALIST_HOMEWORLD_HELLO_2,
+ how_is_rebellion,
+ ROYALIST_REBELLION_1,
+ ROYALIST_REBELLION_2,
+ sorry_about_revolution,
+ ALL_YOUR_FAULT,
+ bye_royalist,
+ GOODBYE_AND_DIE_ROYALIST,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ OUT_TAKES,
+};
+
+#endif /* _STRINGS_H */
diff --git a/src/uqm/comm/yehat/yehatc.c b/src/uqm/comm/yehat/yehatc.c
new file mode 100644
index 0000000..c1860bf
--- /dev/null
+++ b/src/uqm/comm/yehat/yehatc.c
@@ -0,0 +1,685 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/gameev.h"
+#include "libs/mathlib.h"
+
+
+static LOCDATA yehat_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ YEHAT_PMAP_ANIM, /* AlienFrame */
+ YEHAT_FONT, /* AlienFont */
+ WHITE_COLOR_INIT, /* AlienTextFColor */
+ BLACK_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* (SIS_TEXT_WIDTH - 16) * 2 / 3, */ /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_MIDDLE, /* AlienTextValign */
+ YEHAT_COLOR_MAP, /* AlienColorMap */
+ YEHAT_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ YEHAT_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 15, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ { /* right hand-wing tapping keyboard; front guy */
+ 4, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 10, /* FrameRate */
+ ONE_SECOND / 4, ONE_SECOND / 2,/* RestartRate */
+ (1 << 6) | (1 << 7),
+ },
+ { /* left hand-wing tapping keyboard; front guy */
+ 7, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM
+ | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 10, ONE_SECOND / 10, /* FrameRate */
+ ONE_SECOND / 4, ONE_SECOND / 2,/* RestartRate */
+ (1 << 6) | (1 << 7),
+ },
+ {
+ 10, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 4) | (1 << 14),
+ },
+ {
+ 13, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 20, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 5),
+ },
+ {
+ 16, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 6, ONE_SECOND * 3,/* RestartRate */
+ (1 << 2) | (1 << 14),
+ },
+ {
+ 21, /* StartIndex */
+ 5, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 6, ONE_SECOND * 3,/* RestartRate */
+ (1 << 3),
+ },
+ { /* right arm-wing rising; front guy */
+ 26, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 6, ONE_SECOND * 3,/* RestartRate */
+ (1 << 0) | (1 << 1),
+ },
+ { /* left arm-wing rising; front guy */
+ 28, /* StartIndex */
+ 2, /* NumFrames */
+ YOYO_ANIM | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 15, ONE_SECOND / 15, /* FrameRate */
+ ONE_SECOND * 6, ONE_SECOND * 3,/* RestartRate */
+ (1 << 0) | (1 << 1),
+ },
+ {
+ 30, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 33, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 36, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 39, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 42, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 45, /* StartIndex */
+ 3, /* NumFrames */
+ YOYO_ANIM, /* AnimFlags */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* FrameRate */
+ ONE_SECOND / 30, ONE_SECOND / 30, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ {
+ 48, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM | WAIT_TALKING, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ ONE_SECOND, ONE_SECOND * 3, /* RestartRate */
+ (1 << 2) | (1 << 4),
+ },
+ },
+ { /* AlienTransitionDesc - empty */
+ 0, /* StartIndex */
+ 0, /* NumFrames */
+ 0, /* AnimFlags */
+ 0, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ 1, /* StartIndex */
+ 3, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_hostile);
+
+ if (PLAYER_SAID (R, bye_homeworld))
+ NPCPhrase (GOODBYE_AND_DIE_HOMEWORLD);
+ else if (PLAYER_SAID (R, bye_royalist))
+ NPCPhrase (GOODBYE_AND_DIE_ROYALIST);
+ else if (PLAYER_SAID (R, i_demand_you_ally_homeworld0))
+ {
+ NPCPhrase (ENEMY_MUST_DIE);
+
+ SET_GAME_STATE (NO_YEHAT_ALLY_HOME, 1);
+ }
+ else if (PLAYER_SAID (R, bye_space))
+ {
+ if ((BYTE)TFB_Random () & 1)
+ NPCPhrase (GOODBYE_AND_DIE_SPACE);
+ else
+ {
+ NPCPhrase (GO_IN_PEACE);
+
+ setSegue (Segue_peace);
+ }
+ }
+ else if (PLAYER_SAID (R, not_here)
+ || PLAYER_SAID (R, not_send))
+ {
+ switch (GET_GAME_STATE (YEHAT_REBEL_VISITS))
+ {
+ case 0:
+ NPCPhrase (JUST_A_TRICK_1);
+ break;
+ case 1:
+ NPCPhrase (JUST_A_TRICK_2);
+ break;
+ }
+ SET_GAME_STATE (YEHAT_REBEL_VISITS, 1);
+ }
+ else if (PLAYER_SAID (R, ok_send))
+ {
+ NPCPhrase (WE_REVOLT);
+
+ setSegue (Segue_peace);
+ SET_GAME_STATE (YEHAT_CIVIL_WAR, 1);
+ SET_GAME_STATE (YEHAT_VISITS, 0);
+ SET_GAME_STATE (YEHAT_HOME_VISITS, 0);
+ SET_GAME_STATE (YEHAT_REBEL_VISITS, 0);
+ SET_GAME_STATE (YEHAT_REBEL_INFO, 0);
+ SET_GAME_STATE (YEHAT_REBEL_TOLD_PKUNK, 0);
+ SET_GAME_STATE (NO_YEHAT_INFO, 0);
+
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, YEHAT_REBEL_EVENT);
+ }
+}
+
+static void
+Royalists (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, how_is_rebellion))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (YEHAT_ROYALIST_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ROYALIST_REBELLION_1);
+ break;
+ case 1:
+ NPCPhrase (ROYALIST_REBELLION_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_ROYALIST_INFO, NumVisits);
+
+ DISABLE_PHRASE (how_is_rebellion);
+ }
+ else if (PLAYER_SAID (R, what_about_pkunk_royalist))
+ {
+ if (GET_GAME_STATE (YEHAT_ABSORBED_PKUNK))
+ NPCPhrase (PKUNK_ABSORBED_ROYALIST);
+ else
+ NPCPhrase (HATE_PKUNK_ROYALIST);
+
+ SET_GAME_STATE (YEHAT_ROYALIST_TOLD_PKUNK, 1);
+ }
+ else if (PLAYER_SAID (R, sorry_about_revolution))
+ {
+ NPCPhrase (ALL_YOUR_FAULT);
+
+ SET_GAME_STATE (NO_YEHAT_INFO, 1);
+ }
+
+ if (PHRASE_ENABLED (how_is_rebellion))
+ Response (how_is_rebellion, Royalists);
+ if (!GET_GAME_STATE (YEHAT_ROYALIST_TOLD_PKUNK)
+ && GET_GAME_STATE (PKUNK_VISITS)
+ && GET_GAME_STATE (PKUNK_HOME_VISITS))
+ Response (what_about_pkunk_royalist, Royalists);
+ if (!GET_GAME_STATE (NO_YEHAT_INFO))
+ Response (sorry_about_revolution, Royalists);
+ Response (bye_royalist, ExitConversation);
+}
+
+static void
+StartRevolt (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, shofixti_alive_1))
+ {
+ NPCPhrase (SEND_HIM_OVER_1);
+
+ SET_GAME_STATE (YEHAT_REBEL_TOLD_PKUNK, 1);
+ }
+ else if (PLAYER_SAID (R, shofixti_alive_2))
+ NPCPhrase (SEND_HIM_OVER_2);
+
+ if (HaveEscortShip (SHOFIXTI_SHIP))
+ Response (ok_send, ExitConversation);
+ else
+ Response (not_here, ExitConversation);
+ Response (not_send, ExitConversation);
+}
+
+static void
+YehatHome (RESPONSE_REF R)
+{
+
+ if (PLAYER_SAID (R, whats_up_homeworld))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (YEHAT_ROYALIST_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_HOMEWORLD_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_HOMEWORLD_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_ROYALIST_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_homeworld);
+ }
+ else if (PLAYER_SAID (R, at_least_help_us_homeworld))
+ {
+ NPCPhrase (NO_HELP_ENEMY);
+
+ SET_GAME_STATE (NO_YEHAT_HELP_HOME, 1);
+ }
+ else if (PLAYER_SAID (R, give_info))
+ {
+ NPCPhrase (NO_INFO_FOR_ENEMY);
+
+ SET_GAME_STATE (NO_YEHAT_INFO, 1);
+ }
+ else if (PLAYER_SAID (R, what_about_pkunk_royalist))
+ {
+ if (GET_GAME_STATE (YEHAT_ABSORBED_PKUNK))
+ NPCPhrase (PKUNK_ABSORBED_ROYALIST);
+ else
+ NPCPhrase (HATE_PKUNK_ROYALIST);
+
+ SET_GAME_STATE (YEHAT_ROYALIST_TOLD_PKUNK, 1);
+ }
+
+ if (PHRASE_ENABLED (whats_up_homeworld))
+ Response (whats_up_homeworld, YehatHome);
+ if (!GET_GAME_STATE (YEHAT_ROYALIST_TOLD_PKUNK)
+ && GET_GAME_STATE (PKUNK_VISITS)
+ && GET_GAME_STATE (PKUNK_HOME_VISITS))
+ Response (what_about_pkunk_royalist, YehatHome);
+ if (!GET_GAME_STATE (NO_YEHAT_HELP_HOME))
+ Response (at_least_help_us_homeworld, YehatHome);
+ if (!GET_GAME_STATE (NO_YEHAT_INFO))
+ Response (give_info, YehatHome);
+ if (!GET_GAME_STATE (NO_YEHAT_ALLY_HOME))
+ {
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ i_demand_you_ally_homeworld0,
+ GLOBAL_SIS (CommanderName),
+ i_demand_you_ally_homeworld1,
+ buf,
+ i_demand_you_ally_homeworld2,
+ GLOBAL_SIS (ShipName),
+ i_demand_you_ally_homeworld3,
+ (UNICODE*)NULL);
+ DoResponsePhrase (i_demand_you_ally_homeworld0,
+ ExitConversation, shared_phrase_buf);
+ }
+ Response (bye_homeworld, ExitConversation);
+}
+
+static void
+YehatSpace (RESPONSE_REF R)
+{
+ BYTE i, LastStack;
+ RESPONSE_REF pStr[3];
+
+ LastStack = 0;
+ pStr[0] = pStr[1] = pStr[2] = 0;
+ if (PLAYER_SAID (R, whats_up_space_1)
+ || PLAYER_SAID (R, whats_up_space_2)
+ || PLAYER_SAID (R, whats_up_space_3)
+ || PLAYER_SAID (R, whats_up_space_4))
+ {
+ BYTE NumVisits;
+
+ NumVisits = GET_GAME_STATE (YEHAT_REBEL_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (GENERAL_INFO_SPACE_1);
+ break;
+ case 1:
+ NPCPhrase (GENERAL_INFO_SPACE_2);
+ break;
+ case 2:
+ NPCPhrase (GENERAL_INFO_SPACE_3);
+ break;
+ case 3:
+ NPCPhrase (GENERAL_INFO_SPACE_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_REBEL_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_space_1);
+ }
+ else if (PLAYER_SAID (R, i_demand_you_ally_space0))
+ {
+ NPCPhrase (WE_CANNOT_1);
+
+ LastStack = 2;
+ SET_GAME_STATE (NO_YEHAT_ALLY_SPACE, 1);
+ }
+ else if (PLAYER_SAID (R, obligation))
+ {
+ NPCPhrase (WE_CANNOT_2);
+
+ setSegue (Segue_peace);
+ SET_GAME_STATE (NO_YEHAT_ALLY_SPACE, 2);
+
+ return;
+ }
+ else if (PLAYER_SAID (R, at_least_help_us_space))
+ {
+ NPCPhrase (SORRY_CANNOT);
+
+ LastStack = 1;
+ SET_GAME_STATE (NO_YEHAT_HELP_SPACE, 1);
+ }
+ else if (PLAYER_SAID (R, dishonor))
+ {
+ NPCPhrase (HERES_A_HINT);
+
+ SET_GAME_STATE (NO_YEHAT_HELP_SPACE, 2);
+ }
+ else if (PLAYER_SAID (R, what_about_pkunk_royalist))
+ {
+ if (GET_GAME_STATE (YEHAT_ABSORBED_PKUNK))
+ NPCPhrase (PKUNK_ABSORBED_ROYALIST);
+ else
+ NPCPhrase (HATE_PKUNK_ROYALIST);
+
+ SET_GAME_STATE (YEHAT_ROYALIST_TOLD_PKUNK, 1);
+ }
+
+// SET_FUNCPTR (&PtrDesc, YehatSpace);
+ if (PHRASE_ENABLED (whats_up_space_1))
+ {
+ switch (GET_GAME_STATE (YEHAT_REBEL_INFO))
+ {
+ case 0:
+ pStr[0] = whats_up_space_1;
+ break;
+ case 1:
+ pStr[0] = whats_up_space_2;
+ break;
+ case 2:
+ pStr[0] = whats_up_space_3;
+ break;
+ case 3:
+ pStr[0] = whats_up_space_4;
+ break;
+ }
+ }
+ switch (GET_GAME_STATE (NO_YEHAT_HELP_SPACE))
+ {
+ case 0:
+ pStr[1] = at_least_help_us_space;
+ break;
+ case 1:
+ pStr[1] = dishonor;
+ break;
+ }
+ switch (GET_GAME_STATE (NO_YEHAT_ALLY_SPACE))
+ {
+ case 0:
+ {
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ i_demand_you_ally_space0,
+ GLOBAL_SIS (CommanderName),
+ i_demand_you_ally_space1,
+ GLOBAL_SIS (ShipName),
+ i_demand_you_ally_space2,
+ buf,
+ i_demand_you_ally_space3,
+ (UNICODE*)NULL);
+ pStr[2] = i_demand_you_ally_space0;
+ break;
+ }
+ case 1:
+ pStr[2] = obligation;
+ break;
+ }
+
+ if (pStr[LastStack])
+ {
+ if (pStr[LastStack] != i_demand_you_ally_space0)
+ Response (pStr[LastStack], YehatSpace);
+ else
+ DoResponsePhrase (pStr[LastStack], YehatSpace, shared_phrase_buf);
+ }
+ for (i = 0; i < 3; ++i)
+ {
+ if (i != LastStack && pStr[i])
+ {
+ if (pStr[i] != i_demand_you_ally_space0)
+ Response (pStr[i], YehatSpace);
+ else
+ DoResponsePhrase (pStr[i], YehatSpace, shared_phrase_buf);
+ }
+ }
+ if (!GET_GAME_STATE (YEHAT_ROYALIST_TOLD_PKUNK)
+ && GET_GAME_STATE (PKUNK_VISITS)
+ && GET_GAME_STATE (PKUNK_HOME_VISITS))
+ Response (what_about_pkunk_royalist, YehatSpace);
+ if (GET_GAME_STATE (SHOFIXTI_VISITS))
+ {
+ switch (GET_GAME_STATE (YEHAT_REBEL_TOLD_PKUNK))
+ {
+ case 0:
+ Response (shofixti_alive_1, StartRevolt);
+ break;
+ case 1:
+ Response (shofixti_alive_2, StartRevolt);
+ break;
+ }
+ }
+ Response (bye_space, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase (OUT_TAKES);
+
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (GET_GAME_STATE (YEHAT_CIVIL_WAR))
+ {
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (YEHAT_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ROYALIST_HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ROYALIST_HOMEWORLD_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_HOME_VISITS, NumVisits);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (YEHAT_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (ROYALIST_SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (ROYALIST_SPACE_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_VISITS, NumVisits);
+ }
+
+ Royalists ((RESPONSE_REF)0);
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NumVisits = GET_GAME_STATE (YEHAT_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (HOMEWORLD_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (HOMEWORLD_HELLO_2);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_HOME_VISITS, NumVisits);
+
+ YehatHome ((RESPONSE_REF)0);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (YEHAT_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase (SPACE_HELLO_1);
+ break;
+ case 1:
+ NPCPhrase (SPACE_HELLO_2);
+ break;
+ case 2:
+ NPCPhrase (SPACE_HELLO_3);
+ break;
+ case 3:
+ NPCPhrase (SPACE_HELLO_4);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (YEHAT_VISITS, NumVisits);
+
+ YehatSpace ((RESPONSE_REF)0);
+ }
+}
+
+static COUNT
+uninit_yehat (void)
+{
+ return (0);
+}
+
+static void
+post_yehat_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_yehat_comm (void)
+{
+ LOCDATA *retval;
+
+ yehat_desc.init_encounter_func = Intro;
+ yehat_desc.post_encounter_func = post_yehat_enc;
+ yehat_desc.uninit_encounter_func = uninit_yehat;
+
+ yehat_desc.AlienTextBaseline.x = SIS_SCREEN_WIDTH * 2 / 3;
+ yehat_desc.AlienTextBaseline.y = 60;
+ yehat_desc.AlienTextWidth = (SIS_TEXT_WIDTH - 16) * 2 / 3;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE)
+ {
+ setSegue (Segue_hostile);
+ }
+ else
+ {
+ setSegue (Segue_peace);
+ }
+ retval = &yehat_desc;
+
+ return (retval);
+}
diff --git a/src/uqm/comm/zoqfot/Makeinfo b/src/uqm/comm/zoqfot/Makeinfo
new file mode 100644
index 0000000..6e1a268
--- /dev/null
+++ b/src/uqm/comm/zoqfot/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="zoqfotc.c"
+uqm_HFILES="resinst.h strings.h"
diff --git a/src/uqm/comm/zoqfot/resinst.h b/src/uqm/comm/zoqfot/resinst.h
new file mode 100644
index 0000000..a9430d9
--- /dev/null
+++ b/src/uqm/comm/zoqfot/resinst.h
@@ -0,0 +1,9 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ZOQFOTPIK_COLOR_MAP "comm.zoqfotpik.colortable"
+#define ZOQFOTPIK_CONVERSATION_PHRASES "comm.zoqfotpik.dialogue"
+#define ZOQFOTPIK_FONT "comm.zoqfotpik.font"
+#define ZOQFOTPIK_MUSIC "comm.zoqfotpik.music"
+#define ZOQFOTPIK_PMAP_ANIM "comm.zoqfotpik.graphics"
diff --git a/src/uqm/comm/zoqfot/strings.h b/src/uqm/comm/zoqfot/strings.h
new file mode 100644
index 0000000..3278082
--- /dev/null
+++ b/src/uqm/comm/zoqfot/strings.h
@@ -0,0 +1,365 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ZOQFOT_STRINGS_H
+#define ZOQFOT_STRINGS_H
+
+enum
+{
+ NULL_PHRASE,
+ WE_ARE0,
+ WE_ARE1,
+ WE_ARE2,
+ WE_ARE3,
+ WE_ARE4,
+ WE_ARE5,
+ WE_ARE6,
+ WE_ARE7,
+ SCOUT_HELLO0,
+ SCOUT_HELLO1,
+ SCOUT_HELLO2,
+ SCOUT_HELLO3,
+ INIT_HOME_HELLO0,
+ INIT_HOME_HELLO1,
+ INIT_HOME_HELLO2,
+ INIT_HOME_HELLO3,
+ which_fot,
+ HE_IS0,
+ HE_IS1,
+ HE_IS2,
+ HE_IS3,
+ HE_IS4,
+ HE_IS5,
+ HE_IS6,
+ HE_IS7,
+ we_are_vindicator0,
+ we_are_vindicator1,
+ we_are_vindicator2,
+ WE_GLAD0,
+ WE_GLAD1,
+ WE_GLAD2,
+ WE_GLAD3,
+ WE_GLAD4,
+ WE_GLAD5,
+ quiet_toadies,
+ TOLD_YOU0,
+ TOLD_YOU1,
+ TOLD_YOU2,
+ TOLD_YOU3,
+ TOLD_YOU4,
+ TOLD_YOU5,
+ TOLD_YOU6,
+ TOLD_YOU7,
+ your_race,
+ YEARS_AGO0,
+ YEARS_AGO1,
+ YEARS_AGO2,
+ YEARS_AGO3,
+ YEARS_AGO4,
+ YEARS_AGO5,
+ YEARS_AGO6,
+ YEARS_AGO7,
+ YEARS_AGO8,
+ YEARS_AGO9,
+ YEARS_AGO10,
+ YEARS_AGO11,
+ YEARS_AGO12,
+ YEARS_AGO13,
+ where_from,
+ TRAVELED_FAR0,
+ TRAVELED_FAR1,
+ TRAVELED_FAR2,
+ TRAVELED_FAR3,
+ TRAVELED_FAR4,
+ TRAVELED_FAR5,
+ what_emergency,
+ UNDER_ATTACK0,
+ UNDER_ATTACK1,
+ UNDER_ATTACK2,
+ UNDER_ATTACK3,
+ UNDER_ATTACK4,
+ UNDER_ATTACK5,
+ UNDER_ATTACK6,
+ UNDER_ATTACK7,
+ UNDER_ATTACK8,
+ UNDER_ATTACK9,
+ UNDER_ATTACK10,
+ UNDER_ATTACK11,
+ tough_luck,
+ NOT_HELPFUL0,
+ NOT_HELPFUL1,
+ NOT_HELPFUL2,
+ NOT_HELPFUL3,
+ NOT_HELPFUL4,
+ NOT_HELPFUL5,
+ what_look_like,
+ LOOK_LIKE0,
+ LOOK_LIKE1,
+ LOOK_LIKE2,
+ LOOK_LIKE3,
+ valuable_info,
+ GOODBYE0,
+ GOODBYE1,
+ GOODBYE2,
+ GOODBYE3,
+ all_very_interesting,
+ SEE_TOLD_YOU0,
+ SEE_TOLD_YOU1,
+ SEE_TOLD_YOU2,
+ SEE_TOLD_YOU3,
+ how_can_i_help,
+ ALLY_WITH_US0,
+ ALLY_WITH_US1,
+ ALLY_WITH_US2,
+ ALLY_WITH_US3,
+ ALLY_WITH_US4,
+ ALLY_WITH_US5,
+ decide_later,
+ PLEASE_HURRY0,
+ PLEASE_HURRY1,
+ EMMISSARIES0,
+ EMMISSARIES1,
+ EMMISSARIES2,
+ EMMISSARIES3,
+ EMMISSARIES4,
+ EMMISSARIES5,
+ EMMISSARIES6,
+ EMMISSARIES7,
+ sure,
+ WE_ALLY0,
+ WE_ALLY1,
+ WE_ALLY2,
+ WE_ALLY3,
+ WE_ALLY4,
+ WE_ALLY5,
+ never,
+ WE_ENEMIES0,
+ WE_ENEMIES1,
+ HOSTILE_HELLO_10,
+ HOSTILE_HELLO_11,
+ HOSTILE_HELLO_20,
+ HOSTILE_HELLO_21,
+ HOSTILE_HELLO_22,
+ HOSTILE_HELLO_23,
+ HOSTILE_HELLO_24,
+ HOSTILE_HELLO_25,
+ HOSTILE_HELLO_30,
+ HOSTILE_HELLO_31,
+ HOSTILE_HELLO_40,
+ HOSTILE_HELLO_41,
+ NEUTRAL_HOME_HELLO_10,
+ NEUTRAL_HOME_HELLO_11,
+ NEUTRAL_HOME_HELLO_12,
+ NEUTRAL_HOME_HELLO_13,
+ NEUTRAL_HOME_HELLO_20,
+ NEUTRAL_HOME_HELLO_21,
+ NEUTRAL_HOME_HELLO_22,
+ NEUTRAL_HOME_HELLO_23,
+ ALLIED_HOME_HELLO_10,
+ ALLIED_HOME_HELLO_11,
+ ALLIED_HOME_HELLO_12,
+ ALLIED_HOME_HELLO_13,
+ ALLIED_HOME_HELLO_20,
+ ALLIED_HOME_HELLO_21,
+ ALLIED_HOME_HELLO_22,
+ ALLIED_HOME_HELLO_23,
+ ALLIED_HOME_HELLO_24,
+ ALLIED_HOME_HELLO_25,
+ ALLIED_HOME_HELLO_26,
+ ALLIED_HOME_HELLO_27,
+ ALLIED_HOME_HELLO_30,
+ ALLIED_HOME_HELLO_31,
+ ALLIED_HOME_HELLO_40,
+ ALLIED_HOME_HELLO_41,
+ THANKS_FOR_RESCUE0,
+ THANKS_FOR_RESCUE1,
+ THANKS_FOR_RESCUE2,
+ THANKS_FOR_RESCUE3,
+ THANKS_FOR_RESCUE4,
+ THANKS_FOR_RESCUE5,
+ THANKS_FOR_RESCUE6,
+ THANKS_FOR_RESCUE7,
+ THANKS_FOR_RESCUE8,
+ THANKS_FOR_RESCUE9,
+ THANKS_FOR_RESCUE10,
+ THANKS_FOR_RESCUE11,
+ bye_homeworld,
+ GOODBYE_HOME0,
+ GOODBYE_HOME1,
+ whats_up_homeworld,
+ GENERAL_INFO_10,
+ GENERAL_INFO_11,
+ GENERAL_INFO_12,
+ GENERAL_INFO_13,
+ GENERAL_INFO_20,
+ GENERAL_INFO_21,
+ GENERAL_INFO_22,
+ GENERAL_INFO_23,
+ GENERAL_INFO_24,
+ GENERAL_INFO_25,
+ GENERAL_INFO_26,
+ GENERAL_INFO_27,
+ GENERAL_INFO_30,
+ GENERAL_INFO_31,
+ GENERAL_INFO_32,
+ GENERAL_INFO_33,
+ GENERAL_INFO_34,
+ GENERAL_INFO_35,
+ GENERAL_INFO_40,
+ GENERAL_INFO_41,
+ GENERAL_INFO_42,
+ GENERAL_INFO_43,
+ GENERAL_INFO_44,
+ GENERAL_INFO_45,
+ GENERAL_INFO_46,
+ GENERAL_INFO_47,
+ GENERAL_INFO_48,
+ GENERAL_INFO_49,
+ GENERAL_INFO_410,
+ GENERAL_INFO_411,
+ any_war_news,
+ UTWIG_DELAY0,
+ UTWIG_DELAY1,
+ UTWIG_DELAY2,
+ UTWIG_DELAY3,
+ UTWIG_DELAY4,
+ UTWIG_DELAY5,
+ UTWIG_DELAY6,
+ UTWIG_DELAY7,
+ UTWIG_DELAY8,
+ UTWIG_DELAY9,
+ UTWIG_DELAY10,
+ UTWIG_DELAY11,
+ UTWIG_DELAY12,
+ UTWIG_DELAY13,
+ KOHRAH_WINNING0,
+ KOHRAH_WINNING1,
+ KOHRAH_WINNING2,
+ KOHRAH_WINNING3,
+ KOHRAH_WINNING4,
+ KOHRAH_WINNING5,
+ KOHRAH_WINNING6,
+ KOHRAH_WINNING7,
+ KOHRAH_WINNING8,
+ KOHRAH_WINNING9,
+ URQUAN_NEARLY_GONE0,
+ URQUAN_NEARLY_GONE1,
+ URQUAN_NEARLY_GONE2,
+ URQUAN_NEARLY_GONE3,
+ URQUAN_NEARLY_GONE4,
+ URQUAN_NEARLY_GONE5,
+ KOHRAH_FRENZY0,
+ KOHRAH_FRENZY1,
+ KOHRAH_FRENZY2,
+ KOHRAH_FRENZY3,
+ KOHRAH_FRENZY4,
+ KOHRAH_FRENZY5,
+ KOHRAH_FRENZY6,
+ KOHRAH_FRENZY7,
+ KOHRAH_FRENZY8,
+ KOHRAH_FRENZY9,
+ KOHRAH_FRENZY10,
+ KOHRAH_FRENZY11,
+ NO_WAR_NEWS0,
+ NO_WAR_NEWS1,
+ i_want_alliance,
+ GOOD0,
+ GOOD1,
+ GOOD2,
+ GOOD3,
+ GOOD4,
+ GOOD5,
+ GOOD6,
+ GOOD7,
+ GOOD8,
+ GOOD9,
+ want_specific_info,
+ WHAT_SPECIFIC_INFO0,
+ WHAT_SPECIFIC_INFO1,
+ enough_info,
+ OK_ENOUGH_INFO,
+ what_about_others,
+ ABOUT_OTHERS0,
+ ABOUT_OTHERS1,
+ ABOUT_OTHERS2,
+ ABOUT_OTHERS3,
+ ABOUT_OTHERS4,
+ ABOUT_OTHERS5,
+ ABOUT_OTHERS6,
+ ABOUT_OTHERS7,
+ ABOUT_OTHERS8,
+ ABOUT_OTHERS9,
+ ABOUT_OTHERS10,
+ ABOUT_OTHERS11,
+ ABOUT_OTHERS12,
+ ABOUT_OTHERS13,
+ what_about_zebranky,
+ ABOUT_ZEBRANKY0,
+ ABOUT_ZEBRANKY1,
+ ABOUT_ZEBRANKY2,
+ ABOUT_ZEBRANKY3,
+ ABOUT_ZEBRANKY4,
+ ABOUT_ZEBRANKY5,
+ ABOUT_ZEBRANKY6,
+ ABOUT_ZEBRANKY7,
+ what_about_past,
+ ABOUT_PAST0,
+ ABOUT_PAST1,
+ ABOUT_PAST2,
+ ABOUT_PAST3,
+ ABOUT_PAST4,
+ ABOUT_PAST5,
+ ABOUT_PAST6,
+ ABOUT_PAST7,
+ ABOUT_PAST8,
+ ABOUT_PAST9,
+ ABOUT_PAST10,
+ ABOUT_PAST11,
+ what_about_stinger,
+ ABOUT_STINGER0,
+ ABOUT_STINGER1,
+ ABOUT_STINGER2,
+ ABOUT_STINGER3,
+ ABOUT_STINGER4,
+ ABOUT_STINGER5,
+ what_about_guy_in_back,
+ ABOUT_GUY0,
+ ABOUT_GUY1,
+ name_1,
+ name_2,
+ name_3,
+ name_40,
+ name_41,
+ OUT_TAKES0,
+ OUT_TAKES1,
+ OUT_TAKES2,
+ OUT_TAKES3,
+ OUT_TAKES4,
+ OUT_TAKES5,
+ OUT_TAKES6,
+ OUT_TAKES7,
+ OUT_TAKES8,
+ OUT_TAKES9,
+ OUT_TAKES10,
+ OUT_TAKES11,
+ OUT_TAKES12,
+ OUT_TAKES13,
+};
+
+#endif /* _STRINGS_H */
+
diff --git a/src/uqm/comm/zoqfot/zoqfotc.c b/src/uqm/comm/zoqfot/zoqfotc.c
new file mode 100644
index 0000000..d09a7f7
--- /dev/null
+++ b/src/uqm/comm/zoqfot/zoqfotc.c
@@ -0,0 +1,975 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../commall.h"
+#include "resinst.h"
+#include "strings.h"
+
+#include "uqm/build.h"
+#include "uqm/gameev.h"
+
+
+#define ZOQ_FG_COLOR WHITE_COLOR
+#define ZOQ_BG_COLOR BLACK_COLOR
+#define ZOQ_BASE_X (TEXT_X_OFFS + ((SIS_TEXT_WIDTH >> 1) >> 1))
+#define ZOQ_BASE_Y 24
+#define ZOQ_TALK_INDEX 18
+#define ZOQ_TALK_FRAMES 5
+#define FOT_TO_ZOQ 23
+
+#define PIK_FG_COLOR WHITE_COLOR
+#define PIK_BG_COLOR BLACK_COLOR
+#define PIK_BASE_X (SIS_SCREEN_WIDTH - (TEXT_X_OFFS + ((SIS_TEXT_WIDTH >> 1) >> 1)))
+#define PIK_BASE_Y 24
+#define PIK_TALK_INDEX 29
+#define PIK_TALK_FRAMES 2
+#define FOT_TO_PIK 26
+
+static LOCDATA zoqfot_desc =
+{
+ NULL, /* init_encounter_func */
+ NULL, /* post_encounter_func */
+ NULL, /* uninit_encounter_func */
+ ZOQFOTPIK_PMAP_ANIM, /* AlienFrame */
+ ZOQFOTPIK_FONT, /* AlienFont */
+ UNDEFINED_COLOR_INIT, /* AlienTextFColor */
+ UNDEFINED_COLOR_INIT, /* AlienTextBColor */
+ {0, 0}, /* AlienTextBaseline */
+ 0, /* AlienTextWidth */
+ ALIGN_CENTER, /* AlienTextAlign */
+ VALIGN_MIDDLE, /* AlienTextValign */
+ ZOQFOTPIK_COLOR_MAP, /* AlienColorMap */
+ ZOQFOTPIK_MUSIC, /* AlienSong */
+ NULL_RESOURCE, /* AlienAltSong */
+ 0, /* AlienSongFlags */
+ ZOQFOTPIK_CONVERSATION_PHRASES, /* PlayerPhrases */
+ 3, /* NumAnimations */
+ { /* AlienAmbientArray (ambient animations) */
+ { /* Eye blink */
+ 1, /* StartIndex */
+ 4, /* NumFrames */
+ YOYO_ANIM /* AnimFlags */
+ | WAIT_TALKING,
+ ONE_SECOND / 24, 0, /* FrameRate */
+ 0, ONE_SECOND * 10, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Blow smoke */
+ 5, /* StartIndex */
+ 5, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND * 7 / 120, 0, /* FrameRate */
+ ONE_SECOND * 2, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* Gulp */
+ 10, /* StartIndex */
+ 8, /* NumFrames */
+ CIRCULAR_ANIM, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ 0, ONE_SECOND * 10, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ },
+ { /* AlienTransitionDesc - Move Eye */
+ FOT_TO_ZOQ, /* StartIndex */
+ 3, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 30, 0, /* FrameRate */
+ 0, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ { /* AlienTalkDesc */
+ ZOQ_TALK_INDEX, /* StartIndex */
+ ZOQ_TALK_FRAMES, /* NumFrames */
+ 0, /* AnimFlags */
+ ONE_SECOND / 15, 0, /* FrameRate */
+ ONE_SECOND / 12, 0, /* RestartRate */
+ 0, /* BlockMask */
+ },
+ NULL, /* AlienNumberSpeech - none */
+ /* Filler for loaded resources */
+ NULL, NULL, NULL,
+ NULL,
+ NULL,
+};
+
+enum
+{
+ ZOQ_ALIEN,
+ FOT_ALIEN,
+ PIK_ALIEN
+};
+
+static int LastAlien;
+
+// Queued and executes synchronously on the Starcon2Main thread
+static void
+SelectAlienZOQ (CallbackArg arg)
+{
+ if (LastAlien != ZOQ_ALIEN)
+ {
+ // Transition to neutral state first if Pik was talking
+ if (LastAlien != FOT_ALIEN)
+ CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE;
+ LastAlien = ZOQ_ALIEN;
+ CommData.AlienTransitionDesc.AnimFlags |= TALK_INTRO;
+ CommData.AlienTransitionDesc.StartIndex = FOT_TO_ZOQ;
+ CommData.AlienTalkDesc.StartIndex = ZOQ_TALK_INDEX;
+ CommData.AlienTalkDesc.NumFrames = ZOQ_TALK_FRAMES;
+ CommData.AlienAmbientArray[1].AnimFlags &= ~WAIT_TALKING;
+
+ CommData.AlienTextBaseline.x = (SWORD)ZOQ_BASE_X;
+ CommData.AlienTextBaseline.y = ZOQ_BASE_Y;
+ CommData.AlienTextFColor = ZOQ_FG_COLOR;
+ CommData.AlienTextBColor = ZOQ_BG_COLOR;
+ }
+
+ (void)arg; // ignored
+}
+
+// Queued and executes synchronously on the Starcon2Main thread
+static void
+SelectAlienPIK (CallbackArg arg)
+{
+ if (LastAlien != PIK_ALIEN)
+ {
+ // Transition to neutral state first if Zoq was talking
+ if (LastAlien != FOT_ALIEN)
+ CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE;
+ LastAlien = PIK_ALIEN;
+ CommData.AlienTransitionDesc.AnimFlags |= TALK_INTRO;
+ CommData.AlienTransitionDesc.StartIndex = FOT_TO_PIK;
+ CommData.AlienTalkDesc.StartIndex = PIK_TALK_INDEX;
+ CommData.AlienTalkDesc.NumFrames = PIK_TALK_FRAMES;
+ CommData.AlienAmbientArray[1].AnimFlags |= WAIT_TALKING;
+
+ CommData.AlienTextBaseline.x = (SWORD)PIK_BASE_X;
+ CommData.AlienTextBaseline.y = PIK_BASE_Y;
+ CommData.AlienTextFColor = PIK_FG_COLOR;
+ CommData.AlienTextBColor = PIK_BG_COLOR;
+ }
+
+ (void)arg; // ignored
+}
+
+static void
+ZFPTalkSegue (COUNT wait_track)
+{
+ LastAlien = FOT_ALIEN;
+ SelectAlienZOQ (0);
+ AlienTalkSegue (wait_track);
+}
+
+static void
+ExitConversation (RESPONSE_REF R)
+{
+ setSegue (Segue_peace);
+
+ if (PLAYER_SAID (R, bye_homeworld))
+ {
+ NPCPhrase_cb (GOODBYE_HOME0, &SelectAlienZOQ);
+ NPCPhrase_cb (GOODBYE_HOME1, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ }
+ else if (PLAYER_SAID (R, decide_later))
+ {
+ NPCPhrase_cb (PLEASE_HURRY0, &SelectAlienZOQ);
+ NPCPhrase_cb (PLEASE_HURRY1, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ }
+ else if (PLAYER_SAID (R, valuable_info))
+ {
+ NPCPhrase_cb (GOODBYE0, &SelectAlienZOQ);
+ NPCPhrase_cb (GOODBYE1, &SelectAlienPIK);
+ NPCPhrase_cb (GOODBYE2, &SelectAlienZOQ);
+ NPCPhrase_cb (GOODBYE3, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ }
+ else if (PLAYER_SAID (R, how_can_i_help))
+ {
+ NPCPhrase_cb (EMMISSARIES0, &SelectAlienZOQ);
+ NPCPhrase_cb (EMMISSARIES1, &SelectAlienPIK);
+ NPCPhrase_cb (EMMISSARIES2, &SelectAlienZOQ);
+ NPCPhrase_cb (EMMISSARIES3, &SelectAlienPIK);
+ NPCPhrase_cb (EMMISSARIES4, &SelectAlienZOQ);
+ NPCPhrase_cb (EMMISSARIES5, &SelectAlienPIK);
+ NPCPhrase_cb (EMMISSARIES6, &SelectAlienZOQ);
+ NPCPhrase_cb (EMMISSARIES7, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ }
+ else if (PLAYER_SAID (R, sure))
+ {
+ NPCPhrase_cb (WE_ALLY0, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_ALLY1, &SelectAlienPIK);
+ NPCPhrase_cb (WE_ALLY2, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_ALLY3, &SelectAlienPIK);
+ NPCPhrase_cb (WE_ALLY4, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_ALLY5, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ SetRaceAllied (ZOQFOTPIK_SHIP, TRUE);
+ AddEvent (RELATIVE_EVENT, 3, 0, 0, ZOQFOT_DISTRESS_EVENT);
+ SET_GAME_STATE (ZOQFOT_HOME_VISITS, 0);
+ }
+ else if (PLAYER_SAID (R, all_very_interesting))
+ {
+ NPCPhrase_cb (SEE_TOLD_YOU0, &SelectAlienZOQ);
+ NPCPhrase_cb (SEE_TOLD_YOU1, &SelectAlienPIK);
+ NPCPhrase_cb (SEE_TOLD_YOU2, &SelectAlienZOQ);
+ NPCPhrase_cb (SEE_TOLD_YOU3, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ SET_GAME_STATE (ZOQFOT_HOSTILE, 1);
+ SET_GAME_STATE (ZOQFOT_HOME_VISITS, 0);
+ setSegue (Segue_hostile);
+ }
+ else if (PLAYER_SAID (R, never))
+ {
+ NPCPhrase_cb (WE_ENEMIES0, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_ENEMIES1, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ SET_GAME_STATE (ZOQFOT_HOME_VISITS, 0);
+ SET_GAME_STATE (ZOQFOT_HOSTILE, 1);
+ setSegue (Segue_hostile);
+ }
+}
+
+static void
+FormAlliance (RESPONSE_REF R)
+{
+ (void) R; // ignored
+ NPCPhrase_cb (ALLY_WITH_US0, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLY_WITH_US1, &SelectAlienPIK);
+ NPCPhrase_cb (ALLY_WITH_US2, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLY_WITH_US3, &SelectAlienPIK);
+ NPCPhrase_cb (ALLY_WITH_US4, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLY_WITH_US5, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ Response (sure, ExitConversation);
+ Response (never, ExitConversation);
+ Response (decide_later, ExitConversation);
+}
+
+static void
+ZoqFotIntro (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, we_are_vindicator0))
+ {
+ NPCPhrase_cb (WE_GLAD0, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_GLAD1, &SelectAlienPIK);
+ NPCPhrase_cb (WE_GLAD2, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_GLAD3, &SelectAlienPIK);
+ NPCPhrase_cb (WE_GLAD4, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_GLAD5, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (we_are_vindicator0);
+ }
+ else if (PLAYER_SAID (R, your_race))
+ {
+ NPCPhrase_cb (YEARS_AGO0, &SelectAlienZOQ);
+ NPCPhrase_cb (YEARS_AGO1, &SelectAlienPIK);
+ NPCPhrase_cb (YEARS_AGO2, &SelectAlienZOQ);
+ NPCPhrase_cb (YEARS_AGO3, &SelectAlienPIK);
+ NPCPhrase_cb (YEARS_AGO4, &SelectAlienZOQ);
+ NPCPhrase_cb (YEARS_AGO5, &SelectAlienPIK);
+ NPCPhrase_cb (YEARS_AGO6, &SelectAlienZOQ);
+ NPCPhrase_cb (YEARS_AGO7, &SelectAlienPIK);
+ NPCPhrase_cb (YEARS_AGO8, &SelectAlienZOQ);
+ NPCPhrase_cb (YEARS_AGO9, &SelectAlienPIK);
+ NPCPhrase_cb (YEARS_AGO10, &SelectAlienZOQ);
+ NPCPhrase_cb (YEARS_AGO11, &SelectAlienPIK);
+ NPCPhrase_cb (YEARS_AGO12, &SelectAlienZOQ);
+ NPCPhrase_cb (YEARS_AGO13, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (your_race);
+ }
+ else if (PLAYER_SAID (R, where_from))
+ {
+ NPCPhrase_cb (TRAVELED_FAR0, &SelectAlienZOQ);
+ NPCPhrase_cb (TRAVELED_FAR1, &SelectAlienPIK);
+ NPCPhrase_cb (TRAVELED_FAR2, &SelectAlienZOQ);
+ NPCPhrase_cb (TRAVELED_FAR3, &SelectAlienPIK);
+ NPCPhrase_cb (TRAVELED_FAR4, &SelectAlienZOQ);
+ NPCPhrase_cb (TRAVELED_FAR5, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (where_from);
+ }
+ else if (PLAYER_SAID (R, what_emergency))
+ {
+ NPCPhrase_cb (UNDER_ATTACK0, &SelectAlienZOQ);
+ NPCPhrase_cb (UNDER_ATTACK1, &SelectAlienPIK);
+ NPCPhrase_cb (UNDER_ATTACK2, &SelectAlienZOQ);
+ NPCPhrase_cb (UNDER_ATTACK3, &SelectAlienPIK);
+ NPCPhrase_cb (UNDER_ATTACK4, &SelectAlienZOQ);
+ NPCPhrase_cb (UNDER_ATTACK5, &SelectAlienPIK);
+ NPCPhrase_cb (UNDER_ATTACK6, &SelectAlienZOQ);
+ NPCPhrase_cb (UNDER_ATTACK7, &SelectAlienPIK);
+ NPCPhrase_cb (UNDER_ATTACK8, &SelectAlienZOQ);
+ NPCPhrase_cb (UNDER_ATTACK9, &SelectAlienPIK);
+ NPCPhrase_cb (UNDER_ATTACK10, &SelectAlienZOQ);
+ NPCPhrase_cb (UNDER_ATTACK11, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (what_emergency);
+ }
+ else if (PLAYER_SAID (R, tough_luck))
+ {
+ NPCPhrase_cb (NOT_HELPFUL0, &SelectAlienZOQ);
+ NPCPhrase_cb (NOT_HELPFUL1, &SelectAlienPIK);
+ NPCPhrase_cb (NOT_HELPFUL2, &SelectAlienZOQ);
+ NPCPhrase_cb (NOT_HELPFUL3, &SelectAlienPIK);
+ NPCPhrase_cb (NOT_HELPFUL4, &SelectAlienZOQ);
+ NPCPhrase_cb (NOT_HELPFUL5, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (tough_luck);
+ }
+ else if (PLAYER_SAID (R, what_look_like))
+ {
+ NPCPhrase_cb (LOOK_LIKE0, &SelectAlienZOQ);
+ NPCPhrase_cb (LOOK_LIKE1, &SelectAlienPIK);
+ NPCPhrase_cb (LOOK_LIKE2, &SelectAlienZOQ);
+ NPCPhrase_cb (LOOK_LIKE3, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (what_look_like);
+ }
+
+ if (PHRASE_ENABLED (your_race)
+ || PHRASE_ENABLED (where_from)
+ || PHRASE_ENABLED (what_emergency))
+ {
+ if (PHRASE_ENABLED (your_race))
+ Response (your_race, ZoqFotIntro);
+ if (PHRASE_ENABLED (where_from))
+ Response (where_from, ZoqFotIntro);
+ if (PHRASE_ENABLED (what_emergency))
+ Response (what_emergency, ZoqFotIntro);
+ }
+ else
+ {
+ if (PHRASE_ENABLED (tough_luck))
+ Response (tough_luck, ZoqFotIntro);
+ if (PHRASE_ENABLED (what_look_like))
+ Response (what_look_like, ZoqFotIntro);
+ Response (all_very_interesting, ExitConversation);
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ Response (how_can_i_help, FormAlliance);
+ }
+ else
+ {
+ Response (how_can_i_help, ExitConversation);
+ }
+ Response (valuable_info, ExitConversation);
+ }
+}
+
+static void
+AquaintZoqFot (RESPONSE_REF R)
+{
+ if (PLAYER_SAID (R, which_fot))
+ {
+ NPCPhrase_cb (HE_IS0, &SelectAlienZOQ);
+ NPCPhrase_cb (HE_IS1, &SelectAlienPIK);
+ NPCPhrase_cb (HE_IS2, &SelectAlienZOQ);
+ NPCPhrase_cb (HE_IS3, &SelectAlienPIK);
+ NPCPhrase_cb (HE_IS4, &SelectAlienZOQ);
+ NPCPhrase_cb (HE_IS5, &SelectAlienPIK);
+ NPCPhrase_cb (HE_IS6, &SelectAlienZOQ);
+ NPCPhrase_cb (HE_IS7, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (which_fot);
+ }
+ else if (PLAYER_SAID (R, quiet_toadies))
+ {
+ NPCPhrase_cb (TOLD_YOU0, &SelectAlienZOQ);
+ NPCPhrase_cb (TOLD_YOU1, &SelectAlienPIK);
+ NPCPhrase_cb (TOLD_YOU2, &SelectAlienZOQ);
+ NPCPhrase_cb (TOLD_YOU3, &SelectAlienPIK);
+ NPCPhrase_cb (TOLD_YOU4, &SelectAlienZOQ);
+ NPCPhrase_cb (TOLD_YOU5, &SelectAlienPIK);
+ NPCPhrase_cb (TOLD_YOU6, &SelectAlienZOQ);
+ NPCPhrase_cb (TOLD_YOU7, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (quiet_toadies);
+ }
+
+ if (PHRASE_ENABLED (we_are_vindicator0))
+ {
+ UNICODE buf[ALLIANCE_NAME_BUFSIZE];
+
+ GetAllianceName (buf, name_1);
+ construct_response (
+ shared_phrase_buf,
+ we_are_vindicator0,
+ buf,
+ we_are_vindicator1,
+ GLOBAL_SIS (ShipName),
+ we_are_vindicator2,
+ (UNICODE*)NULL);
+ }
+
+ if (PHRASE_ENABLED (which_fot))
+ Response (which_fot, AquaintZoqFot);
+ if (PHRASE_ENABLED (we_are_vindicator0))
+ DoResponsePhrase (we_are_vindicator0, ZoqFotIntro, shared_phrase_buf);
+ if (PHRASE_ENABLED (quiet_toadies))
+ Response (quiet_toadies, AquaintZoqFot);
+ Response (all_very_interesting, ExitConversation);
+ Response (valuable_info, ExitConversation);
+}
+
+static void ZoqFotHome (RESPONSE_REF R);
+
+static void
+ZoqFotInfo (RESPONSE_REF R)
+{
+ BYTE InfoLeft;
+
+ if (PLAYER_SAID (R, want_specific_info))
+ {
+ NPCPhrase_cb (WHAT_SPECIFIC_INFO0, &SelectAlienZOQ);
+ NPCPhrase_cb (WHAT_SPECIFIC_INFO1, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ }
+ else if (PLAYER_SAID (R, what_about_others))
+ {
+ NPCPhrase_cb (ABOUT_OTHERS0, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_OTHERS1, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_OTHERS2, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_OTHERS3, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_OTHERS4, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_OTHERS5, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_OTHERS6, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_OTHERS7, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_OTHERS8, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_OTHERS9, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_OTHERS10, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_OTHERS11, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_OTHERS12, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_OTHERS13, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (what_about_others);
+ }
+ else if (PLAYER_SAID (R, what_about_zebranky))
+ {
+ NPCPhrase_cb (ABOUT_ZEBRANKY0, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_ZEBRANKY1, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_ZEBRANKY2, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_ZEBRANKY3, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_ZEBRANKY4, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_ZEBRANKY5, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_ZEBRANKY6, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_ZEBRANKY7, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (what_about_zebranky);
+ }
+ else if (PLAYER_SAID (R, what_about_stinger))
+ {
+ NPCPhrase_cb (ABOUT_STINGER0, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_STINGER1, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_STINGER2, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_STINGER3, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_STINGER4, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_STINGER5, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (what_about_stinger);
+ }
+ else if (PLAYER_SAID (R, what_about_guy_in_back))
+ {
+ NPCPhrase_cb (ABOUT_GUY0, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_GUY1, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (what_about_guy_in_back);
+ }
+ else if (PLAYER_SAID (R, what_about_past))
+ {
+ NPCPhrase_cb (ABOUT_PAST0, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_PAST1, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_PAST2, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_PAST3, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_PAST4, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_PAST5, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_PAST6, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_PAST7, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_PAST8, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_PAST9, &SelectAlienPIK);
+ NPCPhrase_cb (ABOUT_PAST10, &SelectAlienZOQ);
+ NPCPhrase_cb (ABOUT_PAST11, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ DISABLE_PHRASE (what_about_past);
+ }
+
+ InfoLeft = FALSE;
+ if (PHRASE_ENABLED (what_about_others))
+ {
+ Response (what_about_others, ZoqFotInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_zebranky))
+ {
+ Response (what_about_zebranky, ZoqFotInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_stinger))
+ {
+ Response (what_about_stinger, ZoqFotInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_guy_in_back))
+ {
+ Response (what_about_guy_in_back, ZoqFotInfo);
+ InfoLeft = TRUE;
+ }
+ if (PHRASE_ENABLED (what_about_past))
+ {
+ Response (what_about_past, ZoqFotInfo);
+ InfoLeft = TRUE;
+ }
+ Response (enough_info, ZoqFotHome);
+
+ if (!InfoLeft)
+ {
+ DISABLE_PHRASE (want_specific_info);
+ }
+}
+
+static void
+ZoqFotHome (RESPONSE_REF R)
+{
+ BYTE NumVisits;
+
+ if (PLAYER_SAID (R, whats_up_homeworld))
+ {
+ NumVisits = GET_GAME_STATE (ZOQFOT_INFO);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase_cb (GENERAL_INFO_10, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_11, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_12, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_13, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 1:
+ NPCPhrase_cb (GENERAL_INFO_20, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_21, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_22, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_23, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_24, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_25, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_26, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_27, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 2:
+ NPCPhrase_cb (GENERAL_INFO_30, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_31, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_32, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_33, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_34, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_35, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 3:
+ NPCPhrase_cb (GENERAL_INFO_40, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_41, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_42, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_43, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_44, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_45, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_46, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_47, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_48, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_49, &SelectAlienPIK);
+ NPCPhrase_cb (GENERAL_INFO_410, &SelectAlienZOQ);
+ NPCPhrase_cb (GENERAL_INFO_411, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ZOQFOT_INFO, NumVisits);
+
+ DISABLE_PHRASE (whats_up_homeworld);
+ }
+ else if (PLAYER_SAID (R, any_war_news))
+ {
+#define UTWIG_BUY_TIME (1 << 0)
+#define KOHR_AH_WIN (1 << 1)
+#define URQUAN_LOSE (1 << 2)
+#define KOHR_AH_KILL (1 << 3)
+#define KNOW_ALL (UTWIG_BUY_TIME | KOHR_AH_WIN | URQUAN_LOSE | KOHR_AH_KILL)
+ BYTE KnowMask;
+
+ NumVisits = GET_GAME_STATE (UTWIG_SUPOX_MISSION);
+ KnowMask = GET_GAME_STATE (ZOQFOT_KNOW_MASK);
+ if (!(KnowMask & KOHR_AH_KILL) && GET_GAME_STATE (KOHR_AH_FRENZY))
+ {
+ NPCPhrase_cb (KOHRAH_FRENZY0, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_FRENZY1, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_FRENZY2, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_FRENZY3, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_FRENZY4, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_FRENZY5, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_FRENZY6, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_FRENZY7, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_FRENZY8, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_FRENZY9, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_FRENZY10, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_FRENZY11, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ KnowMask = KNOW_ALL;
+ }
+ else if (!(KnowMask & UTWIG_BUY_TIME)
+ && NumVisits > 0 && NumVisits < 5)
+ {
+ NPCPhrase_cb (UTWIG_DELAY0, &SelectAlienZOQ);
+ NPCPhrase_cb (UTWIG_DELAY1, &SelectAlienPIK);
+ NPCPhrase_cb (UTWIG_DELAY2, &SelectAlienZOQ);
+ NPCPhrase_cb (UTWIG_DELAY3, &SelectAlienPIK);
+ NPCPhrase_cb (UTWIG_DELAY4, &SelectAlienZOQ);
+ NPCPhrase_cb (UTWIG_DELAY5, &SelectAlienPIK);
+ NPCPhrase_cb (UTWIG_DELAY6, &SelectAlienZOQ);
+ NPCPhrase_cb (UTWIG_DELAY7, &SelectAlienPIK);
+ NPCPhrase_cb (UTWIG_DELAY8, &SelectAlienZOQ);
+ NPCPhrase_cb (UTWIG_DELAY9, &SelectAlienPIK);
+ NPCPhrase_cb (UTWIG_DELAY10, &SelectAlienZOQ);
+ NPCPhrase_cb (UTWIG_DELAY11, &SelectAlienPIK);
+ NPCPhrase_cb (UTWIG_DELAY12, &SelectAlienZOQ);
+ NPCPhrase_cb (UTWIG_DELAY13, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ KnowMask |= UTWIG_BUY_TIME;
+ }
+ else
+ {
+ SIZE i;
+
+ i = START_YEAR + YEARS_TO_KOHRAH_VICTORY;
+ if (NumVisits)
+ ++i;
+ if ((i -= GLOBAL (GameClock.year_index)) == 1
+ && GLOBAL (GameClock.month_index) > 2)
+ i = 0;
+ if (!(KnowMask & URQUAN_LOSE) && i <= 0)
+ {
+ NPCPhrase_cb (URQUAN_NEARLY_GONE0, &SelectAlienZOQ);
+ NPCPhrase_cb (URQUAN_NEARLY_GONE1, &SelectAlienPIK);
+ NPCPhrase_cb (URQUAN_NEARLY_GONE2, &SelectAlienZOQ);
+ NPCPhrase_cb (URQUAN_NEARLY_GONE3, &SelectAlienPIK);
+ NPCPhrase_cb (URQUAN_NEARLY_GONE4, &SelectAlienZOQ);
+ NPCPhrase_cb (URQUAN_NEARLY_GONE5, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ KnowMask |= KOHR_AH_WIN | URQUAN_LOSE;
+ }
+ else if (!(KnowMask & KOHR_AH_WIN) && i == 1)
+ {
+ NPCPhrase_cb (KOHRAH_WINNING0, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_WINNING1, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_WINNING2, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_WINNING3, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_WINNING4, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_WINNING5, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_WINNING6, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_WINNING7, &SelectAlienPIK);
+ NPCPhrase_cb (KOHRAH_WINNING8, &SelectAlienZOQ);
+ NPCPhrase_cb (KOHRAH_WINNING9, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ KnowMask |= KOHR_AH_WIN;
+ }
+ else
+ {
+ NPCPhrase_cb (NO_WAR_NEWS0, &SelectAlienZOQ);
+ NPCPhrase_cb (NO_WAR_NEWS1, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ }
+ }
+ SET_GAME_STATE (ZOQFOT_KNOW_MASK, KnowMask);
+
+ DISABLE_PHRASE (any_war_news);
+ }
+ else if (PLAYER_SAID (R, i_want_alliance))
+ {
+ NPCPhrase_cb (GOOD0, &SelectAlienZOQ);
+ NPCPhrase_cb (GOOD1, &SelectAlienPIK);
+ NPCPhrase_cb (GOOD2, &SelectAlienZOQ);
+ NPCPhrase_cb (GOOD3, &SelectAlienPIK);
+ NPCPhrase_cb (GOOD4, &SelectAlienZOQ);
+ NPCPhrase_cb (GOOD5, &SelectAlienPIK);
+ NPCPhrase_cb (GOOD6, &SelectAlienZOQ);
+ NPCPhrase_cb (GOOD7, &SelectAlienPIK);
+ NPCPhrase_cb (GOOD8, &SelectAlienZOQ);
+ NPCPhrase_cb (GOOD9, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ SetRaceAllied (ZOQFOTPIK_SHIP, TRUE);
+ AddEvent (RELATIVE_EVENT, 3, 0, 0, ZOQFOT_DISTRESS_EVENT);
+ }
+ else if (PLAYER_SAID (R, enough_info))
+ {
+ NPCPhrase_cb (OK_ENOUGH_INFO, &SelectAlienZOQ);
+ ZFPTalkSegue ((COUNT)~0);
+ }
+
+ if (PHRASE_ENABLED (whats_up_homeworld))
+ Response (whats_up_homeworld, ZoqFotHome);
+ if (PHRASE_ENABLED (any_war_news))
+ Response (any_war_news, ZoqFotHome);
+ if (CheckAlliance (ZOQFOTPIK_SHIP) != GOOD_GUY)
+ Response (i_want_alliance, ZoqFotHome);
+ else if (PHRASE_ENABLED (want_specific_info))
+ {
+ Response (want_specific_info, ZoqFotInfo);
+ }
+ Response (bye_homeworld, ExitConversation);
+}
+
+static void
+Intro (void)
+{
+ BYTE NumVisits;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ NPCPhrase_cb (OUT_TAKES0, &SelectAlienZOQ);
+ NPCPhrase_cb (OUT_TAKES1, &SelectAlienPIK);
+ NPCPhrase_cb (OUT_TAKES2, &SelectAlienZOQ);
+ NPCPhrase_cb (OUT_TAKES3, &SelectAlienPIK);
+ NPCPhrase_cb (OUT_TAKES4, &SelectAlienZOQ);
+ NPCPhrase_cb (OUT_TAKES5, &SelectAlienPIK);
+ NPCPhrase_cb (OUT_TAKES6, &SelectAlienZOQ);
+ NPCPhrase_cb (OUT_TAKES7, &SelectAlienPIK);
+ NPCPhrase_cb (OUT_TAKES8, &SelectAlienZOQ);
+ NPCPhrase_cb (OUT_TAKES9, &SelectAlienPIK);
+ NPCPhrase_cb (OUT_TAKES10, &SelectAlienZOQ);
+ NPCPhrase_cb (OUT_TAKES11, &SelectAlienPIK);
+ NPCPhrase_cb (OUT_TAKES12, &SelectAlienZOQ);
+ NPCPhrase_cb (OUT_TAKES13, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ setSegue (Segue_peace);
+ return;
+ }
+
+ if (GET_GAME_STATE (ZOQFOT_HOSTILE))
+ {
+ NumVisits = GET_GAME_STATE (ZOQFOT_HOME_VISITS);
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase_cb (HOSTILE_HELLO_10, &SelectAlienZOQ);
+ NPCPhrase_cb (HOSTILE_HELLO_11, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 1:
+ NPCPhrase_cb (HOSTILE_HELLO_20, &SelectAlienZOQ);
+ NPCPhrase_cb (HOSTILE_HELLO_21, &SelectAlienPIK);
+ NPCPhrase_cb (HOSTILE_HELLO_22, &SelectAlienZOQ);
+ NPCPhrase_cb (HOSTILE_HELLO_23, &SelectAlienPIK);
+ NPCPhrase_cb (HOSTILE_HELLO_24, &SelectAlienZOQ);
+ NPCPhrase_cb (HOSTILE_HELLO_25, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 2:
+ NPCPhrase_cb (HOSTILE_HELLO_30, &SelectAlienZOQ);
+ NPCPhrase_cb (HOSTILE_HELLO_31, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 3:
+ NPCPhrase_cb (HOSTILE_HELLO_40, &SelectAlienZOQ);
+ NPCPhrase_cb (HOSTILE_HELLO_41, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ --NumVisits;
+ break;
+ }
+ SET_GAME_STATE (ZOQFOT_HOME_VISITS, NumVisits);
+
+ setSegue (Segue_hostile);
+ }
+ else if (!GET_GAME_STATE (MET_ZOQFOT))
+ {
+ SET_GAME_STATE (MET_ZOQFOT, 1);
+
+ NPCPhrase_cb (WE_ARE0, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_ARE1, &SelectAlienPIK);
+ NPCPhrase_cb (WE_ARE2, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_ARE3, &SelectAlienPIK);
+ NPCPhrase_cb (WE_ARE4, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_ARE5, &SelectAlienPIK);
+ NPCPhrase_cb (WE_ARE6, &SelectAlienZOQ);
+ NPCPhrase_cb (WE_ARE7, &SelectAlienPIK);
+
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7))
+ {
+ NPCPhrase_cb (INIT_HOME_HELLO0, &SelectAlienZOQ);
+ NPCPhrase_cb (INIT_HOME_HELLO1, &SelectAlienPIK);
+ NPCPhrase_cb (INIT_HOME_HELLO2, &SelectAlienZOQ);
+ NPCPhrase_cb (INIT_HOME_HELLO3, &SelectAlienPIK);
+ }
+ else
+ {
+ NPCPhrase_cb (SCOUT_HELLO0, &SelectAlienZOQ);
+ NPCPhrase_cb (SCOUT_HELLO1, &SelectAlienPIK);
+ NPCPhrase_cb (SCOUT_HELLO2, &SelectAlienZOQ);
+ NPCPhrase_cb (SCOUT_HELLO3, &SelectAlienPIK);
+ }
+
+ ZFPTalkSegue ((COUNT)~0);
+
+ AquaintZoqFot (0);
+ }
+ else
+ {
+ if (GET_GAME_STATE (ZOQFOT_DISTRESS))
+ {
+#define MAX_ZFP_SHIPS 4
+ NPCPhrase_cb (THANKS_FOR_RESCUE0, &SelectAlienZOQ);
+ NPCPhrase_cb (THANKS_FOR_RESCUE1, &SelectAlienPIK);
+ NPCPhrase_cb (THANKS_FOR_RESCUE2, &SelectAlienZOQ);
+ NPCPhrase_cb (THANKS_FOR_RESCUE3, &SelectAlienPIK);
+ NPCPhrase_cb (THANKS_FOR_RESCUE4, &SelectAlienZOQ);
+ NPCPhrase_cb (THANKS_FOR_RESCUE5, &SelectAlienPIK);
+ NPCPhrase_cb (THANKS_FOR_RESCUE6, &SelectAlienZOQ);
+ NPCPhrase_cb (THANKS_FOR_RESCUE7, &SelectAlienPIK);
+ NPCPhrase_cb (THANKS_FOR_RESCUE8, &SelectAlienZOQ);
+ NPCPhrase_cb (THANKS_FOR_RESCUE9, &SelectAlienPIK);
+ NPCPhrase_cb (THANKS_FOR_RESCUE10, &SelectAlienZOQ);
+ NPCPhrase_cb (THANKS_FOR_RESCUE11, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+
+ SET_GAME_STATE (ZOQFOT_DISTRESS, 0);
+ AddEscortShips (ZOQFOTPIK_SHIP, MAX_ZFP_SHIPS);
+ }
+ else
+ {
+ NumVisits = GET_GAME_STATE (ZOQFOT_HOME_VISITS);
+ if (CheckAlliance (ZOQFOTPIK_SHIP) != GOOD_GUY)
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase_cb (NEUTRAL_HOME_HELLO_10, &SelectAlienZOQ);
+ NPCPhrase_cb (NEUTRAL_HOME_HELLO_11, &SelectAlienPIK);
+ NPCPhrase_cb (NEUTRAL_HOME_HELLO_12, &SelectAlienZOQ);
+ NPCPhrase_cb (NEUTRAL_HOME_HELLO_13, &SelectAlienPIK);
+ break;
+ case 1:
+ NPCPhrase_cb (NEUTRAL_HOME_HELLO_20, &SelectAlienZOQ);
+ NPCPhrase_cb (NEUTRAL_HOME_HELLO_21, &SelectAlienPIK);
+ NPCPhrase_cb (NEUTRAL_HOME_HELLO_22, &SelectAlienZOQ);
+ NPCPhrase_cb (NEUTRAL_HOME_HELLO_23, &SelectAlienPIK);
+ --NumVisits;
+ break;
+ }
+ ZFPTalkSegue ((COUNT)~0);
+ }
+ else
+ {
+ switch (NumVisits++)
+ {
+ case 0:
+ NPCPhrase_cb (ALLIED_HOME_HELLO_10, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_11, &SelectAlienPIK);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_12, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_13, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 1:
+ NPCPhrase_cb (ALLIED_HOME_HELLO_20, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_21, &SelectAlienPIK);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_22, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_23, &SelectAlienPIK);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_24, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_25, &SelectAlienPIK);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_26, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_27, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 2:
+ NPCPhrase_cb (ALLIED_HOME_HELLO_30, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_31, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ break;
+ case 3:
+ NPCPhrase_cb (ALLIED_HOME_HELLO_40, &SelectAlienZOQ);
+ NPCPhrase_cb (ALLIED_HOME_HELLO_41, &SelectAlienPIK);
+ ZFPTalkSegue ((COUNT)~0);
+ --NumVisits;
+ break;
+ }
+ }
+ SET_GAME_STATE (ZOQFOT_HOME_VISITS, NumVisits);
+ }
+
+ ZoqFotHome (0);
+ }
+}
+
+static COUNT
+uninit_zoqfot (void)
+{
+ return (0);
+}
+
+static void
+post_zoqfot_enc (void)
+{
+ // nothing defined so far
+}
+
+LOCDATA*
+init_zoqfot_comm (void)
+{
+ LOCDATA *retval;
+
+ zoqfot_desc.init_encounter_func = Intro;
+ zoqfot_desc.post_encounter_func = post_zoqfot_enc;
+ zoqfot_desc.uninit_encounter_func = uninit_zoqfot;
+
+ zoqfot_desc.AlienTextWidth = (SIS_TEXT_WIDTH >> 1) - TEXT_X_OFFS;
+
+ if (CheckAlliance (ZOQFOTPIK_SHIP) == GOOD_GUY
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ {
+ setSegue (Segue_peace);
+ }
+ else
+ {
+ setSegue (Segue_hostile);
+ }
+
+ retval = &zoqfot_desc;
+
+ return (retval);
+}
+
diff --git a/src/uqm/commanim.c b/src/uqm/commanim.c
new file mode 100644
index 0000000..02e9362
--- /dev/null
+++ b/src/uqm/commanim.c
@@ -0,0 +1,623 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define COMM_INTERNAL
+#include "commanim.h"
+
+#include "comm.h"
+#include "element.h"
+#include "setup.h"
+#include "libs/compiler.h"
+#include "libs/graphics/cmap.h"
+#include "libs/mathlib.h"
+
+
+static TimeCount LastTime;
+static SEQUENCE Sequences[MAX_ANIMATIONS + 2];
+ // 2 extra for Talk and Transition animations
+static DWORD ActiveMask;
+ // Bit mask of all animations that are currently active.
+ // Bit 'i' is set if the animation with index 'i' is active.
+static ANIMATION_DESC TalkDesc;
+static ANIMATION_DESC TransitDesc;
+static SEQUENCE* Talk;
+static SEQUENCE* Transit;
+static COUNT FirstAmbient;
+static COUNT TotalSequences;
+
+
+static inline DWORD
+randomFrameRate (SEQUENCE *pSeq)
+{
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ return ADPtr->BaseFrameRate +
+ TFB_Random () % (ADPtr->RandomFrameRate + 1);
+}
+
+static inline DWORD
+randomRestartRate (SEQUENCE *pSeq)
+{
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ return ADPtr->BaseRestartRate +
+ TFB_Random () % (ADPtr->RandomRestartRate + 1);
+}
+
+static inline COUNT
+randomFrameIndex (SEQUENCE *pSeq, COUNT from)
+{
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ return from + TFB_Random () % (ADPtr->NumFrames - from);
+}
+
+static void
+SetupAmbientSequences (SEQUENCE *pSeq, COUNT Num)
+{
+ COUNT i;
+
+ for (i = 0; i < Num; ++i, ++pSeq)
+ {
+ ANIMATION_DESC *ADPtr = &CommData.AlienAmbientArray[i];
+
+ memset (pSeq, 0, sizeof (*pSeq));
+
+ pSeq->ADPtr = ADPtr;
+ if (ADPtr->AnimFlags & COLORXFORM_ANIM)
+ pSeq->AnimType = COLOR_ANIM;
+ else
+ pSeq->AnimType = PICTURE_ANIM;
+ pSeq->Direction = UP_DIR;
+ pSeq->FramesLeft = ADPtr->NumFrames;
+ // Default: first frame is neutral
+ if (ADPtr->AnimFlags & RANDOM_ANIM)
+ { // Set a random frame/colormap
+ pSeq->NextIndex = TFB_Random () % ADPtr->NumFrames;
+ }
+ else if (ADPtr->AnimFlags & YOYO_ANIM)
+ { // Skip the first frame/colormap (it's neutral)
+ pSeq->NextIndex = 1;
+ --pSeq->FramesLeft;
+ }
+ else if (ADPtr->AnimFlags & CIRCULAR_ANIM)
+ { // Exception that makes everything more painful:
+ // *Last* frame is neutral
+ pSeq->CurIndex = ADPtr->NumFrames - 1;
+ pSeq->NextIndex = 0;
+ }
+
+ pSeq->Alarm = randomRestartRate (pSeq) + 1;
+ }
+}
+
+static void
+SetupTalkSequence (SEQUENCE *pSeq, ANIMATION_DESC *ADPtr)
+{
+ memset (pSeq, 0, sizeof (*pSeq));
+ // Initially disabled, and until needed
+ ADPtr->AnimFlags |= ANIM_DISABLED;
+ pSeq->ADPtr = ADPtr;
+ pSeq->AnimType = PICTURE_ANIM;
+}
+
+static inline BOOLEAN
+animAtNeutralIndex (SEQUENCE *pSeq)
+{
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ if (ADPtr->AnimFlags & CIRCULAR_ANIM)
+ { // CIRCULAR_ANIM's neutral frame is the last
+ return pSeq->NextIndex == 0;
+ }
+ else
+ { // All others, neutral frame is the first
+ return pSeq->CurIndex == 0;
+ }
+}
+
+static inline BOOLEAN
+conflictsWithTalkingAnim (SEQUENCE *pSeq)
+{
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ return ADPtr->AnimFlags & CommData.AlienTalkDesc.AnimFlags & WAIT_TALKING;
+}
+
+static void
+ProcessColormapAnims (SEQUENCE *pSeq, COUNT Num)
+{
+ COUNT i;
+
+ for (i = 0; i < Num; ++i, ++pSeq)
+ {
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ if ((ADPtr->AnimFlags & ANIM_DISABLED)
+ || pSeq->AnimType != COLOR_ANIM
+ || !pSeq->Change)
+ continue;
+
+ XFormColorMap (GetColorMapAddress (
+ SetAbsColorMapIndex (CommData.AlienColorMap,
+ ADPtr->StartIndex + pSeq->CurIndex)),
+ pSeq->Alarm - 1);
+ pSeq->Change = FALSE;
+ }
+}
+
+static BOOLEAN
+AdvanceAmbientSequence (SEQUENCE *pSeq)
+{
+ BOOLEAN active;
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ --pSeq->FramesLeft;
+ // YOYO_ANIM does not actually end until it comes back
+ // in reverse direction, even if FramesLeft gets to 0 here
+ if (pSeq->FramesLeft
+ || ((ADPtr->AnimFlags & YOYO_ANIM) && pSeq->NextIndex != 0))
+ {
+ active = TRUE;
+ pSeq->Alarm = randomFrameRate (pSeq) + 1;
+ }
+ else
+ { // last animation frame
+ active = FALSE;
+ pSeq->Alarm = randomRestartRate (pSeq) + 1;
+
+ // RANDOM_ANIM must end on a neutral frame
+ if (ADPtr->AnimFlags & RANDOM_ANIM)
+ pSeq->NextIndex = 0;
+ }
+
+ // Will draw the next frame or change to next colormap
+ pSeq->CurIndex = pSeq->NextIndex;
+ pSeq->Change = TRUE;
+
+ if (pSeq->FramesLeft == 0)
+ { // Animation ended
+ // Set it up for the next round
+ pSeq->FramesLeft = ADPtr->NumFrames;
+
+ if (ADPtr->AnimFlags & YOYO_ANIM)
+ { // YOYO_ANIM never draws the first frame
+ // ("first" depends on direction)
+ --pSeq->FramesLeft;
+ pSeq->Direction = -pSeq->Direction;
+ }
+ else if (ADPtr->AnimFlags & CIRCULAR_ANIM)
+ { // Rewind the CIRCULAR_ANIM
+ // NextIndex will be brought to 0 just below
+ pSeq->NextIndex = -1;
+ }
+ // RANDOM_ANIM is setup just below
+ }
+
+ if (ADPtr->AnimFlags & RANDOM_ANIM)
+ pSeq->NextIndex = randomFrameIndex (pSeq, 0);
+ else
+ pSeq->NextIndex += pSeq->Direction;
+
+ return active;
+}
+
+static void
+ResetSequence (SEQUENCE *pSeq)
+{
+ // Reset the animation and cause a redraw of the neutral frame,
+ // assuming it is not ANIM_DISABLED
+ // NOTE: This does not handle CIRCULAR_ANIM properly
+ pSeq->Direction = NO_DIR;
+ pSeq->CurIndex = 0;
+ pSeq->Change = TRUE;
+}
+
+static void
+AdvanceTalkingSequence (SEQUENCE *pSeq, DWORD ElapsedTicks)
+{
+ // We use the actual descriptor for flags processing and
+ // a copied one for drawing. A copied one is updated only
+ // when it is safe to do so.
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ if (pSeq->Direction == NO_DIR)
+ { // just starting now
+ pSeq->Direction = UP_DIR;
+ // It's now safe to pick up new Talk descriptor if changed
+ // (e.g. Zoq and Pik taking turns to talk)
+ if (CommData.AlienTalkDesc.StartIndex != ADPtr->StartIndex)
+ { // copy the new one
+ *ADPtr = CommData.AlienTalkDesc;
+ }
+
+ assert (pSeq->CurIndex == 0);
+ pSeq->Alarm = 0; // now!
+ ADPtr->AnimFlags &= ~ANIM_DISABLED;
+ }
+
+ if (pSeq->Alarm > ElapsedTicks)
+ { // Not time yet
+ pSeq->Alarm -= ElapsedTicks;
+ return;
+ }
+
+ // Time to start or advance the animation
+ pSeq->Alarm = randomFrameRate (pSeq);
+ pSeq->Change = TRUE;
+ // Talking animation is like RANDOM_ANIM, except that
+ // random frames always alternate with the neutral one
+ // The animation does not stop until we reset it
+ if (pSeq->CurIndex == 0)
+ { // random frame next
+ pSeq->CurIndex = randomFrameIndex (pSeq, 1);
+ pSeq->Alarm += randomRestartRate (pSeq);
+ }
+ else
+ { // neutral frame next
+ pSeq->CurIndex = 0;
+ }
+}
+
+static BOOLEAN
+AdvanceTransitSequence (SEQUENCE *pSeq, DWORD ElapsedTicks)
+{
+ BOOLEAN done = FALSE;
+ // We use the actual descriptor for flags processing and
+ // a copied one for drawing. A copied one is updated only
+ // when it is safe to do so.
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ if (pSeq->Direction == NO_DIR)
+ { // just starting now
+ pSeq->Alarm = 0; // now!
+ ADPtr->AnimFlags &= ~ANIM_DISABLED;
+ }
+
+ if (pSeq->Alarm > ElapsedTicks)
+ { // Not time yet
+ pSeq->Alarm -= ElapsedTicks;
+ return FALSE;
+ }
+
+ // Time to start or advance the animation
+ pSeq->Change = TRUE;
+
+ if (pSeq->Direction == NO_DIR)
+ { // just starting now
+ pSeq->FramesLeft = ADPtr->NumFrames;
+ // Both INTRO and DONE may be set at the same time,
+ // when e.g. Zoq and Pik are taking turns to talk
+ // Process the DONE transition first to go into
+ // a neutral state before switching over.
+ if (CommData.AlienTransitionDesc.AnimFlags & TALK_DONE)
+ {
+ pSeq->Direction = DOWN_DIR;
+ pSeq->CurIndex = ADPtr->NumFrames - 1;
+ }
+ else if (CommData.AlienTransitionDesc.AnimFlags & TALK_INTRO)
+ {
+ pSeq->Direction = UP_DIR;
+ // It's now safe to pick up new Transition descriptor if changed
+ // (e.g. Zoq and Pik taking turns to talk)
+ if (CommData.AlienTransitionDesc.StartIndex
+ != ADPtr->StartIndex)
+ { // copy the new one
+ *ADPtr = CommData.AlienTransitionDesc;
+ }
+
+ pSeq->CurIndex = 0;
+ }
+ }
+
+ --pSeq->FramesLeft;
+ if (pSeq->FramesLeft == 0)
+ { // animation is done
+ if (pSeq->Direction == UP_DIR)
+ { // done with TALK_INTRO transition
+ CommData.AlienTransitionDesc.AnimFlags &= ~TALK_INTRO;
+ }
+ else if (pSeq->Direction == DOWN_DIR)
+ { // done with TALK_DONE transition
+ CommData.AlienTransitionDesc.AnimFlags &= ~TALK_DONE;
+
+ // Done with all transition frames
+ ADPtr->AnimFlags |= ANIM_DISABLED;
+ done = TRUE;
+ }
+ pSeq->Direction = NO_DIR;
+ }
+ else
+ { // next frame
+ pSeq->Alarm = randomFrameRate (pSeq);
+ pSeq->CurIndex += pSeq->Direction;
+ }
+
+ return done;
+}
+
+void
+InitCommAnimations (void)
+{
+ ActiveMask = 0;
+
+ TalkDesc = CommData.AlienTalkDesc;
+ TransitDesc = CommData.AlienTransitionDesc;
+
+ // Animation sequences have to be drawn in reverse, and
+ // talk animations have to be drawn last (so we add them first)
+ TotalSequences = 0;
+ // Transition animation last
+ Transit = Sequences + TotalSequences;
+ SetupTalkSequence (Transit, &TransitDesc);
+ ++TotalSequences;
+ // Talk animation second last
+ Talk = Sequences + TotalSequences;
+ SetupTalkSequence (Talk, &TalkDesc);
+ ++TotalSequences;
+ FirstAmbient = TotalSequences;
+ SetupAmbientSequences (Sequences + FirstAmbient, CommData.NumAnimations);
+ TotalSequences += CommData.NumAnimations;
+
+ LastTime = GetTimeCounter ();
+}
+
+BOOLEAN
+ProcessCommAnimations (BOOLEAN FullRedraw, BOOLEAN paused)
+{
+ if (paused)
+ { // Drive colormap xforms and nothing else
+ XFormColorMap_step ();
+ return FALSE;
+ }
+ else
+ {
+ COUNT i;
+ SEQUENCE *pSeq;
+ BOOLEAN Change;
+ BOOLEAN CanTalk = TRUE;
+ TimeCount CurTime;
+ DWORD ElapsedTicks;
+ DWORD NextActiveMask;
+
+ CurTime = GetTimeCounter ();
+ ElapsedTicks = CurTime - LastTime;
+ LastTime = CurTime;
+
+ // Process ambient animations
+ NextActiveMask = ActiveMask;
+ pSeq = Sequences + FirstAmbient;
+ for (i = 0; i < CommData.NumAnimations; ++i, ++pSeq)
+ {
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+ DWORD ActiveBit = 1L << i;
+
+ if (ADPtr->AnimFlags & ANIM_DISABLED)
+ continue;
+
+ if (pSeq->Direction == NO_DIR)
+ { // animation is paused
+ if (!conflictsWithTalkingAnim (pSeq))
+ { // start it up
+ pSeq->Direction = UP_DIR;
+ }
+ }
+ else if (pSeq->Alarm > ElapsedTicks)
+ { // not time yet
+ pSeq->Alarm -= ElapsedTicks;
+ }
+ else if (ActiveMask & ADPtr->BlockMask)
+ { // animation is blocked
+ assert (!(ActiveMask & ActiveBit) &&
+ "Check animations' mutual blocking masks");
+ assert (animAtNeutralIndex (pSeq));
+ // reschedule
+ pSeq->Alarm = randomRestartRate (pSeq) + 1;
+ continue;
+ }
+ else
+ { // Time to start or advance the animation
+ if (AdvanceAmbientSequence (pSeq))
+ { // Animation is active this frame and the next
+ ActiveMask |= ActiveBit;
+ NextActiveMask |= ActiveBit;
+ }
+ else
+ { // Animation remains active this frame but not the next
+ // This keeps any conflicting animations (BlockMask)
+ // from activating in the same frame and scribbling over
+ // our last image.
+ NextActiveMask &= ~ActiveBit;
+ }
+ }
+
+ if (pSeq->AnimType == PICTURE_ANIM && pSeq->Direction != NO_DIR
+ && conflictsWithTalkingAnim (pSeq))
+ {
+ // We want to talk, but this is a running picture animation
+ // which conflicts with the talking animation
+ // See if it is safe to stop it now.
+ if (animAtNeutralIndex (pSeq))
+ { // pause the animation
+ pSeq->Direction = NO_DIR;
+ NextActiveMask &= ~ActiveBit;
+ // Talk animation is drawn last, so it's not a conflict
+ // for this frame. The talk animation will be drawn
+ // over the neutral frame.
+ }
+ else
+ { // Otherwise, let the animation run until it's safe
+ CanTalk = FALSE;
+ }
+ }
+ }
+ // All ambient animations have been processed. Advance the mask.
+ ActiveMask = NextActiveMask;
+
+ // Process the talking and transition animations
+ if (CanTalk && haveTalkingAnim () && runningTalkingAnim ())
+ {
+ BOOLEAN done = FALSE;
+
+ if (signaledStopTalkingAnim () && haveTransitionAnim ())
+ { // Run the transition. We will clear everything
+ // when it is done
+ CommData.AlienTransitionDesc.AnimFlags |= TALK_DONE;
+ }
+
+ if (CommData.AlienTransitionDesc.AnimFlags
+ & (TALK_INTRO | TALK_DONE))
+ { // Transitioning in or out of talking
+ if ((CommData.AlienTransitionDesc.AnimFlags & TALK_DONE)
+ && Transit->Direction == NO_DIR)
+ { // This is needed when switching talking anims
+ ResetSequence (Talk);
+ }
+ done = AdvanceTransitSequence (Transit, ElapsedTicks);
+ }
+ else if (!signaledStopTalkingAnim ())
+ { // Talking, transition is done
+ AdvanceTalkingSequence (Talk, ElapsedTicks);
+ }
+ else
+ { // Not talking
+ ResetSequence (Talk);
+ done = TRUE;
+ }
+
+ if (signaledStopTalkingAnim () && done)
+ {
+ clearRunTalkingAnim ();
+ clearStopTalkingAnim ();
+ }
+ }
+ else
+ { // Not talking -- disable talking anim if it is done
+ if (Talk->Direction == NO_DIR)
+ TalkDesc.AnimFlags |= ANIM_DISABLED;
+ }
+
+ BatchGraphics ();
+
+ // Draw all animations
+ {
+ BOOLEAN ColorChange = XFormColorMap_step ();
+
+ if (ColorChange)
+ FullRedraw = TRUE;
+
+ // Colormap animations are processed separately
+ // from picture anims (see XFormColorMap_step)
+ ProcessColormapAnims (Sequences + FirstAmbient,
+ CommData.NumAnimations);
+
+ Change = DrawAlienFrame (Sequences, TotalSequences, FullRedraw);
+ if (FullRedraw)
+ Change = TRUE;
+ }
+
+ UnbatchGraphics ();
+
+ // Post-process ambient animations
+ pSeq = Sequences + FirstAmbient;
+ for (i = 0; i < CommData.NumAnimations; ++i, ++pSeq)
+ {
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+ DWORD ActiveBit = 1L << i;
+
+ if (ADPtr->AnimFlags & ANIM_DISABLED)
+ continue;
+
+ // We can only disable a one-shot anim here, otherwise the
+ // last frame will not be drawn
+ if ((ADPtr->AnimFlags & ONE_SHOT_ANIM)
+ && !(NextActiveMask & ActiveBit))
+ { // One-shot animation, inactive next frame
+ ADPtr->AnimFlags |= ANIM_DISABLED;
+ }
+ }
+
+ return Change;
+ }
+}
+
+BOOLEAN
+DrawAlienFrame (SEQUENCE *Sequences, COUNT Num, BOOLEAN fullRedraw)
+{
+ int i;
+ STAMP s;
+ BOOLEAN Change = FALSE;
+
+ BatchGraphics ();
+
+ s.origin.x = -SAFE_X;
+ s.origin.y = 0;
+
+ if (fullRedraw)
+ {
+ // Draw the main frame
+ s.frame = CommData.AlienFrame;
+ DrawStamp (&s);
+
+ // Draw any static frames (has to be in reverse)
+ for (i = CommData.NumAnimations - 1; i >= 0; --i)
+ {
+ ANIMATION_DESC *ADPtr = &CommData.AlienAmbientArray[i];
+
+ if (ADPtr->AnimFlags & ANIM_MASK)
+ continue;
+
+ ADPtr->AnimFlags |= ANIM_DISABLED;
+
+ if (!(ADPtr->AnimFlags & COLORXFORM_ANIM))
+ { // It's a static frame (e.g. Flagship picture at Starbase)
+ s.frame = SetAbsFrameIndex (CommData.AlienFrame,
+ ADPtr->StartIndex);
+ DrawStamp (&s);
+ }
+ }
+ }
+
+ if (Sequences)
+ { // Draw the animation sequences (has to be in reverse)
+ for (i = Num - 1; i >= 0; --i)
+ {
+ SEQUENCE *pSeq = &Sequences[i];
+ ANIMATION_DESC *ADPtr = pSeq->ADPtr;
+
+ if ((ADPtr->AnimFlags & ANIM_DISABLED)
+ || pSeq->AnimType != PICTURE_ANIM)
+ continue;
+
+ // Draw current animation frame only if changed
+ if (!fullRedraw && !pSeq->Change)
+ continue;
+
+ s.frame = SetAbsFrameIndex (CommData.AlienFrame,
+ ADPtr->StartIndex + pSeq->CurIndex);
+ DrawStamp (&s);
+ pSeq->Change = FALSE;
+
+ Change = TRUE;
+ }
+ }
+
+ UnbatchGraphics ();
+
+ return Change;
+}
diff --git a/src/uqm/commanim.h b/src/uqm/commanim.h
new file mode 100644
index 0000000..40527f5
--- /dev/null
+++ b/src/uqm/commanim.h
@@ -0,0 +1,141 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_COMMANIM_H_
+#define UQM_COMMANIM_H_
+
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// Some background: every animation has a neutral frame which returns
+// the image to the state it was in before the animation began. Which
+// frame is neutral depends on the animation type.
+// Animation types:
+#define RANDOM_ANIM (1 << 0)
+ // The next index is randomly chosen.
+ // The first frame is neutral
+#define CIRCULAR_ANIM (1 << 1)
+ // After the last index has been reached, the animation starts over.
+ // The last frame is neutral. This complicates the animation task.
+#define YOYO_ANIM (1 << 2)
+ // After the last index has been reached, the order that the
+ // animation frames are used is reversed.
+ // The first frame is neutral
+#define ANIM_MASK (RANDOM_ANIM | CIRCULAR_ANIM | YOYO_ANIM)
+ // Mask of all animation types.
+ // Static frames do not have any of these flags set.
+
+#define WAIT_TALKING (1 << 3)
+ // This is set in AlienTalkDesc when the talking animation is active
+ // or should be active.
+ // In AlienAmbientArray, this is set for those ambient animations
+ // which can not be active while the talking animation is active.
+ // Such animations stop at the end of the current animation cycle
+ // when the talking animation activates.
+#define PAUSE_TALKING (1 << 4)
+ // Set in AlienTalkDesc when we do not want the talking animation
+#define TALK_INTRO (1 << 5)
+ // In AlienTransitionDesc: indicates a transition to talking state
+#define TALK_DONE (1 << 6)
+ // In AlienTransitionDesc: indicates a transition to silent state
+ // In AlienTalkDesc: signals the end of talking animation
+#define ANIM_DISABLED (1 << 7)
+
+#define COLORXFORM_ANIM PAUSE_TALKING
+
+#define ONE_SHOT_ANIM TALK_INTRO
+ // Set in AlienAmbientArray for animations that should be
+ // disabled after they run once.
+
+typedef struct
+{
+ COUNT StartIndex;
+ // Index of the first image (for image animation) or
+ // index of the first color map (for palette animation)
+ BYTE NumFrames;
+ // Number of frames in the animation.
+
+ BYTE AnimFlags;
+ // One of RANDOM_ANIM, CIRCULAR_ANIM, or YOYO_ANIM
+ // plus flags (WAIT_TALKING, ANIM_DISABLED)
+
+ COUNT BaseFrameRate;
+ // Minimum interframe delay
+ COUNT RandomFrameRate;
+ // Maximum additional interframe delay
+ // Actual delay: BaseFrameRate + Random(0..RandomFrameRate)
+ COUNT BaseRestartRate;
+ // Minimum delay before restarting animation
+ COUNT RandomRestartRate;
+ // Maximum additional delay before restarting animation
+ // Actual delay: BaseRestartRate + Random(0..RandomRestartRate)
+
+ DWORD BlockMask;
+ // Bit mask of the indices of all animations that can not
+ // be active at the same time as this animation, usually,
+ // due to the image overlap conflicts.
+} ANIMATION_DESC;
+
+#define MAX_ANIMATIONS 20
+
+
+#ifdef COMM_INTERNAL
+
+typedef enum
+{
+ DOWN_DIR = -1, // Animation indices are decreasing
+ NO_DIR = 0,
+ UP_DIR = 1, // Animation indices are increasing
+} ANIM_DIR;
+
+typedef enum
+{
+ PICTURE_ANIM,
+ // Parts of a picture are replaced
+ COLOR_ANIM
+ // Colormap tricks on a picture
+} ANIM_TYPE;
+
+// Describes an active animation.
+struct SEQUENCE
+{
+ ANIMATION_DESC *ADPtr;
+ DWORD Alarm;
+ ANIM_DIR Direction;
+ COUNT CurIndex;
+ COUNT NextIndex;
+ COUNT FramesLeft;
+ ANIM_TYPE AnimType;
+ BOOLEAN Change;
+};
+#endif
+
+typedef struct SEQUENCE SEQUENCE;
+
+// Returns TRUE if there was an animation change
+extern BOOLEAN DrawAlienFrame (SEQUENCE *pSeq, COUNT Num, BOOLEAN fullRedraw);
+extern void InitCommAnimations (void);
+extern BOOLEAN ProcessCommAnimations (BOOLEAN fullRedraw, BOOLEAN paused);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_COMMANIM_H_ */
diff --git a/src/uqm/commglue.c b/src/uqm/commglue.c
new file mode 100644
index 0000000..a7b514c
--- /dev/null
+++ b/src/uqm/commglue.c
@@ -0,0 +1,421 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "commglue.h"
+
+#include "battle.h"
+ // For instantVictory
+#include "races.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <assert.h>
+#include "libs/log.h"
+
+static int NPCNumberPhrase (int number, const char *fmt, UNICODE **ptrack);
+
+// The CallbackFunction is queued and executes synchronously
+// on the Starcon2Main thread
+void
+NPCPhrase_cb (int index, CallbackFunction cb)
+{
+ UNICODE *pStr, buf[400];
+ void *pClip, *pTimeStamp;
+
+ switch (index)
+ {
+ case GLOBAL_PLAYER_NAME:
+ pStr = GLOBAL_SIS (CommanderName);
+ pClip = 0;
+ pTimeStamp = 0;
+ break;
+ case GLOBAL_SHIP_NAME:
+ pStr = GLOBAL_SIS (ShipName);
+ pClip = 0;
+ pTimeStamp = 0;
+ break;
+ case 0:
+ {
+ return;
+ }
+ default:
+ if (index < 0)
+ { // One of the alliance name variants
+ COUNT i;
+ STRING S;
+
+ index -= GLOBAL_ALLIANCE_NAME;
+
+ i = GET_GAME_STATE (NEW_ALLIANCE_NAME);
+ S = SetAbsStringTableIndex (CommData.ConversationPhrases, (index - 1) + i);
+ strcpy (buf, (UNICODE *)GetStringAddress (S));
+ if (i == 3)
+ strcat (buf, GLOBAL_SIS (CommanderName));
+
+ pStr = buf;
+ pClip = 0;
+ pTimeStamp = 0;
+ }
+ else
+ {
+ pStr = (UNICODE *)GetStringAddress (
+ SetAbsStringTableIndex (CommData.ConversationPhrases, index - 1)
+ );
+ pClip = GetStringSoundClip (
+ SetAbsStringTableIndex (CommData.ConversationPhrases, index - 1)
+ );
+ pTimeStamp = GetStringTimeStamp (
+ SetAbsStringTableIndex (CommData.ConversationPhrases, index - 1)
+ );
+ }
+ break;
+ }
+
+ SpliceTrack (pClip, pStr, pTimeStamp, cb);
+}
+
+// Special case variant: prevents page breaks.
+void
+NPCPhrase_splice (int index)
+{
+ UNICODE *pStr;
+ void *pClip;
+
+ assert (index >= 0);
+ if (index == 0)
+ return;
+
+ pStr = (UNICODE *)GetStringAddress (
+ SetAbsStringTableIndex (CommData.ConversationPhrases, index - 1));
+ pClip = GetStringSoundClip (
+ SetAbsStringTableIndex (CommData.ConversationPhrases, index - 1));
+
+ if (!pClip)
+ { // Just appending some text
+ SpliceTrack (NULL, pStr, NULL, NULL);
+ }
+ else
+ { // Splicing in some voice
+ UNICODE *tracks[] = {NULL, NULL};
+
+ tracks[0] = pClip;
+ SpliceMultiTrack (tracks, pStr);
+ }
+}
+
+void
+NPCNumber (int number, const char *fmt)
+{
+ UNICODE buf[32];
+
+ if (!fmt)
+ fmt = "%d";
+
+ if (CommData.AlienNumberSpeech)
+ {
+ NPCNumberPhrase (number, fmt, NULL);
+ return;
+ }
+
+ // just splice in the subtitle text
+ snprintf (buf, sizeof buf, fmt, number);
+ SpliceTrack (NULL, buf, NULL, NULL);
+}
+
+static int
+NPCNumberPhrase (int number, const char *fmt, UNICODE **ptrack)
+{
+#define MAX_NUMBER_TRACKS 20
+ NUMBER_SPEECH speech = CommData.AlienNumberSpeech;
+ COUNT i;
+ int queued = 0;
+ int toplevel = 0;
+ UNICODE *TrackNames[MAX_NUMBER_TRACKS];
+ UNICODE numbuf[60];
+ const SPEECH_DIGIT* dig = NULL;
+
+ if (!speech)
+ return 0;
+
+ if (!ptrack)
+ {
+ toplevel = 1;
+ if (!fmt)
+ fmt = "%d";
+ sprintf (numbuf, fmt, number);
+ ptrack = TrackNames;
+ }
+
+ for (i = 0; i < speech->NumDigits; ++i)
+ {
+ int quot;
+
+ dig = speech->Digits + i;
+ quot = number / dig->Divider;
+
+ if (quot == 0)
+ continue;
+ quot -= dig->Subtrahend;
+ if (quot < 0)
+ continue;
+
+ if (dig->StrDigits)
+ {
+ COUNT index;
+
+ assert (quot < 10);
+ index = dig->StrDigits[quot];
+ if (index == 0)
+ continue;
+ index -= 1;
+
+ *ptrack++ = GetStringSoundClip (SetAbsStringTableIndex (
+ CommData.ConversationPhrases, index
+ ));
+ queued++;
+ }
+ else
+ {
+ int ctracks = NPCNumberPhrase (quot, NULL, ptrack);
+ ptrack += ctracks;
+ queued += ctracks;
+ }
+
+ if (dig->Names != 0)
+ {
+ SPEECH_DIGITNAME* name;
+
+ for (name = dig->Names; name->Divider; ++name)
+ {
+ if (number % name->Divider <= name->MaxRemainder)
+ {
+ *ptrack++ = GetStringSoundClip (
+ SetAbsStringTableIndex (
+ CommData.ConversationPhrases, name->StrIndex - 1));
+ queued++;
+ break;
+ }
+ }
+ }
+ else if (dig->CommonNameIndex != 0)
+ {
+ *ptrack++ = GetStringSoundClip (SetAbsStringTableIndex (
+ CommData.ConversationPhrases, dig->CommonNameIndex - 1));
+ queued++;
+ }
+
+ number %= dig->Divider;
+ }
+
+ if (toplevel)
+ {
+ if (queued == 0)
+ { // nothing queued, say "zero"
+ assert (number == 0);
+ *ptrack++ = GetStringSoundClip (SetAbsStringTableIndex (
+ CommData.ConversationPhrases, dig->StrDigits[number] - 1));
+ }
+ *ptrack++ = NULL; // term
+
+ SpliceMultiTrack (TrackNames, numbuf);
+ }
+
+ return queued;
+}
+
+void
+GetAllianceName (UNICODE *buf, RESPONSE_REF name_1)
+{
+ COUNT i;
+ STRING S;
+
+ i = GET_GAME_STATE (NEW_ALLIANCE_NAME);
+ S = SetAbsStringTableIndex (CommData.ConversationPhrases, (name_1 - 1) + i);
+ // XXX: this should someday be changed so that the function takes
+ // the buffer size as an argument
+ strcpy (buf, (UNICODE *)GetStringAddress (S));
+ if (i == 3)
+ {
+ strcat (buf, GLOBAL_SIS (CommanderName));
+ strcat (buf, (UNICODE *)GetStringAddress (SetRelStringTableIndex (S, 1)));
+ }
+}
+
+void
+construct_response (UNICODE *buf, int R /* promoted from RESPONSE_REF */, ...)
+{
+ UNICODE *buf_start = buf;
+ UNICODE *name;
+ va_list vlist;
+
+ va_start (vlist, R);
+
+ do
+ {
+ COUNT len;
+ STRING S;
+
+ S = SetAbsStringTableIndex (CommData.ConversationPhrases, R - 1);
+
+ strcpy (buf, (UNICODE *)GetStringAddress (S));
+
+ len = strlen (buf);
+
+ buf += len;
+
+ name = va_arg (vlist, UNICODE *);
+
+ if (name)
+ {
+ len = strlen (name);
+ strcpy (buf, name);
+ buf += len;
+
+ /*
+ if ((R = va_arg (vlist, RESPONSE_REF)) == (RESPONSE_REF)-1)
+ name = 0;
+ */
+
+ R = va_arg(vlist, int);
+ if (R == ((RESPONSE_REF) -1))
+ name = 0;
+ }
+ } while (name);
+ va_end (vlist);
+
+ *buf = '\0';
+
+ // XXX: this should someday be changed so that the function takes
+ // the buffer size as an argument
+ if ((buf_start == shared_phrase_buf) &&
+ (buf > shared_phrase_buf + sizeof (shared_phrase_buf)))
+ {
+ log_add (log_Fatal, "Error: shared_phrase_buf size exceeded,"
+ " please increase!\n");
+ exit (EXIT_FAILURE);
+ }
+}
+
+void
+setSegue (Segue segue)
+{
+ switch (segue)
+ {
+ case Segue_peace:
+ SET_GAME_STATE (BATTLE_SEGUE, 0);
+ break;
+ case Segue_hostile:
+ SET_GAME_STATE (BATTLE_SEGUE, 1);
+ break;
+ case Segue_victory:
+ instantVictory = TRUE;
+ SET_GAME_STATE (BATTLE_SEGUE, 1);
+ break;
+ case Segue_defeat:
+ SET_GAME_STATE (BATTLE_SEGUE, 0);
+ GLOBAL_SIS(CrewEnlisted) = (COUNT)~0;
+ GLOBAL(CurrentActivity) |= CHECK_RESTART;
+ break;
+ }
+}
+
+Segue
+getSegue (void)
+{
+ if (GET_GAME_STATE(BATTLE_SEGUE) == 0) {
+ if (GLOBAL_SIS(CrewEnlisted) == (COUNT)~0 &&
+ (GLOBAL(CurrentActivity) & CHECK_RESTART)) {
+ return Segue_defeat;
+ } else {
+ return Segue_peace;
+ }
+ } else /* GET_GAME_STATE(BATTLE_SEGUE) == 1) */ {
+ if (instantVictory) {
+ return Segue_victory;
+ } else {
+ return Segue_hostile;
+ }
+ }
+}
+
+LOCDATA*
+init_race (CONVERSATION comm_id)
+{
+ switch (comm_id)
+ {
+ case ARILOU_CONVERSATION:
+ return init_arilou_comm ();
+ case BLACKURQ_CONVERSATION:
+ return init_blackurq_comm ();
+ case CHMMR_CONVERSATION:
+ return init_chmmr_comm ();
+ case COMMANDER_CONVERSATION:
+ if (!GET_GAME_STATE (STARBASE_AVAILABLE))
+ return init_commander_comm ();
+ else
+ return init_starbase_comm ();
+ case DRUUGE_CONVERSATION:
+ return init_druuge_comm ();
+ case ILWRATH_CONVERSATION:
+ return init_ilwrath_comm ();
+ case MELNORME_CONVERSATION:
+ return init_melnorme_comm ();
+ case MYCON_CONVERSATION:
+ return init_mycon_comm ();
+ case ORZ_CONVERSATION:
+ return init_orz_comm ();
+ case PKUNK_CONVERSATION:
+ return init_pkunk_comm ();
+ case SHOFIXTI_CONVERSATION:
+ return init_shofixti_comm ();
+ case SLYLANDRO_CONVERSATION:
+ return init_slyland_comm ();
+ case SLYLANDRO_HOME_CONVERSATION:
+ return init_slylandro_comm ();
+ case SPATHI_CONVERSATION:
+ if (!(GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) & (1 << 7)))
+ return init_spathi_comm ();
+ else
+ return init_spahome_comm ();
+ case SUPOX_CONVERSATION:
+ return init_supox_comm ();
+ case SYREEN_CONVERSATION:
+ return init_syreen_comm ();
+ case TALKING_PET_CONVERSATION:
+ return init_talkpet_comm ();
+ case THRADD_CONVERSATION:
+ return init_thradd_comm ();
+ case UMGAH_CONVERSATION:
+ return init_umgah_comm ();
+ case URQUAN_CONVERSATION:
+ return init_urquan_comm ();
+ case UTWIG_CONVERSATION:
+ return init_utwig_comm ();
+ case VUX_CONVERSATION:
+ return init_vux_comm ();
+ case YEHAT_REBEL_CONVERSATION:
+ return init_rebel_yehat_comm ();
+ case YEHAT_CONVERSATION:
+ return init_yehat_comm ();
+ case ZOQFOTPIK_CONVERSATION:
+ return init_zoqfot_comm ();
+ default:
+ return init_chmmr_comm ();
+ }
+}
diff --git a/src/uqm/commglue.h b/src/uqm/commglue.h
new file mode 100644
index 0000000..5a1e440
--- /dev/null
+++ b/src/uqm/commglue.h
@@ -0,0 +1,183 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_COMMGLUE_H_
+#define UQM_COMMGLUE_H_
+
+#include "globdata.h"
+#include "resinst.h"
+#include "libs/sound/trackplayer.h"
+#include "libs/callback.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef enum {
+ ARILOU_CONVERSATION,
+ CHMMR_CONVERSATION,
+ COMMANDER_CONVERSATION,
+ ORZ_CONVERSATION,
+ PKUNK_CONVERSATION,
+ SHOFIXTI_CONVERSATION,
+ SPATHI_CONVERSATION,
+ SUPOX_CONVERSATION,
+ THRADD_CONVERSATION,
+ UTWIG_CONVERSATION,
+ VUX_CONVERSATION,
+ YEHAT_CONVERSATION,
+ MELNORME_CONVERSATION,
+ DRUUGE_CONVERSATION,
+ ILWRATH_CONVERSATION,
+ MYCON_CONVERSATION,
+ SLYLANDRO_CONVERSATION,
+ UMGAH_CONVERSATION,
+ URQUAN_CONVERSATION,
+ ZOQFOTPIK_CONVERSATION,
+ SYREEN_CONVERSATION,
+ BLACKURQ_CONVERSATION,
+ TALKING_PET_CONVERSATION,
+ SLYLANDRO_HOME_CONVERSATION,
+ URQUAN_DRONE_CONVERSATION,
+ YEHAT_REBEL_CONVERSATION,
+ INVALID_CONVERSATION,
+} CONVERSATION;
+
+extern LOCDATA CommData;
+extern UNICODE shared_phrase_buf[2048];
+
+#define PLAYER_SAID(r,i) ((r)==(i))
+#define PHRASE_ENABLED(p) \
+ (*(UNICODE *)GetStringAddress ( \
+ SetAbsStringTableIndex (CommData.ConversationPhrases, (p)-1) \
+ ) != '\0')
+#define DISABLE_PHRASE(p) \
+ (*(UNICODE *)GetStringAddress ( \
+ SetAbsStringTableIndex (CommData.ConversationPhrases, (p)-1) \
+ ) = '\0')
+
+#define Response(i,a) \
+ DoResponsePhrase(i,(RESPONSE_FUNC)a,0)
+
+enum
+{
+ GLOBAL_PLAYER_NAME = -1000000,
+ GLOBAL_SHIP_NAME,
+ GLOBAL_ALLIANCE_NAME,
+};
+
+typedef COUNT RESPONSE_REF;
+
+typedef void (*RESPONSE_FUNC) (RESPONSE_REF R);
+
+extern void DoResponsePhrase (RESPONSE_REF R, RESPONSE_FUNC
+ response_func, UNICODE *ContstructStr);
+extern void DoNPCPhrase (UNICODE *pStr);
+
+// The CallbackFunction is queued and executes synchronously
+// on the Starcon2Main thread
+extern void NPCPhrase_cb (int index, CallbackFunction cb);
+#define NPCPhrase(index) NPCPhrase_cb ((index), NULL)
+extern void NPCPhrase_splice (int index);
+extern void NPCNumber (int number, const char *fmt);
+
+#define ALLIANCE_NAME_BUFSIZE 256
+extern void GetAllianceName (UNICODE *buf, RESPONSE_REF name_1);
+
+extern void construct_response (UNICODE *buf, int R /* promoted from
+ RESPONSE_REF */, ...);
+
+typedef enum {
+ Segue_peace,
+ // When initiating a conversation, open comms directly.
+ // When terminating a conversation, depart in peace.
+ Segue_hostile,
+ // When initiating a conversation, offer the choice to attack.
+ // When terminating a conversation, go into battle.
+ Segue_victory,
+ // (when terminating a conversation) instant victory
+ Segue_defeat,
+ // (when terminating a conversation) game over
+} Segue;
+
+void setSegue (Segue segue);
+Segue getSegue (void);
+
+extern LOCDATA* init_race (CONVERSATION comm_id);
+
+extern LOCDATA* init_arilou_comm (void);
+
+extern LOCDATA* init_blackurq_comm (void);
+
+extern LOCDATA* init_chmmr_comm (void);
+
+extern LOCDATA* init_commander_comm (void);
+
+extern LOCDATA* init_druuge_comm (void);
+
+extern LOCDATA* init_ilwrath_comm (void);
+
+extern LOCDATA* init_melnorme_comm (void);
+
+extern LOCDATA* init_mycon_comm (void);
+
+extern LOCDATA* init_orz_comm (void);
+
+extern LOCDATA* init_pkunk_comm (void);
+
+extern LOCDATA* init_rebel_yehat_comm (void);
+
+extern LOCDATA* init_shofixti_comm (void);
+
+extern LOCDATA* init_slyland_comm (void);
+
+extern LOCDATA* init_slylandro_comm (void);
+
+extern LOCDATA* init_spahome_comm (void);
+
+extern LOCDATA* init_spathi_comm (void);
+
+extern LOCDATA* init_starbase_comm (void);
+
+extern LOCDATA* init_supox_comm (void);
+
+extern LOCDATA* init_syreen_comm (void);
+
+extern LOCDATA* init_talkpet_comm (void);
+
+extern LOCDATA* init_thradd_comm (void);
+
+extern LOCDATA* init_umgah_comm (void);
+
+extern LOCDATA* init_urquan_comm (void);
+
+extern LOCDATA* init_utwig_comm (void);
+
+extern LOCDATA* init_vux_comm (void);
+
+extern LOCDATA* init_yehat_comm (void);
+
+extern LOCDATA* init_zoqfot_comm (void);
+
+extern LOCDATA* init_umgah_comm (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_COMMGLUE_H_ */
diff --git a/src/uqm/confirm.c b/src/uqm/confirm.c
new file mode 100644
index 0000000..a24472b
--- /dev/null
+++ b/src/uqm/confirm.c
@@ -0,0 +1,250 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "controls.h"
+#include "colors.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "gamestr.h"
+#include "util.h"
+#include "libs/graphics/widgets.h"
+#include "libs/sound/trackplayer.h"
+#include "libs/log.h"
+#include "libs/resource/stringbank.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+
+
+#define CONFIRM_WIN_WIDTH 80
+#define CONFIRM_WIN_HEIGHT 22
+
+static void
+DrawConfirmationWindow (BOOLEAN answer)
+{
+ Color oldfg = SetContextForeGroundColor (MENU_TEXT_COLOR);
+ FONT oldfont = SetContextFont (StarConFont);
+ FRAME oldFontEffect = SetContextFontEffect (NULL);
+ RECT r;
+ TEXT t;
+
+ BatchGraphics ();
+ r.corner.x = (SCREEN_WIDTH - CONFIRM_WIN_WIDTH) >> 1;
+ r.corner.y = (SCREEN_HEIGHT - CONFIRM_WIN_HEIGHT) >> 1;
+ r.extent.width = CONFIRM_WIN_WIDTH;
+ r.extent.height = CONFIRM_WIN_HEIGHT;
+ DrawShadowedBox (&r, SHADOWBOX_BACKGROUND_COLOR,
+ SHADOWBOX_DARK_COLOR, SHADOWBOX_MEDIUM_COLOR);
+
+ t.baseline.x = r.corner.x + (r.extent.width >> 1);
+ t.baseline.y = r.corner.y + 8;
+ t.pStr = GAME_STRING (QUITMENU_STRING_BASE); // "Really Quit?"
+ t.align = ALIGN_CENTER;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += 10;
+ t.baseline.x = r.corner.x + (r.extent.width >> 2);
+ t.pStr = GAME_STRING (QUITMENU_STRING_BASE + 1); // "Yes"
+ SetContextForeGroundColor (answer ? MENU_HIGHLIGHT_COLOR : MENU_TEXT_COLOR);
+ font_DrawText (&t);
+ t.baseline.x += (r.extent.width >> 1);
+ t.pStr = GAME_STRING (QUITMENU_STRING_BASE + 2); // "No"
+ SetContextForeGroundColor (answer ? MENU_TEXT_COLOR : MENU_HIGHLIGHT_COLOR);
+ font_DrawText (&t);
+
+ UnbatchGraphics ();
+
+ SetContextFontEffect (oldFontEffect);
+ SetContextFont (oldfont);
+ SetContextForeGroundColor (oldfg);
+}
+
+BOOLEAN
+DoConfirmExit (void)
+{
+ BOOLEAN result;
+
+ if (PlayingTrack ())
+ PauseTrack ();
+
+ PauseFlash ();
+
+ {
+ RECT r;
+ STAMP s;
+ RECT ctxRect;
+ CONTEXT oldContext;
+ RECT oldRect;
+ BOOLEAN response = FALSE, done;
+
+ oldContext = SetContext (ScreenContext);
+ GetContextClipRect (&oldRect);
+ SetContextClipRect (NULL);
+
+ GetContextClipRect (&ctxRect);
+ r.extent.width = CONFIRM_WIN_WIDTH + 4;
+ r.extent.height = CONFIRM_WIN_HEIGHT + 4;
+ r.corner.x = (ctxRect.extent.width - r.extent.width) >> 1;
+ r.corner.y = (ctxRect.extent.height - r.extent.height) >> 1;
+ s = SaveContextFrame (&r);
+ SetSystemRect (&r);
+
+ DrawConfirmationWindow (response);
+ FlushGraphics ();
+
+ FlushInput ();
+ done = FALSE;
+
+ do {
+ // Forbid recursive calls or pausing here!
+ ExitRequested = FALSE;
+ GamePaused = FALSE;
+ UpdateInputState ();
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ { // something else triggered an exit
+ done = TRUE;
+ response = TRUE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ done = TRUE;
+ PlayMenuSound (MENU_SOUND_SUCCESS);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ done = TRUE;
+ response = FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_LEFT] || PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ response = !response;
+ DrawConfirmationWindow (response);
+ PlayMenuSound (MENU_SOUND_MOVE);
+ }
+ SleepThread (ONE_SECOND / 30);
+ } while (!done);
+
+ // Restore the screen under the confirmation window
+ DrawStamp (&s);
+ DestroyDrawable (ReleaseDrawable (s.frame));
+ ClearSystemRect ();
+ if (response || (GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ result = TRUE;
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ }
+ else
+ {
+ result = FALSE;
+ }
+ ExitRequested = FALSE;
+ GamePaused = FALSE;
+ FlushInput ();
+ SetContextClipRect (&oldRect);
+ SetContext (oldContext);
+ }
+
+ ContinueFlash ();
+
+ if (PlayingTrack ())
+ ResumeTrack ();
+
+ return (result);
+}
+
+typedef struct popup_state
+{
+ // standard state required by DoInput
+ BOOLEAN (*InputFunc) (struct popup_state *self);
+} POPUP_STATE;
+
+static BOOLEAN
+DoPopup (struct popup_state *self)
+{
+ (void)self;
+ SleepThread (ONE_SECOND / 20);
+ return !(PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL] ||
+ (GLOBAL (CurrentActivity) & CHECK_ABORT));
+}
+
+void
+DoPopupWindow (const char *msg)
+{
+ stringbank *bank = StringBank_Create ();
+ const char *lines[30];
+ WIDGET_LABEL label;
+ STAMP s;
+ CONTEXT oldContext;
+ RECT oldRect;
+ RECT windowRect;
+ POPUP_STATE state;
+ MENU_SOUND_FLAGS s0, s1;
+ InputFrameCallback *oldCallback;
+
+ if (!bank)
+ {
+ log_add (log_Fatal, "FATAL: Memory exhaustion when preparing popup window");
+ exit (EXIT_FAILURE);
+ }
+
+ label.tag = WIDGET_TYPE_LABEL;
+ label.parent = NULL;
+ label.handleEvent = Widget_HandleEventIgnoreAll;
+ label.receiveFocus = Widget_ReceiveFocusRefuseFocus;
+ label.draw = Widget_DrawLabel;
+ label.height = Widget_HeightLabel;
+ label.width = Widget_WidthFullScreen;
+ label.line_count = SplitString (msg, '\n', 30, lines, bank);
+ label.lines = lines;
+
+ PauseFlash ();
+
+ oldContext = SetContext (ScreenContext);
+ GetContextClipRect (&oldRect);
+ SetContextClipRect (NULL);
+
+ // TODO: Maybe DrawLabelAsWindow() should return a saved STAMP?
+ // We do not know the dimensions here, and so save the whole context
+ s = SaveContextFrame (NULL);
+
+ Widget_SetFont (StarConFont);
+ Widget_SetWindowColors (SHADOWBOX_BACKGROUND_COLOR, SHADOWBOX_DARK_COLOR,
+ SHADOWBOX_MEDIUM_COLOR);
+ DrawLabelAsWindow (&label, &windowRect);
+ SetSystemRect (&windowRect);
+
+ GetMenuSounds (&s0, &s1);
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ oldCallback = SetInputCallback (NULL);
+
+ state.InputFunc = DoPopup;
+ DoInput (&state, TRUE);
+
+ SetInputCallback (oldCallback);
+ ClearSystemRect ();
+ DrawStamp (&s);
+ DestroyDrawable (ReleaseDrawable (s.frame));
+ SetContextClipRect (&oldRect);
+ SetContext (oldContext);
+ ContinueFlash ();
+ SetMenuSounds (s0, s1);
+ StringBank_Free (bank);
+}
+
diff --git a/src/uqm/cons_res.c b/src/uqm/cons_res.c
new file mode 100644
index 0000000..9db9258
--- /dev/null
+++ b/src/uqm/cons_res.c
@@ -0,0 +1,112 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+
+#include "cons_res.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "setup.h"
+#include "units.h"
+ // for NUM_VIEWS
+#include "planets/planets.h"
+ // for NUMBER_OF_PLANET_TYPES, PLANET_SHIELDED
+
+static const char *planet_types[] = {
+ "oolite", "yttric", "quasidegenerate", "lanthanide", "treasure",
+ "urea", "metal", "radioactive", "opalescent", "cyanic",
+ "acid", "alkali", "halide", "green", "copper",
+ "carbide", "ultramarine", "noble", "azure", "chondrite",
+ "purple", "superdense", "pellucid", "dust", "crimson",
+ "cimmerian", "infrared", "selenic", "auric", "fluorescent",
+ "ultraviolet", "plutonic", "rainbow", "shattered", "sapphire",
+ "organic", "xenolithic", "redux", "primordial", "emerald",
+ "chlorine", "magnetic", "water", "telluric", "hydrocarbon",
+ "iodine", "vinylogous", "ruby", "magma", "maroon",
+ "bluegas", "cyangas", "greengas", "greygas", "orangegas",
+ "purplegas", "redgas", "violetgas", "yellowgas"
+};
+
+static const char *planet_sizes[] = {
+ "large", "medium", "small"
+};
+
+FRAME planet[NUM_VIEWS];
+static char buffer[80];
+
+void
+load_gravity_well (BYTE selector)
+{
+ COUNT i;
+
+ if (selector == NUMBER_OF_PLANET_TYPES)
+ {
+ planet[0] = CaptureDrawable (
+ LoadGraphic (SAMATRA_BIG_MASK_PMAP_ANIM)
+ );
+ planet[1] = planet[2] = 0;
+ }
+ else
+ {
+ const char *ptype;
+ if (selector & PLANET_SHIELDED)
+ {
+ ptype = "slaveshield";
+ }
+ else
+ {
+ ptype = planet_types[selector];
+ }
+
+ for (i = 0; i < NUM_VIEWS; ++i)
+ {
+ snprintf (buffer, 79, "planet.%s.%s", ptype, planet_sizes[i]);
+ buffer[79] = '\0';
+ planet[i] = CaptureDrawable (LoadGraphic (buffer));
+ }
+ }
+
+}
+
+void
+free_gravity_well (void)
+{
+ COUNT i;
+
+ for (i = 0; i < NUM_VIEWS; ++i)
+ {
+ DestroyDrawable (ReleaseDrawable (planet[i]));
+ planet[i] = 0;
+ }
+}
+
+FRAME
+load_life_form (BYTE selector)
+{
+ snprintf (buffer, 79, "graphics.life.%d", selector);
+ buffer[79] = '\0'; /* Shouldn't be necessary, but better safe than sorry */
+ return CaptureDrawable (LoadGraphic (buffer));
+}
+
+MUSIC_REF
+load_orbit_theme (BYTE selector)
+{
+ snprintf (buffer, 79, "music.orbit%d", selector + 1);
+ buffer[79] = '\0'; /* Shouldn't be necessary, but better safe than sorry */
+ return LoadMusic (buffer);
+}
diff --git a/src/uqm/cons_res.h b/src/uqm/cons_res.h
new file mode 100644
index 0000000..2325e16
--- /dev/null
+++ b/src/uqm/cons_res.h
@@ -0,0 +1,38 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CONS_RES_H_
+#define CONS_RES_H_
+
+#include "libs/gfxlib.h"
+#include "libs/sndlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void load_gravity_well (BYTE selector);
+void free_gravity_well (void);
+
+FRAME load_life_form (BYTE selector);
+
+MUSIC_REF load_orbit_theme (BYTE selector);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* CONS_RES_H_ */
diff --git a/src/uqm/controls.h b/src/uqm/controls.h
new file mode 100644
index 0000000..8273aed
--- /dev/null
+++ b/src/uqm/controls.h
@@ -0,0 +1,172 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_CONTROLS_H_
+#define UQM_CONTROLS_H_
+
+#include "libs/compiler.h"
+#include "libs/strlib.h"
+#include "libs/timelib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// Enumerated type for controls
+enum {
+ KEY_UP,
+ KEY_DOWN,
+ KEY_LEFT,
+ KEY_RIGHT,
+ KEY_WEAPON,
+ KEY_SPECIAL,
+ KEY_ESCAPE,
+ NUM_KEYS
+};
+enum {
+ KEY_PAUSE,
+ KEY_EXIT,
+ KEY_ABORT,
+ KEY_DEBUG,
+ KEY_FULLSCREEN,
+ KEY_MENU_UP,
+ KEY_MENU_DOWN,
+ KEY_MENU_LEFT,
+ KEY_MENU_RIGHT,
+ KEY_MENU_SELECT,
+ KEY_MENU_CANCEL,
+ KEY_MENU_SPECIAL,
+ KEY_MENU_PAGE_UP,
+ KEY_MENU_PAGE_DOWN,
+ KEY_MENU_HOME,
+ KEY_MENU_END,
+ KEY_MENU_ZOOM_IN,
+ KEY_MENU_ZOOM_OUT,
+ KEY_MENU_DELETE,
+ KEY_MENU_BACKSPACE,
+ KEY_MENU_EDIT_CANCEL,
+ KEY_MENU_SEARCH,
+ KEY_MENU_NEXT,
+ KEY_MENU_ANY, /* abstract char key */
+ NUM_MENU_KEYS
+};
+
+typedef enum {
+ CONTROL_TEMPLATE_KB_1,
+ CONTROL_TEMPLATE_KB_2,
+ CONTROL_TEMPLATE_KB_3,
+ CONTROL_TEMPLATE_JOY_1,
+ CONTROL_TEMPLATE_JOY_2,
+ CONTROL_TEMPLATE_JOY_3,
+ NUM_TEMPLATES
+} CONTROL_TEMPLATE;
+
+typedef struct _controller_input_state {
+ int key[NUM_TEMPLATES][NUM_KEYS];
+ int menu[NUM_MENU_KEYS];
+} CONTROLLER_INPUT_STATE;
+
+typedef UBYTE BATTLE_INPUT_STATE;
+#define BATTLE_LEFT ((BATTLE_INPUT_STATE)(1 << 0))
+#define BATTLE_RIGHT ((BATTLE_INPUT_STATE)(1 << 1))
+#define BATTLE_THRUST ((BATTLE_INPUT_STATE)(1 << 2))
+#define BATTLE_WEAPON ((BATTLE_INPUT_STATE)(1 << 3))
+#define BATTLE_SPECIAL ((BATTLE_INPUT_STATE)(1 << 4))
+#define BATTLE_ESCAPE ((BATTLE_INPUT_STATE)(1 << 5))
+#define BATTLE_DOWN ((BATTLE_INPUT_STATE)(1 << 6))
+
+BATTLE_INPUT_STATE CurrentInputToBattleInput (COUNT player);
+BATTLE_INPUT_STATE PulsedInputToBattleInput (COUNT player);
+
+extern CONTROLLER_INPUT_STATE CurrentInputState;
+extern CONTROLLER_INPUT_STATE PulsedInputState;
+extern volatile CONTROLLER_INPUT_STATE ImmediateInputState;
+extern CONTROL_TEMPLATE PlayerControls[];
+
+void UpdateInputState (void);
+extern void FlushInput (void);
+void SetMenuRepeatDelay (DWORD min, DWORD max, DWORD step, BOOLEAN gestalt);
+void SetDefaultMenuRepeatDelay (void);
+void ResetKeyRepeat (void);
+BOOLEAN PauseGame (void);
+void SleepGame (void);
+BOOLEAN DoConfirmExit (void);
+BOOLEAN ConfirmExit (void);
+
+#define WAIT_INFINITE ((TimePeriod)-1)
+BOOLEAN WaitForAnyButton (BOOLEAN newButton, TimePeriod duration,
+ BOOLEAN resetInput);
+BOOLEAN WaitForAnyButtonUntil (BOOLEAN newButton, TimeCount timeOut,
+ BOOLEAN resetInput);
+BOOLEAN WaitForNoInput (TimePeriod duration, BOOLEAN resetInput);
+BOOLEAN WaitForNoInputUntil (TimeCount timeOut, BOOLEAN resetInput);
+
+void DoPopupWindow(const char *msg);
+
+typedef void (InputFrameCallback) (void);
+InputFrameCallback* SetInputCallback (InputFrameCallback *);
+// pInputState must point to a struct derived from INPUT_STATE_DESC
+void DoInput (void *pInputState, BOOLEAN resetInput);
+
+extern volatile BOOLEAN GamePaused;
+extern volatile BOOLEAN ExitRequested;
+
+typedef struct joy_char joy_char_t;
+
+typedef struct textentry_state
+{
+ // standard state required by DoInput
+ BOOLEAN (*InputFunc) (struct textentry_state *pTES);
+
+ // these are semi-private read-only
+ BOOLEAN Initialized;
+ DWORD NextTime; // use this for input frame timing
+ BOOLEAN Success; // edit confirmed or canceled
+ UNICODE *CacheStr; // cached copy to revert immediate changes
+ STRING JoyAlphaString; // joystick alphabet definition
+ BOOLEAN JoystickMode; // TRUE when doing joystick input
+ BOOLEAN UpperRegister; // TRUE when entering Caps
+ joy_char_t *JoyAlpha; // joystick alphabet
+ int JoyAlphaLength;
+ joy_char_t *JoyUpper; // joystick upper register
+ joy_char_t *JoyLower; // joystick lower register
+ int JoyRegLength;
+ UNICODE *InsPt; // set to current pos of insertion point
+ // these are public and must be set before calling DoTextEntry
+ UNICODE *BaseStr; // set to string to edit
+ int CursorPos; // set to current cursor pos in chars
+ int MaxSize; // set to max size of edited string
+
+ BOOLEAN (*ChangeCallback) (struct textentry_state *pTES);
+ // returns TRUE if last change is OK
+ BOOLEAN (*FrameCallback) (struct textentry_state *pTES);
+ // called on every input frame; do whatever;
+ // returns TRUE to continue processing
+ void *CbParam; // callback parameter, use as you like
+
+} TEXTENTRY_STATE;
+
+extern BOOLEAN DoTextEntry (TEXTENTRY_STATE *pTES);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+
+
diff --git a/src/uqm/corecode.h b/src/uqm/corecode.h
new file mode 100644
index 0000000..11bd449
--- /dev/null
+++ b/src/uqm/corecode.h
@@ -0,0 +1,49 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CORECODE_H_
+#define CORECODE_H_
+
+#include "ships/androsyn/icode.h"
+#include "ships/arilou/icode.h"
+#include "ships/blackurq/icode.h"
+#include "ships/chenjesu/icode.h"
+#include "ships/chmmr/icode.h"
+#include "ships/druuge/icode.h"
+#include "ships/human/icode.h"
+#include "ships/ilwrath/icode.h"
+#include "ships/lastbat/icode.h"
+#include "ships/melnorme/icode.h"
+#include "ships/mmrnmhrm/icode.h"
+#include "ships/mycon/icode.h"
+#include "ships/orz/icode.h"
+#include "ships/pkunk/icode.h"
+#include "ships/probe/icode.h"
+#include "ships/shofixti/icode.h"
+#include "ships/sis_ship/icode.h"
+#include "ships/slylandr/icode.h"
+#include "ships/spathi/icode.h"
+#include "ships/supox/icode.h"
+#include "ships/syreen/icode.h"
+#include "ships/thradd/icode.h"
+#include "ships/umgah/icode.h"
+#include "ships/urquan/icode.h"
+#include "ships/utwig/icode.h"
+#include "ships/vux/icode.h"
+#include "ships/yehat/icode.h"
+#include "ships/zoqfot/icode.h"
+
+#endif /* CORECODE_H_ */
diff --git a/src/uqm/credits.c b/src/uqm/credits.c
new file mode 100644
index 0000000..1889484
--- /dev/null
+++ b/src/uqm/credits.c
@@ -0,0 +1,839 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "credits.h"
+
+#include "controls.h"
+#include "colors.h"
+#include "options.h"
+#include "oscill.h"
+#include "comm.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "settings.h"
+#include "sounds.h"
+#include "setup.h"
+#include "libs/graphics/drawable.h"
+#include <math.h>
+
+// Rates in pixel lines per second
+#define CREDITS_BASE_RATE 9
+#define CREDITS_MAX_RATE 130
+// Maximum frame rate
+#define CREDITS_FRAME_RATE 36
+
+#define CREDITS_TIMEOUT (ONE_SECOND * 5)
+
+#define TRANS_COLOR BRIGHT_BLUE_COLOR
+
+// Positive or negative scroll rate in pixel lines per second
+static int CreditsRate;
+
+static BOOLEAN OutTakesRunning;
+static BOOLEAN CreditsRunning;
+static STRING CreditsTab;
+static FRAME CreditsBack;
+
+// Context used for drawing to the screen
+static CONTEXT DrawContext;
+// Context used for pre-rendering a credits frame
+static CONTEXT LocalContext;
+// Pre-rendered frame, possibly with a cutout
+static FRAME CreditsFrame;
+// Size of the credits "window" (normally screen size)
+static EXTENT CreditsExtent;
+
+typedef struct
+{
+ FRAME frame;
+ int strIndex;
+} CreditTextFrame;
+
+#define MAX_CREDIT_FRAMES 32
+// Circular text frame buffer for scrolling
+// Text frames are generated as needed, and when a text frame scrolls
+// of the screen, it is destroyed
+static CreditTextFrame textFrames[MAX_CREDIT_FRAMES];
+// Index of first active frame in the circular buffer (the first frame
+// is the one on top)
+static int firstFrame;
+// Index of last active frame in the circular buffer + 1
+static int lastFrame;
+// Total height of all active frames in the circular buffer
+static int totalHeight;
+// Current vertical offset into the first text frame
+static int curFrameOfs;
+
+typedef struct
+{
+ int size;
+ RESOURCE res;
+ FONT font;
+} FONT_SIZE_DEF;
+
+static FONT_SIZE_DEF CreditsFont[] =
+{
+ { 13, PT13AA_FONT, 0 },
+ { 17, PT17AA_FONT, 0 },
+ { 45, PT45AA_FONT, 0 },
+ { 0, 0, 0 },
+};
+
+
+static FRAME
+Credits_MakeTransFrame (int w, int h, Color TransColor)
+{
+ FRAME OldFrame;
+ FRAME f;
+
+ f = CaptureDrawable (CreateDrawable (WANT_PIXMAP, w, h, 1));
+ SetFrameTransparentColor (f, TransColor);
+
+ OldFrame = SetContextFGFrame (f);
+ SetContextBackGroundColor (TransColor);
+ ClearDrawable ();
+ SetContextFGFrame (OldFrame);
+
+ return f;
+}
+
+static int
+ParseTextLines (TEXT *Lines, int MaxLines, char *Buffer)
+{
+ int i;
+ const char* pEnd = Buffer + strlen (Buffer);
+
+ for (i = 0; i < MaxLines && Buffer < pEnd; ++i, ++Lines)
+ {
+ char* pTerm = strchr (Buffer, '\n');
+ if (!pTerm)
+ pTerm = Buffer + strlen (Buffer);
+ *pTerm = '\0'; /* terminate string */
+ Lines->pStr = Buffer;
+ Lines->CharCount = ~0;
+ Buffer = pTerm + 1;
+ }
+ return i;
+}
+
+#define MAX_TEXT_LINES 50
+#define MAX_TEXT_COLS 5
+
+static FRAME
+Credits_RenderTextFrame (CONTEXT TempContext, int *istr, int dir,
+ Color BackColor, Color ForeColor)
+{
+ FRAME f;
+ CONTEXT OldContext;
+ FRAME OldFrame;
+ TEXT TextLines[MAX_TEXT_LINES];
+ char *pStr = NULL;
+ int size;
+ char salign[32];
+ char *scol;
+ int scaned;
+ int i, rows, cnt;
+ char buf[2048];
+ FONT_SIZE_DEF *fdef;
+ SIZE leading;
+ TEXT t;
+ RECT r;
+ typedef struct
+ {
+ TEXT_ALIGN align;
+ COORD basex;
+ } col_format_t;
+ col_format_t colfmt[MAX_TEXT_COLS];
+
+ if (*istr < 0 || *istr >= GetStringTableCount (CreditsTab))
+ { // check if next one is within range
+ int next_s = *istr + dir;
+
+ if (next_s < 0 || next_s >= GetStringTableCount (CreditsTab))
+ return 0;
+
+ *istr = next_s;
+ }
+
+ // skip empty lines
+ while (*istr >= 0 && *istr < GetStringTableCount (CreditsTab))
+ {
+ pStr = GetStringAddress (
+ SetAbsStringTableIndex (CreditsTab, *istr));
+ *istr += dir;
+ if (pStr && *pStr != '\0')
+ break;
+ }
+
+ if (!pStr || *pStr == '\0')
+ return 0;
+
+ if (2 != sscanf (pStr, "%d %31s %n", &size, salign, &scaned)
+ || size <= 0)
+ return 0;
+ pStr += scaned;
+
+ utf8StringCopy (buf, sizeof (buf), pStr);
+ rows = ParseTextLines (TextLines, MAX_TEXT_LINES, buf);
+ if (rows == 0)
+ return 0;
+ // parse text columns
+ for (i = 0, cnt = rows; i < rows; ++i)
+ {
+ char *nextcol;
+ int icol;
+
+ // we abuse the baseline here, but only a tiny bit
+ // every line starts at col 0
+ TextLines[i].baseline.x = 0;
+ TextLines[i].baseline.y = i + 1;
+
+ for (icol = 1, nextcol = strchr (TextLines[i].pStr, '\t');
+ icol < MAX_TEXT_COLS && nextcol;
+ ++icol, nextcol = strchr (nextcol, '\t'))
+ {
+ *nextcol = '\0';
+ ++nextcol;
+
+ if (cnt < MAX_TEXT_LINES)
+ {
+ TextLines[cnt].pStr = nextcol;
+ TextLines[cnt].CharCount = ~0;
+ TextLines[cnt].baseline.x = icol;
+ TextLines[cnt].baseline.y = i + 1;
+ ++cnt;
+ }
+ }
+ }
+
+ // init alignments
+ for (i = 0; i < MAX_TEXT_COLS; ++i)
+ {
+ colfmt[i].align = ALIGN_LEFT;
+ colfmt[i].basex = CreditsExtent.width / 64;
+ }
+
+ // find the right font
+ for (fdef = CreditsFont; fdef->size && size > fdef->size; ++fdef)
+ ;
+ if (!fdef->size)
+ return 0;
+
+ t.align = ALIGN_LEFT;
+ t.baseline.x = 100; // any value will do
+ t.baseline.y = 100; // any value will do
+ t.pStr = " ";
+ t.CharCount = 1;
+
+ OldContext = SetContext (TempContext);
+
+ // get font dimensions
+ SetContextFont (fdef->font);
+ GetContextFontLeading (&leading);
+ // get left/right margin
+ TextRect (&t, &r, NULL);
+
+ // parse text column alignment
+ for (i = 0, scol = strtok (salign, ",");
+ scol && i < MAX_TEXT_COLS;
+ ++i, scol = strtok (NULL, ","))
+ {
+ char c;
+ int x;
+ int n;
+
+ // default
+ colfmt[i].align = ALIGN_LEFT;
+ colfmt[i].basex = r.extent.width;
+
+ n = sscanf (scol, "%c/%d", &c, &x);
+ if (n < 1)
+ { // DOES NOT COMPUTE! :)
+ continue;
+ }
+
+ switch (c)
+ {
+ case 'L':
+ colfmt[i].align = ALIGN_LEFT;
+ if (n >= 2)
+ colfmt[i].basex = x;
+ break;
+ case 'C':
+ colfmt[i].align = ALIGN_CENTER;
+ if (n >= 2)
+ colfmt[i].basex = x;
+ else
+ colfmt[i].basex = CreditsExtent.width / 2;
+ break;
+ case 'R':
+ colfmt[i].align = ALIGN_RIGHT;
+ if (n >= 2)
+ colfmt[i].basex = x;
+ else
+ colfmt[i].basex = CreditsExtent.width - r.extent.width;
+ break;
+ }
+ }
+
+ for (i = 0; i < cnt; ++i)
+ {
+ // baseline contains coords in row/col quantities
+ col_format_t *fmt = colfmt + TextLines[i].baseline.x;
+
+ TextLines[i].align = fmt->align;
+ TextLines[i].baseline.x = fmt->basex;
+ TextLines[i].baseline.y *= leading;
+ }
+
+ f = Credits_MakeTransFrame (CreditsExtent.width, leading * rows + (leading >> 1),
+ BackColor);
+ OldFrame = SetContextFGFrame (f);
+ // draw text
+ SetContextForeGroundColor (ForeColor);
+ for (i = 0; i < cnt; ++i)
+ font_DrawText (TextLines + i);
+
+ SetContextFGFrame (OldFrame);
+ SetContext (OldContext);
+
+ return f;
+}
+
+static inline int
+frameIndex (int index)
+{
+ // Make sure index is positive before %
+ return (index + MAX_CREDIT_FRAMES) % MAX_CREDIT_FRAMES;
+}
+
+static void
+RenderCreditsScreen (CONTEXT targetContext)
+{
+ CONTEXT oldContext;
+ STAMP s;
+ int i;
+
+ oldContext = SetContext (targetContext);
+ // draw background
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = CreditsBack;
+ DrawStamp (&s);
+
+ // draw text frames
+ s.origin.y = -curFrameOfs;
+ for (i = firstFrame; i != lastFrame; i = frameIndex (i + 1))
+ {
+ RECT fr;
+
+ s.frame = textFrames[i].frame;
+ DrawStamp (&s);
+ GetFrameRect (s.frame, &fr);
+ s.origin.y += fr.extent.height;
+ }
+
+ if (OutTakesRunning)
+ { // Cut out the Outtakes rect
+ SetContextForeGroundColor (TRANS_COLOR);
+ DrawFilledRectangle (&CommWndRect);
+ }
+
+ SetContext (oldContext);
+}
+
+static void
+InitCredits (void)
+{
+ RECT ctxRect;
+ CONTEXT oldContext;
+ FRAME targetFrame;
+
+ memset (textFrames, 0, sizeof textFrames);
+
+ LocalContext = CreateContext ("Credits.LocalContext");
+ DrawContext = CreateContext ("Credits.DrawContext");
+
+ targetFrame = GetContextFGFrame ();
+ GetContextClipRect (&ctxRect);
+ CreditsExtent = ctxRect.extent;
+
+ // prep our local context
+ oldContext = SetContext (LocalContext);
+ // Local screen copy. We draw everything to this frame, then cut
+ // the Outtakes rect out and draw this frame to the screen.
+ CreditsFrame = Credits_MakeTransFrame (CreditsExtent.width,
+ CreditsExtent.height, TRANS_COLOR);
+ SetContextFGFrame (CreditsFrame);
+
+ // The first credits frame is fake, the height of the screen,
+ // so that the credits can roll in from the bottom
+ textFrames[0].frame = Credits_MakeTransFrame (1, CreditsExtent.height,
+ TRANS_COLOR);
+ textFrames[0].strIndex = -1;
+ firstFrame = 0;
+ lastFrame = firstFrame + 1;
+
+ totalHeight = GetFrameHeight (textFrames[0].frame);
+ curFrameOfs = 0;
+
+ // We use an own screen draw context to avoid collisions
+ SetContext (DrawContext);
+ SetContextFGFrame (targetFrame);
+
+ SetContext (oldContext);
+
+ // Prepare the first screen frame
+ RenderCreditsScreen (LocalContext);
+
+ CreditsRate = CREDITS_BASE_RATE;
+ CreditsRunning = TRUE;
+}
+
+static void
+freeCreditTextFrame (CreditTextFrame *tf)
+{
+ DestroyDrawable (ReleaseDrawable (tf->frame));
+ tf->frame = NULL;
+}
+
+static void
+UninitCredits (void)
+{
+ DestroyContext (DrawContext);
+ DrawContext = NULL;
+ DestroyContext (LocalContext);
+ LocalContext = NULL;
+
+ // free remaining frames
+ DestroyDrawable (ReleaseDrawable (CreditsFrame));
+ CreditsFrame = NULL;
+ for ( ; firstFrame != lastFrame; firstFrame = frameIndex (firstFrame + 1))
+ freeCreditTextFrame (&textFrames[firstFrame]);
+}
+
+static int
+calcDeficitHeight (void)
+{
+ int i;
+ int maxPos;
+
+ maxPos = -curFrameOfs;
+ for (i = firstFrame; i != lastFrame; i = frameIndex (i + 1))
+ {
+ RECT fr;
+
+ GetFrameRect (textFrames[i].frame, &fr);
+ maxPos += fr.extent.height;
+ }
+
+ return CreditsExtent.height - maxPos;
+}
+
+static void
+processCreditsFrame (void)
+{
+ static TimeCount NextTime;
+ TimeCount Now = GetTimeCounter ();
+
+ if (Now >= NextTime)
+ {
+ RECT fr;
+ CONTEXT OldContext;
+ int rate, direction, dirstep;
+ int deficitHeight;
+ STAMP s;
+
+ rate = abs (CreditsRate);
+ if (rate != 0)
+ {
+ // scroll direction; forward or backward
+ direction = CreditsRate / rate;
+ // step in pixels
+ dirstep = (rate + CREDITS_FRAME_RATE - 1) / CREDITS_FRAME_RATE;
+ rate = ONE_SECOND * dirstep / rate;
+ // step is also directional
+ dirstep *= direction;
+ }
+ else
+ { // scroll stopped
+ direction = 0;
+ dirstep = 0;
+ // one second interframe
+ rate = ONE_SECOND;
+ }
+
+ NextTime = GetTimeCounter () + rate;
+
+ // draw the credits
+ // comm animations play with contexts so we need to make
+ // sure the context is not desynced
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = CreditsFrame;
+
+ OldContext = SetContext (DrawContext);
+ DrawStamp (&s);
+ SetContext (OldContext);
+ FlushGraphics ();
+
+ // prepare next screen frame
+ deficitHeight = calcDeficitHeight ();
+ curFrameOfs += dirstep;
+ // cap scroll
+ if (curFrameOfs < -(CreditsExtent.height / 20))
+ { // at the begining, deceleration
+ if (CreditsRate < 0)
+ CreditsRate -= CreditsRate / 10 - 1;
+ }
+ else if (deficitHeight > CreditsExtent.height / 25)
+ { // frame deficit -- credits almost over, deceleration
+ if (CreditsRate > 0)
+ CreditsRate -= CreditsRate / 10 + 1;
+
+ CreditsRunning = (CreditsRate != 0);
+ }
+ else if (!CreditsRunning)
+ { // resumed
+ CreditsRunning = TRUE;
+ }
+
+ if (firstFrame != lastFrame)
+ { // clean up frames that scrolled off the screen
+ if (direction > 0)
+ { // forward scroll
+ GetFrameRect (textFrames[firstFrame].frame, &fr);
+ if (curFrameOfs >= fr.extent.height)
+ { // past this frame already
+ totalHeight -= fr.extent.height;
+ freeCreditTextFrame (&textFrames[firstFrame]);
+ // next frame
+ firstFrame = frameIndex (firstFrame + 1);
+ curFrameOfs -= fr.extent.height;
+ }
+ }
+ else if (direction < 0)
+ { // backward scroll
+ int index = frameIndex (lastFrame - 1);
+ int framePos;
+
+ GetFrameRect (textFrames[index].frame, &fr);
+ framePos = totalHeight - curFrameOfs - fr.extent.height;
+ if (framePos >= CreditsExtent.height)
+ { // past this frame already
+ lastFrame = index;
+ totalHeight -= fr.extent.height;
+ freeCreditTextFrame (&textFrames[lastFrame]);
+ }
+ }
+ }
+
+ // render new text frames if needed
+ if (direction > 0)
+ { // forward scroll
+ int next_s = 0;
+
+ // get next string
+ if (firstFrame != lastFrame)
+ next_s = textFrames[frameIndex (lastFrame - 1)].strIndex + 1;
+
+ while (totalHeight - curFrameOfs < CreditsExtent.height
+ && next_s < GetStringTableCount (CreditsTab))
+ {
+ CreditTextFrame *tf = &textFrames[lastFrame];
+
+ tf->frame = Credits_RenderTextFrame (LocalContext, &next_s,
+ direction, BLACK_COLOR, CREDITS_TEXT_COLOR);
+ tf->strIndex = next_s - 1;
+ if (tf->frame)
+ {
+ GetFrameRect (tf->frame, &fr);
+ totalHeight += fr.extent.height;
+
+ lastFrame = frameIndex (lastFrame + 1);
+ }
+ }
+ }
+ else if (direction < 0)
+ { // backward scroll
+ int next_s = GetStringTableCount (CreditsTab) - 1;
+
+ // get next string
+ if (firstFrame != lastFrame)
+ next_s = textFrames[firstFrame].strIndex - 1;
+
+ while (curFrameOfs < 0 && next_s >= 0)
+ {
+ int index = frameIndex (firstFrame - 1);
+ CreditTextFrame *tf = &textFrames[index];
+
+ tf->frame = Credits_RenderTextFrame (LocalContext, &next_s,
+ direction, BLACK_COLOR, CREDITS_TEXT_COLOR);
+ tf->strIndex = next_s + 1;
+ if (tf->frame)
+ {
+ GetFrameRect (tf->frame, &fr);
+ totalHeight += fr.extent.height;
+
+ firstFrame = index;
+ curFrameOfs += fr.extent.height;
+ }
+ }
+ }
+
+ // draw next screen frame
+ RenderCreditsScreen (LocalContext);
+ }
+}
+
+static BOOLEAN
+LoadCredits (void)
+{
+ FONT_SIZE_DEF *fdef;
+
+ CreditsTab = CaptureStringTable (LoadStringTable (CREDITS_STRTAB));
+ if (!CreditsTab)
+ return FALSE;
+ CreditsBack = CaptureDrawable (LoadGraphic (CREDITS_BACK_ANIM));
+ // load fonts
+ for (fdef = CreditsFont; fdef->size; ++fdef)
+ fdef->font = LoadFont (fdef->res);
+
+ return TRUE;
+}
+
+static void
+FreeCredits (void)
+{
+ FONT_SIZE_DEF *fdef;
+
+ DestroyStringTable (ReleaseStringTable (CreditsTab));
+ CreditsTab = NULL;
+
+ DestroyDrawable (ReleaseDrawable (CreditsBack));
+ CreditsBack = NULL;
+
+ // free fonts
+ for (fdef = CreditsFont; fdef->size; ++fdef)
+ {
+ DestroyFont (fdef->font);
+ fdef->font = NULL;
+ }
+}
+
+static void
+OutTakes (void)
+{
+#define NUM_OUTTAKES 15
+ static CONVERSATION outtake_list[NUM_OUTTAKES] =
+ {
+ ZOQFOTPIK_CONVERSATION,
+ TALKING_PET_CONVERSATION,
+ ORZ_CONVERSATION,
+ UTWIG_CONVERSATION,
+ THRADD_CONVERSATION,
+ SUPOX_CONVERSATION,
+ SYREEN_CONVERSATION,
+ SHOFIXTI_CONVERSATION,
+ PKUNK_CONVERSATION,
+ YEHAT_CONVERSATION,
+ DRUUGE_CONVERSATION,
+ URQUAN_CONVERSATION,
+ VUX_CONVERSATION,
+ BLACKURQ_CONVERSATION,
+ ARILOU_CONVERSATION
+ };
+
+ BOOLEAN oldsubtitles = optSubtitles;
+ int i = 0;
+
+ // Outtakes have no voice tracks, so the subtitles are always on
+ optSubtitles = TRUE;
+ sliderDisabled = TRUE;
+ oscillDisabled = TRUE;
+
+ for (i = 0; (i < NUM_OUTTAKES) &&
+ !(GLOBAL (CurrentActivity) & CHECK_ABORT); i++)
+ {
+ SetCommIntroMode (CIM_CROSSFADE_WINDOW, 0);
+ InitCommunication (outtake_list[i]);
+ }
+
+ optSubtitles = oldsubtitles;
+ sliderDisabled = FALSE;
+ oscillDisabled = FALSE;
+}
+
+typedef struct
+{
+ // standard state required by DoInput
+ BOOLEAN (*InputFunc) (void *pInputState);
+
+ BOOLEAN AllowCancel;
+ BOOLEAN AllowSpeedChange;
+ BOOLEAN CloseWhenDone;
+ DWORD CloseTimeOut;
+
+} CREDITS_INPUT_STATE;
+
+static BOOLEAN
+DoCreditsInput (void *pIS)
+{
+ CREDITS_INPUT_STATE *pCIS = (CREDITS_INPUT_STATE *) pIS;
+
+ if (CreditsRunning)
+ { // cancel timeout if resumed (or just running)
+ pCIS->CloseTimeOut = 0;
+ }
+
+ if ((GLOBAL (CurrentActivity) & CHECK_ABORT)
+ || (pCIS->AllowCancel &&
+ (PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL]))
+ )
+ { // aborted
+ return FALSE;
+ }
+
+ if (pCIS->AllowSpeedChange
+ && (PulsedInputState.menu[KEY_MENU_UP]
+ || PulsedInputState.menu[KEY_MENU_DOWN]))
+ { // speed adjustment
+ int newrate = CreditsRate;
+ int step = abs (CreditsRate) / 5 + 1;
+
+ if (PulsedInputState.menu[KEY_MENU_DOWN])
+ newrate += step;
+ else if (PulsedInputState.menu[KEY_MENU_UP])
+ newrate -= step;
+ if (newrate < -CREDITS_MAX_RATE)
+ newrate = -CREDITS_MAX_RATE;
+ else if (newrate > CREDITS_MAX_RATE)
+ newrate = CREDITS_MAX_RATE;
+
+ CreditsRate = newrate;
+ }
+
+ if (!CreditsRunning)
+ { // always allow cancelling once credits run through
+ pCIS->AllowCancel = TRUE;
+ }
+
+ if (!CreditsRunning && pCIS->CloseWhenDone)
+ { // auto-close controlled by timeout
+ if (pCIS->CloseTimeOut == 0)
+ { // set timeout
+ pCIS->CloseTimeOut = GetTimeCounter () + CREDITS_TIMEOUT;
+ }
+ else if (GetTimeCounter () > pCIS->CloseTimeOut)
+ { // all done!
+ return FALSE;
+ }
+ }
+
+ if (!CreditsRunning
+ && (PulsedInputState.menu[KEY_MENU_SELECT]
+ || PulsedInputState.menu[KEY_MENU_CANCEL]))
+ { // credits finished and exit requested
+ return FALSE;
+ }
+
+ SleepThread (ONE_SECOND / CREDITS_FRAME_RATE);
+
+ return TRUE;
+}
+
+static void
+on_input_frame (void)
+{
+ processCreditsFrame ();
+}
+
+void
+Credits (BOOLEAN WithOuttakes)
+{
+ MUSIC_REF hMusic;
+ CREDITS_INPUT_STATE cis;
+ RECT screenRect;
+ STAMP s;
+
+ hMusic = LoadMusic (CREDITS_MUSIC);
+
+ SetContext (ScreenContext);
+ SetContextClipRect (NULL);
+ GetContextClipRect (&screenRect);
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+
+ if (!LoadCredits ())
+ return;
+
+ // Fade in the background
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = CreditsBack;
+ DrawStamp (&s);
+ FadeScreen (FadeAllToColor, ONE_SECOND / 2);
+
+ // set the position of outtakes comm
+ CommWndRect.corner.x = (screenRect.extent.width - CommWndRect.extent.width)
+ / 2;
+ CommWndRect.corner.y = 5;
+
+ InitCredits ();
+ SetInputCallback (on_input_frame);
+
+ if (WithOuttakes)
+ {
+ OutTakesRunning = TRUE;
+ OutTakes ();
+ OutTakesRunning = FALSE;
+ }
+
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ if (hMusic)
+ PlayMusic (hMusic, TRUE, 1);
+
+ // nothing to do now but wait until credits
+ // are done or canceled by user
+ cis.InputFunc = DoCreditsInput;
+ cis.AllowCancel = !WithOuttakes;
+ cis.CloseWhenDone = !WithOuttakes;
+ cis.AllowSpeedChange = TRUE;
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ DoInput (&cis, TRUE);
+ }
+
+ SetInputCallback (NULL);
+ FadeMusic (0, ONE_SECOND / 2);
+ UninitCredits ();
+
+ SetContext (ScreenContext);
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2));
+ FlushColorXForms ();
+
+ if (hMusic)
+ {
+ StopMusic ();
+ DestroyMusic (hMusic);
+ }
+ FadeMusic (NORMAL_VOLUME, 0);
+
+ FreeCredits ();
+}
diff --git a/src/uqm/credits.h b/src/uqm/credits.h
new file mode 100644
index 0000000..c007b35
--- /dev/null
+++ b/src/uqm/credits.h
@@ -0,0 +1,32 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_CREDITS_H_
+#define UQM_CREDITS_H_
+
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern void Credits (BOOLEAN WithOuttakes);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_CREDITS_H_ */
diff --git a/src/uqm/cyborg.c b/src/uqm/cyborg.c
new file mode 100644
index 0000000..a50f3b8
--- /dev/null
+++ b/src/uqm/cyborg.c
@@ -0,0 +1,1339 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "colors.h"
+#include "collide.h"
+#include "element.h"
+#include "ship.h"
+#include "globdata.h"
+#include "intel.h"
+#include "setup.h"
+#include "units.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+
+//#define DEBUG_CYBORG
+
+COUNT
+PlotIntercept (ELEMENT *ElementPtr0, ELEMENT *ElementPtr1,
+ COUNT max_turns, COUNT margin_of_error)
+{
+ SIZE dy;
+ SIZE time_y_0, time_y_1;
+ POINT dst[2];
+ RECT r0 = {{0, 0}, {0, 0}};
+ RECT r1 = {{0, 0}, {0, 0}};
+ SIZE dx_0, dy_0, dx_1, dy_1;
+
+ if ((ElementPtr0->state_flags | ElementPtr1->state_flags) & FINITE_LIFE)
+ {
+ if (!(ElementPtr0->state_flags & FINITE_LIFE))
+ {
+ if (ElementPtr1->life_span < max_turns)
+ max_turns = ElementPtr1->life_span;
+ }
+ else if (!(ElementPtr1->state_flags & FINITE_LIFE))
+ {
+ if (ElementPtr0->life_span < max_turns)
+ max_turns = ElementPtr0->life_span;
+ }
+ else
+ {
+ if (ElementPtr0->life_span < max_turns)
+ max_turns = ElementPtr0->life_span;
+ if (ElementPtr1->life_span < max_turns)
+ max_turns = ElementPtr1->life_span;
+ }
+ }
+
+ dst[0] = ElementPtr0->current.location;
+ GetCurrentVelocityComponents (&ElementPtr0->velocity, &dx_0, &dy_0);
+ dx_0 = (SIZE)VELOCITY_TO_WORLD ((long)dx_0 * (long)max_turns);
+ dy_0 = (SIZE)VELOCITY_TO_WORLD ((long)dy_0 * (long)max_turns);
+
+ dst[1] = ElementPtr1->current.location;
+ GetCurrentVelocityComponents (&ElementPtr1->velocity, &dx_1, &dy_1);
+ dx_1 = (SIZE)VELOCITY_TO_WORLD ((long)dx_1 * (long)max_turns);
+ dy_1 = (SIZE)VELOCITY_TO_WORLD ((long)dy_1 * (long)max_turns);
+
+ if (margin_of_error)
+ {
+ dst[1].y -= margin_of_error;
+ time_y_0 = 1;
+ time_y_1 = margin_of_error << 1;
+ }
+ else
+ {
+ GetFrameRect (ElementPtr0->IntersectControl.IntersectStamp.frame, &r0);
+ GetFrameRect (ElementPtr1->IntersectControl.IntersectStamp.frame, &r1);
+
+ dst[0].y += DISPLAY_TO_WORLD (r0.corner.y);
+ dst[1].y += DISPLAY_TO_WORLD (r1.corner.y);
+ time_y_0 = DISPLAY_TO_WORLD (r0.extent.height);
+ time_y_1 = DISPLAY_TO_WORLD (r1.extent.height);
+ }
+
+ dy = dst[1].y - dst[0].y;
+ time_y_0 = dy - time_y_0 + 1;
+ time_y_1 = dy + time_y_1 - 1;
+ dy = dy_0 - dy_1;
+
+ if ((time_y_0 <= 0 && time_y_1 >= 0)
+ || (time_y_0 > 0 && dy >= time_y_0)
+ || (time_y_1 < 0 && dy <= time_y_1))
+ {
+ SIZE dx;
+ SIZE time_x_0, time_x_1;
+
+ if (margin_of_error)
+ {
+ dst[1].x -= margin_of_error;
+ time_x_0 = 1;
+ time_x_1 = margin_of_error << 1;
+ }
+ else
+ {
+ dst[0].x += DISPLAY_TO_WORLD (r0.corner.x);
+ dst[1].x += DISPLAY_TO_WORLD (r1.corner.x);
+ time_x_0 = DISPLAY_TO_WORLD (r0.extent.width);
+ time_x_1 = DISPLAY_TO_WORLD (r1.extent.width);
+ }
+
+ dx = dst[1].x - dst[0].x;
+ time_x_0 = dx - time_x_0 + 1;
+ time_x_1 = dx + time_x_1 - 1;
+ dx = dx_0 - dx_1;
+
+ if ((time_x_0 <= 0 && time_x_1 >= 0)
+ || (time_x_0 > 0 && dx >= time_x_0)
+ || (time_x_1 < 0 && dx <= time_x_1))
+ {
+ if (dx == 0 && dy == 0)
+ time_y_0 = time_y_1 = 0;
+ else
+ {
+ SIZE t;
+ long time_beg, time_end, fract;
+
+ if (time_y_1 < 0)
+ {
+ t = time_y_0;
+ time_y_0 = -time_y_1;
+ time_y_1 = -t;
+ }
+ else if (time_y_0 <= 0)
+ {
+ if (dy < 0)
+ time_y_1 = -time_y_0;
+ time_y_0 = 0;
+ }
+ if (dy < 0)
+ dy = -dy;
+ if (dy < time_y_1)
+ time_y_1 = dy;
+
+ if (time_x_1 < 0)
+ {
+ t = time_x_0;
+ time_x_0 = -time_x_1;
+ time_x_1 = -t;
+ }
+ else if (time_x_0 <= 0)
+ {
+ if (dx < 0)
+ time_x_1 = -time_x_0;
+ time_x_0 = 0;
+ }
+ if (dx < 0)
+ dx = -dx;
+ if (dx < time_x_1)
+ time_x_1 = dx;
+
+ if (dx == 0)
+ {
+ time_beg = time_y_0;
+ time_end = time_y_1;
+ fract = dy;
+ }
+ else if (dy == 0)
+ {
+ time_beg = time_x_0;
+ time_end = time_x_1;
+ fract = dx;
+ }
+ else
+ {
+ long time_x, time_y;
+
+ time_x = (long)time_x_0 * (long)dy;
+ time_y = (long)time_y_0 * (long)dx;
+ time_beg = time_x < time_y ? time_y : time_x;
+
+ time_x = (long)time_x_1 * (long)dy;
+ time_y = (long)time_y_1 * (long)dx;
+ time_end = time_x > time_y ? time_y : time_x;
+
+ fract = (long)dx * (long)dy;
+ }
+
+ if ((time_beg *= max_turns) < fract)
+ time_y_0 = 0;
+ else
+ time_y_0 = (SIZE)(time_beg / fract);
+
+ if (time_end >= fract) /* just in case of overflow */
+ time_y_1 = max_turns - 1;
+ else
+ time_y_1 = (SIZE)((time_end * max_turns) / fract);
+ }
+
+ if (time_y_0 <= time_y_1)
+ {
+ if (margin_of_error != 0)
+ return ((COUNT)time_y_0 + 1);
+ else
+ {
+ POINT Pt0, Pt1;
+ VELOCITY_DESC Velocity0, Velocity1;
+ INTERSECT_CONTROL Control0, Control1;
+
+ Pt0 = ElementPtr0->current.location;
+ Velocity0 = ElementPtr0->velocity;
+ Control0 = ElementPtr0->IntersectControl;
+
+ Pt1 = ElementPtr1->current.location;
+ Velocity1 = ElementPtr1->velocity;
+ Control1 = ElementPtr1->IntersectControl;
+
+ if (time_y_0)
+ {
+ GetNextVelocityComponents (&Velocity0, &dx_0, &dy_0, time_y_0);
+ Pt0.x += dx_0;
+ Pt0.y += dy_0;
+ Control0.EndPoint.x = WORLD_TO_DISPLAY (Pt0.x);
+ Control0.EndPoint.y = WORLD_TO_DISPLAY (Pt0.y);
+
+ GetNextVelocityComponents (&Velocity1, &dx_1, &dy_1, time_y_0);
+ Pt1.x += dx_1;
+ Pt1.y += dy_1;
+ Control1.EndPoint.x = WORLD_TO_DISPLAY (Pt1.x);
+ Control1.EndPoint.y = WORLD_TO_DISPLAY (Pt1.y);
+ }
+
+ do
+ {
+ TIME_VALUE when;
+
+ ++time_y_0;
+
+ GetNextVelocityComponents (&Velocity0, &dx_0, &dy_0, 1);
+ Pt0.x += dx_0;
+ Pt0.y += dy_0;
+
+ GetNextVelocityComponents (&Velocity1, &dx_1, &dy_1, 1);
+ Pt1.x += dx_1;
+ Pt1.y += dy_1;
+
+ Control0.IntersectStamp.origin = Control0.EndPoint;
+ Control0.EndPoint.x = WORLD_TO_DISPLAY (Pt0.x);
+ Control0.EndPoint.y = WORLD_TO_DISPLAY (Pt0.y);
+
+ Control1.IntersectStamp.origin = Control1.EndPoint;
+ Control1.EndPoint.x = WORLD_TO_DISPLAY (Pt1.x);
+ Control1.EndPoint.y = WORLD_TO_DISPLAY (Pt1.y);
+ when = DrawablesIntersect (&Control0,
+ &Control1, MAX_TIME_VALUE);
+ if (when)
+ {
+ if (when == 1
+ && time_y_0 == 1
+ && ((ElementPtr0->state_flags
+ | ElementPtr1->state_flags) & APPEARING))
+ {
+ when = 0;
+ Control0.EndPoint.x = WORLD_TO_DISPLAY (Pt0.x);
+ Control0.EndPoint.y = WORLD_TO_DISPLAY (Pt0.y);
+
+ Control1.EndPoint.x = WORLD_TO_DISPLAY (Pt1.x);
+ Control1.EndPoint.y = WORLD_TO_DISPLAY (Pt1.y);
+ }
+
+ if (when)
+ return ((COUNT)time_y_0);
+ }
+ } while (time_y_0 < time_y_1);
+ }
+ }
+ }
+ }
+
+ return (0);
+}
+
+static void
+InitCyborg (STARSHIP *StarShipPtr)
+{
+ COUNT Index, Divisor;
+
+ Index = StarShipPtr->RaceDescPtr->characteristics.max_thrust
+ * StarShipPtr->RaceDescPtr->characteristics.thrust_increment;
+ if ((Divisor = StarShipPtr->RaceDescPtr->characteristics.turn_wait
+ + StarShipPtr->RaceDescPtr->characteristics.thrust_wait) > 0)
+ Index /= Divisor;
+ else
+ Index >>= 1;
+#ifdef PRINT_MI
+ {
+ char *shipName;
+
+ shipName = GetStringAddress (
+ StarShipPtr->RaceDescPtr->ship_data.race_strings);
+ log_add (log_Debug, "MI(%s) -- <%u:%u> = %u", shipName,
+ StarShipPtr->RaceDescPtr->characteristics.max_thrust *
+ StarShipPtr->RaceDescPtr->characteristics.thrust_increment,
+ Divisor, Index);
+ }
+#endif /* PRINT_MI */
+ StarShipPtr->RaceDescPtr->cyborg_control.ManeuverabilityIndex = Index;
+}
+
+static void
+ship_movement (ELEMENT *ShipPtr, EVALUATE_DESC *EvalDescPtr)
+{
+ if (EvalDescPtr->which_turn == 0)
+ EvalDescPtr->which_turn = 1;
+
+ switch (EvalDescPtr->MoveState)
+ {
+ case PURSUE:
+ Pursue (ShipPtr, EvalDescPtr);
+ break;
+ case AVOID:
+#ifdef NOTYET
+ Avoid (ShipPtr, EvalDescPtr);
+ break;
+#endif /* NOTYET */
+ case ENTICE:
+ Entice (ShipPtr, EvalDescPtr);
+ break;
+ case NO_MOVEMENT:
+ break;
+ }
+}
+
+BOOLEAN
+ship_weapons (ELEMENT *ShipPtr, ELEMENT *OtherPtr, COUNT margin_of_error)
+{
+ SIZE delta_x, delta_y;
+ COUNT n, num_weapons;
+ ELEMENT Ship;
+ HELEMENT Weapon[6];
+ STARSHIP *StarShipPtr;
+
+ if (OBJECT_CLOAKED (OtherPtr))
+ margin_of_error += DISPLAY_TO_WORLD (40);
+
+ Ship = *ShipPtr;
+ GetNextVelocityComponents (&Ship.velocity,
+ &delta_x, &delta_y, 1);
+ Ship.next.location.x =
+ Ship.current.location.x + delta_x;
+ Ship.next.location.y =
+ Ship.current.location.y + delta_y;
+ Ship.current.location = Ship.next.location;
+
+ GetElementStarShip (&Ship, &StarShipPtr);
+ num_weapons =
+ (*StarShipPtr->RaceDescPtr->init_weapon_func) (&Ship, Weapon);
+
+ if ((n = num_weapons))
+ {
+ HELEMENT *WeaponPtr, w;
+ //STARSHIP *StarShipPtr;
+ ELEMENT *EPtr;
+
+ WeaponPtr = &Weapon[0];
+ do
+ {
+ w = *WeaponPtr;
+ if (w)
+ {
+ LockElement (w, &EPtr);
+ if (EPtr->state_flags & APPEARING)
+ {
+ EPtr->next = EPtr->current;
+ InitIntersectStartPoint (EPtr);
+ InitIntersectEndPoint (EPtr);
+ InitIntersectFrame (EPtr);
+ }
+
+ if (PlotIntercept (EPtr, OtherPtr,
+ EPtr->life_span, margin_of_error))
+ {
+ UnlockElement (w);
+ break;
+ }
+
+ UnlockElement (w);
+ FreeElement (w);
+ }
+ ++WeaponPtr;
+ } while (--n);
+
+ if ((num_weapons = n))
+ {
+ do
+ {
+ w = *WeaponPtr++;
+ if (w)
+ FreeElement (w);
+ } while (--n);
+ }
+ }
+
+ return (num_weapons > 0);
+}
+
+void
+ship_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ BOOLEAN ShipMoved, ShipFired;
+ COUNT margin_of_error;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ ShipMoved = TRUE;
+ if (ShipPtr->turn_wait == 0)
+ ShipMoved = FALSE;
+ if (ShipPtr->thrust_wait == 0)
+ ShipMoved = FALSE;
+
+ ShipFired = TRUE;
+ if (StarShipPtr->weapon_counter == 0)
+ {
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ if (!(StarShipPtr->RaceDescPtr->ship_info.ship_flags & SEEKING_WEAPON))
+ ShipFired = FALSE;
+ }
+
+ if (StarShipPtr->control & AWESOME_RATING)
+ margin_of_error = 0;
+ else if (StarShipPtr->control & GOOD_RATING)
+ margin_of_error = DISPLAY_TO_WORLD (20);
+ else /* if (StarShipPtr->control & STANDARD_RATING) */
+ margin_of_error = DISPLAY_TO_WORLD (40);
+
+ ObjectsOfConcern += ConcernCounter;
+ while (ConcernCounter--)
+ {
+ --ObjectsOfConcern;
+ if (ObjectsOfConcern->ObjectPtr)
+ {
+ if (!ShipMoved
+ && (ConcernCounter != ENEMY_WEAPON_INDEX
+ || ObjectsOfConcern->MoveState == PURSUE
+ || (ObjectsOfConcern->ObjectPtr->state_flags & CREW_OBJECT)
+ || MANEUVERABILITY (
+ &StarShipPtr->RaceDescPtr->cyborg_control
+ ) >= MEDIUM_SHIP))
+ {
+ ship_movement (ShipPtr, ObjectsOfConcern);
+ ShipMoved = TRUE;
+ }
+ if (!ShipFired
+ && (ConcernCounter == ENEMY_SHIP_INDEX
+ || (ConcernCounter == ENEMY_WEAPON_INDEX
+ && ObjectsOfConcern->MoveState != AVOID
+#ifdef NEVER
+ && !(StarShipPtr->control & STANDARD_RATING)
+#endif /* NEVER */
+ )))
+ {
+ ShipFired = ship_weapons (ShipPtr,
+ ObjectsOfConcern->ObjectPtr, margin_of_error);
+ if (ShipFired)
+ StarShipPtr->ship_input_state |= WEAPON;
+ }
+ }
+ }
+}
+
+BOOLEAN
+TurnShip (ELEMENT *ShipPtr, COUNT angle)
+{
+ COUNT f, ship_delta_facing;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ f = StarShipPtr->ShipFacing;
+ ship_delta_facing = NORMALIZE_FACING (ANGLE_TO_FACING (angle) - f);
+ if (ship_delta_facing)
+ {
+ if (ship_delta_facing == ANGLE_TO_FACING (HALF_CIRCLE))
+ ship_delta_facing =
+ NORMALIZE_FACING (ship_delta_facing +
+ (TFB_Random () & 1 ?
+ ANGLE_TO_FACING (OCTANT >> 1) :
+ -ANGLE_TO_FACING (OCTANT >> 1)));
+
+ if (ship_delta_facing < ANGLE_TO_FACING (HALF_CIRCLE))
+ {
+ StarShipPtr->ship_input_state |= RIGHT;
+ ++f;
+ ShipPtr->next.image.frame =
+ IncFrameIndex (ShipPtr->current.image.frame);
+ }
+ else
+ {
+ StarShipPtr->ship_input_state |= LEFT;
+ --f;
+ ShipPtr->next.image.frame =
+ DecFrameIndex (ShipPtr->current.image.frame);
+ }
+
+#ifdef NOTYET
+ if (((StarShipPtr->ship_input_state & (LEFT | RIGHT))
+ ^ (StarShipPtr->cur_status_flags & (LEFT | RIGHT))) == (LEFT | RIGHT))
+ StarShipPtr->ship_input_state &= ~(LEFT | RIGHT);
+ else
+#endif /* NOTYET */
+ {
+ StarShipPtr->ShipFacing = NORMALIZE_FACING (f);
+
+ return (TRUE);
+ }
+ }
+
+ return (FALSE);
+}
+
+BOOLEAN
+ThrustShip (ELEMENT *ShipPtr, COUNT angle)
+{
+ BOOLEAN ShouldThrust;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->ship_input_state & THRUST)
+ ShouldThrust = TRUE;
+ else if (NORMALIZE_FACING (ANGLE_TO_FACING (angle)
+ - ANGLE_TO_FACING (GetVelocityTravelAngle (&ShipPtr->velocity))) == 0
+ && (StarShipPtr->cur_status_flags
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED))
+ && !(StarShipPtr->cur_status_flags & SHIP_IN_GRAVITY_WELL))
+ ShouldThrust = FALSE;
+ else
+ {
+ SIZE ship_delta_facing;
+
+ ship_delta_facing =
+ NORMALIZE_FACING (ANGLE_TO_FACING (angle)
+ - StarShipPtr->ShipFacing + ANGLE_TO_FACING (QUADRANT));
+ if (ship_delta_facing == ANGLE_TO_FACING (QUADRANT)
+ || ((StarShipPtr->cur_status_flags & SHIP_BEYOND_MAX_SPEED)
+ && ship_delta_facing <= ANGLE_TO_FACING (HALF_CIRCLE)))
+ ShouldThrust = TRUE;
+ else
+ ShouldThrust = FALSE;
+ }
+
+ if (ShouldThrust)
+ {
+ inertial_thrust (ShipPtr);
+
+ StarShipPtr->ship_input_state |= THRUST;
+ }
+
+ return (ShouldThrust);
+}
+
+void
+Pursue (ELEMENT *ShipPtr, EVALUATE_DESC *EvalDescPtr)
+{
+ BYTE maneuver_state;
+ COUNT desired_thrust_angle, desired_turn_angle;
+ SIZE delta_x, delta_y;
+ SIZE ship_delta_x, ship_delta_y;
+ SIZE other_delta_x, other_delta_y;
+ ELEMENT *OtherObjPtr;
+ VELOCITY_DESC ShipVelocity, OtherVelocity;
+
+ ShipVelocity = ShipPtr->velocity;
+ GetNextVelocityComponents (&ShipVelocity,
+ &ship_delta_x, &ship_delta_y, EvalDescPtr->which_turn);
+ ShipPtr->next.location.x =
+ ShipPtr->current.location.x + ship_delta_x;
+ ShipPtr->next.location.y =
+ ShipPtr->current.location.y + ship_delta_y;
+
+ OtherObjPtr = EvalDescPtr->ObjectPtr;
+ OtherVelocity = OtherObjPtr->velocity;
+ GetNextVelocityComponents (&OtherVelocity,
+ &other_delta_x, &other_delta_y, EvalDescPtr->which_turn);
+
+ delta_x = (OtherObjPtr->current.location.x + other_delta_x)
+ - ShipPtr->next.location.x;
+ delta_y = (OtherObjPtr->current.location.y + other_delta_y)
+ - ShipPtr->next.location.y;
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = WRAP_DELTA_Y (delta_y);
+ desired_thrust_angle = ARCTAN (delta_x, delta_y);
+
+ maneuver_state = 0;
+ if (ShipPtr->turn_wait == 0)
+ maneuver_state |= LEFT | RIGHT;
+ if (ShipPtr->thrust_wait == 0
+ && ((OtherObjPtr->state_flags & PLAYER_SHIP)
+ || elementsOfSamePlayer (OtherObjPtr, ShipPtr)
+ || OtherObjPtr->preprocess_func == crew_preprocess))
+ maneuver_state |= THRUST;
+
+ desired_turn_angle = NORMALIZE_ANGLE (desired_thrust_angle + HALF_CIRCLE);
+ /* other player's ship */
+ if ((OtherObjPtr->state_flags & PLAYER_SHIP)
+ && OtherObjPtr->mass_points <= MAX_SHIP_MASS)
+ {
+ STARSHIP *StarShipPtr;
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ GetElementStarShip (OtherObjPtr, &EnemyStarShipPtr);
+ if ((MANEUVERABILITY (
+ &StarShipPtr->RaceDescPtr->cyborg_control
+ ) >= FAST_SHIP
+ && WEAPON_RANGE (&StarShipPtr->RaceDescPtr->cyborg_control)
+ > CLOSE_RANGE_WEAPON)
+ || (EvalDescPtr->which_turn >= 24
+ && (StarShipPtr->RaceDescPtr->characteristics.max_thrust * 2 / 3 <
+ EnemyStarShipPtr->RaceDescPtr->characteristics.max_thrust
+ || (EnemyStarShipPtr->cur_status_flags & SHIP_BEYOND_MAX_SPEED))))
+ {
+ UWORD ship_flags;
+
+ ship_flags = EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags;
+ /* you're maneuverable */
+ if (MANEUVERABILITY (
+ &StarShipPtr->RaceDescPtr->cyborg_control
+ ) >= MEDIUM_SHIP)
+ {
+ UWORD fire_flags;
+ COUNT facing;
+
+ for (fire_flags = FIRES_FORE, facing = EvalDescPtr->facing;
+ fire_flags <= FIRES_LEFT;
+ fire_flags <<= 1, facing += QUADRANT)
+ {
+ if
+ (
+ /* he's dangerous in this direction */
+ (ship_flags & fire_flags)
+ /* he's facing direction you want to go */
+ && NORMALIZE_ANGLE (
+ desired_turn_angle - facing + OCTANT
+ ) <= QUADRANT
+ && (
+ /* he's moving */
+ (other_delta_x != 0 || other_delta_y != 0)
+ &&
+ /* he's coasting backwards */
+ NORMALIZE_ANGLE (
+ (GetVelocityTravelAngle (&OtherVelocity) + HALF_CIRCLE)
+ - facing + (OCTANT + (OCTANT >> 1)))
+ <= ((OCTANT + (OCTANT >> 1)) << 1))
+ )
+ {
+ /* catch him on the back side */
+ desired_thrust_angle = desired_turn_angle;
+ break;
+ }
+ }
+ }
+
+ if (desired_thrust_angle != desired_turn_angle
+ && (other_delta_x || other_delta_y)
+ && EvalDescPtr->which_turn >= 24
+ && NORMALIZE_ANGLE (desired_thrust_angle
+ - GetVelocityTravelAngle (&OtherVelocity)
+ + OCTANT) <= QUADRANT
+ && ((NORMALIZE_ANGLE (
+ GetVelocityTravelAngle (&OtherVelocity)
+ - GetVelocityTravelAngle (&ShipVelocity)
+ + OCTANT) <= QUADRANT
+ && (((StarShipPtr->cur_status_flags & SHIP_AT_MAX_SPEED)
+ && !(StarShipPtr->cur_status_flags & SHIP_BEYOND_MAX_SPEED))
+ || (ship_flags & DONT_CHASE)))
+ || NORMALIZE_ANGLE (
+ desired_turn_angle
+ - FACING_TO_ANGLE (StarShipPtr->ShipFacing)
+ + OCTANT) <= QUADRANT))
+ desired_thrust_angle = desired_turn_angle;
+ }
+ }
+
+ if (maneuver_state & (LEFT | RIGHT))
+ TurnShip (ShipPtr, desired_thrust_angle);
+
+ if (maneuver_state & THRUST)
+ ThrustShip (ShipPtr, desired_thrust_angle);
+}
+
+void
+Entice (ELEMENT *ShipPtr, EVALUATE_DESC *EvalDescPtr)
+{
+ BYTE maneuver_state;
+ COUNT desired_thrust_angle, desired_turn_angle;
+ COUNT cone_of_fire, travel_angle;
+ SIZE delta_x, delta_y;
+ SIZE ship_delta_x, ship_delta_y;
+ SIZE other_delta_x, other_delta_y;
+ ELEMENT *OtherObjPtr;
+ VELOCITY_DESC ShipVelocity, OtherVelocity;
+ STARSHIP *StarShipPtr;
+ RACE_DESC *RDPtr;
+
+ ShipVelocity = ShipPtr->velocity;
+ GetNextVelocityComponents (&ShipVelocity,
+ &ship_delta_x, &ship_delta_y, EvalDescPtr->which_turn);
+ ShipPtr->next.location.x =
+ ShipPtr->current.location.x + ship_delta_x;
+ ShipPtr->next.location.y =
+ ShipPtr->current.location.y + ship_delta_y;
+
+ OtherObjPtr = EvalDescPtr->ObjectPtr;
+ OtherVelocity = OtherObjPtr->velocity;
+ GetNextVelocityComponents (&OtherVelocity,
+ &other_delta_x, &other_delta_y, EvalDescPtr->which_turn);
+
+ delta_x = (OtherObjPtr->current.location.x + other_delta_x)
+ - ShipPtr->next.location.x;
+ delta_y = (OtherObjPtr->current.location.y + other_delta_y)
+ - ShipPtr->next.location.y;
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = WRAP_DELTA_Y (delta_y);
+ desired_thrust_angle = ARCTAN (delta_x, delta_y);
+
+ maneuver_state = 0;
+ if (ShipPtr->turn_wait == 0)
+ maneuver_state |= LEFT | RIGHT;
+ if (ShipPtr->thrust_wait == 0)
+ maneuver_state |= THRUST;
+
+ delta_x = ship_delta_x - other_delta_x;
+ delta_y = ship_delta_y - other_delta_y;
+ travel_angle = ARCTAN (delta_x, delta_y);
+ desired_turn_angle = NORMALIZE_ANGLE (desired_thrust_angle + HALF_CIRCLE);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ RDPtr = StarShipPtr->RaceDescPtr;
+ if (EvalDescPtr->MoveState == AVOID)
+ {
+ desired_turn_angle =
+ NORMALIZE_ANGLE (desired_turn_angle - EvalDescPtr->facing);
+
+ if (NORMALIZE_FACING (ANGLE_TO_FACING (desired_turn_angle)))
+ {
+ if (desired_turn_angle <= HALF_CIRCLE)
+ desired_thrust_angle = RIGHT;
+ else /* if (desired_turn_angle > HALF_CIRCLE) */
+ desired_thrust_angle = LEFT;
+ }
+ else
+ {
+ desired_turn_angle = NORMALIZE_ANGLE (
+ FACING_TO_ANGLE (StarShipPtr->ShipFacing)
+ - EvalDescPtr->facing
+ );
+ if ((desired_turn_angle & (HALF_CIRCLE - 1)) == 0)
+ desired_thrust_angle = TFB_Random () & 1 ? RIGHT : LEFT;
+ else
+ desired_thrust_angle = desired_turn_angle < HALF_CIRCLE ? RIGHT : LEFT;
+ }
+
+ if (desired_thrust_angle == LEFT)
+ {
+#define FLANK_LEFT -QUADRANT
+#define SHIP_LEFT -OCTANT
+ desired_thrust_angle = EvalDescPtr->facing
+ + FLANK_LEFT - (SHIP_LEFT >> 1);
+ }
+ else
+ {
+#define FLANK_RIGHT QUADRANT
+#define SHIP_RIGHT OCTANT
+ desired_thrust_angle = EvalDescPtr->facing
+ + FLANK_RIGHT - (SHIP_RIGHT >> 1);
+ }
+
+ desired_thrust_angle = NORMALIZE_ANGLE (desired_thrust_angle);
+ }
+ else if (GRAVITY_MASS (OtherObjPtr->mass_points))
+ {
+ COUNT planet_facing;
+
+ planet_facing = NORMALIZE_FACING (ANGLE_TO_FACING (desired_thrust_angle));
+ cone_of_fire = NORMALIZE_FACING (
+ planet_facing
+ - StarShipPtr->ShipFacing
+ + ANGLE_TO_FACING (QUADRANT));
+
+ if (RDPtr->characteristics.thrust_increment !=
+ RDPtr->characteristics.max_thrust)
+ maneuver_state &= ~THRUST;
+
+ /* if not pointing towards planet */
+ if (cone_of_fire > ANGLE_TO_FACING (QUADRANT << 1))
+ desired_turn_angle = desired_thrust_angle;
+ /* if pointing directly at planet */
+ else if (cone_of_fire == ANGLE_TO_FACING (QUADRANT)
+ && NORMALIZE_FACING (ANGLE_TO_FACING (travel_angle)) != planet_facing)
+ desired_turn_angle = travel_angle;
+ else if (cone_of_fire == 0
+ || cone_of_fire == ANGLE_TO_FACING (QUADRANT << 1)
+ || (!(maneuver_state & THRUST)
+ && (cone_of_fire < ANGLE_TO_FACING (OCTANT)
+ || cone_of_fire > ANGLE_TO_FACING ((QUADRANT << 1) - OCTANT))))
+ {
+ desired_turn_angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+ if (NORMALIZE_ANGLE (desired_turn_angle
+ - travel_angle + QUADRANT) > HALF_CIRCLE)
+ desired_turn_angle = travel_angle;
+ if (ShipPtr->thrust_wait == 0)
+ maneuver_state |= THRUST;
+ }
+
+ desired_thrust_angle = desired_turn_angle;
+ }
+ else
+ {
+ COUNT WRange;
+
+ WRange = WEAPON_RANGE (
+ &RDPtr->cyborg_control
+ );
+
+ cone_of_fire = NORMALIZE_ANGLE (desired_turn_angle
+ - EvalDescPtr->facing + OCTANT);
+ if (OtherObjPtr->state_flags & PLAYER_SHIP)
+ {
+ UWORD fire_flags, ship_flags;
+ COUNT facing;
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (OtherObjPtr, &EnemyStarShipPtr);
+ ship_flags = EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags;
+ for (fire_flags = FIRES_FORE, facing = EvalDescPtr->facing;
+ fire_flags <= FIRES_LEFT;
+ fire_flags <<= 1, facing += QUADRANT)
+ {
+ if
+ (
+ /* he's dangerous in this direction */
+ (ship_flags & fire_flags)
+ /* he's facing direction you want to go */
+ && (cone_of_fire = NORMALIZE_ANGLE (
+ desired_turn_angle - facing + OCTANT
+ )) <= QUADRANT
+ /* he's moving */
+ && ((other_delta_x != 0 || other_delta_y != 0)
+ /* he's coasting backwards */
+ && NORMALIZE_ANGLE (
+ (GetVelocityTravelAngle (&OtherVelocity) + HALF_CIRCLE)
+ - facing + OCTANT) <= QUADRANT)
+ )
+ {
+ /* need to be close for a kill */
+ if (WRange < LONG_RANGE_WEAPON
+ && EvalDescPtr->which_turn <= 32)
+ {
+ /* catch him on the back side */
+ desired_thrust_angle = desired_turn_angle;
+ goto DoManeuver;
+ }
+
+ break;
+ }
+ }
+
+ if (EvalDescPtr->which_turn <= 8
+ && RDPtr->characteristics.max_thrust <=
+ EnemyStarShipPtr->RaceDescPtr->characteristics.max_thrust)
+ goto DoManeuver;
+ }
+
+ if
+ (
+#ifdef NOTYET
+ WRange < LONG_RANGE_WEAPON
+ &&
+#endif /* NOTYET */
+ /* not at full speed */
+ !(StarShipPtr->cur_status_flags
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED))
+ && (PlotIntercept (
+ ShipPtr, OtherObjPtr, 40, CLOSE_RANGE_WEAPON << 1
+ )
+#ifdef NOTYET
+ ||
+ (
+ /* object's facing direction you want to go */
+ cone_of_fire <= QUADRANT
+ /* and you're basically going in that direction */
+ && (travel_angle == FULL_CIRCLE
+ || NORMALIZE_ANGLE (travel_angle
+ - desired_thrust_angle + QUADRANT) <= HALF_CIRCLE)
+ /* and object's in range */
+ && PlotIntercept (ShipPtr, OtherObjPtr, 1, WRange)
+ )
+#endif /* NOTYET */
+ )
+ )
+ {
+ if
+ (
+ /* pointed straight at him */
+ NORMALIZE_ANGLE (desired_thrust_angle
+ - FACING_TO_ANGLE (StarShipPtr->ShipFacing) + OCTANT) <= QUADRANT
+ /* or not exposed to business end */
+ || cone_of_fire > QUADRANT
+ )
+ {
+ desired_thrust_angle = desired_turn_angle;
+ }
+ else
+ {
+#ifdef NOTYET
+ if
+ (
+ travel_angle != FULL_CIRCLE
+ && NORMALIZE_ANGLE (travel_angle
+ - desired_turn_angle + OCTANT) <= QUADRANT
+ )
+ {
+ desired_turn_angle =
+ NORMALIZE_ANGLE ((EvalDescPtr->facing + HALF_CIRCLE)
+ + (travel_angle - desired_turn_angle));
+ if (!(maneuver_state & (LEFT | RIGHT)))
+ maneuver_state &= ~THRUST;
+ }
+
+ if (maneuver_state & (LEFT | RIGHT))
+ {
+ TurnShip (ShipPtr, desired_turn_angle);
+ maneuver_state &= ~(LEFT | RIGHT);
+ }
+#endif /* NOTYET */
+
+ desired_thrust_angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+desired_turn_angle = desired_thrust_angle;
+ }
+ }
+ else if ((cone_of_fire = PlotIntercept (
+ ShipPtr, OtherObjPtr, 10, WRange
+#ifdef OLD
+ - (WRange >> 3)
+#else /* !OLD */
+ - (WRange >> 2)
+#endif /* OLD */
+ )))
+ {
+ if (RDPtr->characteristics.thrust_increment !=
+ RDPtr->characteristics.max_thrust
+ /* and already at full speed */
+ && (StarShipPtr->cur_status_flags
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED))
+ /* and facing away from enemy */
+ && (NORMALIZE_ANGLE (desired_turn_angle
+ - ARCTAN (ship_delta_x, ship_delta_y)
+ + (OCTANT + 2)) <= ((OCTANT + 2) << 1)
+ /* or not on collision course */
+ || !PlotIntercept (
+ ShipPtr, OtherObjPtr, 30, CLOSE_RANGE_WEAPON << 1
+ )))
+ maneuver_state &= ~THRUST;
+ /* veer off */
+ else if (cone_of_fire == 1
+ || RDPtr->characteristics.thrust_increment !=
+ RDPtr->characteristics.max_thrust)
+ {
+ if (maneuver_state & (LEFT | RIGHT))
+ {
+ TurnShip (ShipPtr, desired_turn_angle);
+ maneuver_state &= ~(LEFT | RIGHT);
+ }
+
+ if (NORMALIZE_ANGLE (desired_thrust_angle
+ - ARCTAN (ship_delta_x, ship_delta_y)
+ + (OCTANT + 2)) <= ((OCTANT + 2) << 1))
+ desired_thrust_angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+ else
+ desired_thrust_angle = desired_turn_angle;
+ }
+ }
+ }
+
+DoManeuver:
+ if (maneuver_state & (LEFT | RIGHT))
+ TurnShip (ShipPtr, desired_thrust_angle);
+
+ if (maneuver_state & THRUST)
+ ThrustShip (ShipPtr, desired_thrust_angle);
+}
+
+void
+Avoid (ELEMENT *ShipPtr, EVALUATE_DESC *EvalDescPtr)
+{
+ (void) ShipPtr; /* Satisfying compiler (unused parameter) */
+ (void) EvalDescPtr; /* Satisfying compiler (unused parameter) */
+}
+
+BATTLE_INPUT_STATE
+tactical_intelligence (ComputerInputContext *context, STARSHIP *StarShipPtr)
+{
+ ELEMENT *ShipPtr;
+ ELEMENT Ship;
+ COUNT ShipFacing;
+ HELEMENT hElement, hNextElement;
+ COUNT ConcernCounter;
+ EVALUATE_DESC ObjectsOfConcern[10];
+ BOOLEAN ShipMoved, UltraManeuverable;
+ STARSHIP *EnemyStarShipPtr;
+ RACE_DESC *RDPtr;
+ RACE_DESC *EnemyRDPtr;
+
+ RDPtr = StarShipPtr->RaceDescPtr;
+
+ if (RDPtr->cyborg_control.ManeuverabilityIndex == 0)
+ InitCyborg (StarShipPtr);
+
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ if (RDPtr->ship_info.crew_level == 0
+ || GetPrimType (&DisplayArray[ShipPtr->PrimIndex]) == NO_PRIM)
+ {
+ UnlockElement (StarShipPtr->hShip);
+ return (0);
+ }
+
+ ShipMoved = TRUE;
+ /* Disable ship's special completely for the Standard AI */
+ if (StarShipPtr->control & STANDARD_RATING)
+ ++StarShipPtr->special_counter;
+
+#ifdef DEBUG_CYBORG
+if (!(ShipPtr->state_flags & FINITE_LIFE)
+ && ShipPtr->life_span == NORMAL_LIFE)
+ ShipPtr->life_span += 2; /* make ship invulnerable */
+#endif /* DEBUG_CYBORG */
+ Ship = *ShipPtr;
+ UnlockElement (StarShipPtr->hShip);
+ ShipFacing = StarShipPtr->ShipFacing;
+
+ for (ConcernCounter = 0;
+ ConcernCounter <= FIRST_EMPTY_INDEX; ++ConcernCounter)
+ {
+ ObjectsOfConcern[ConcernCounter].ObjectPtr = 0;
+ ObjectsOfConcern[ConcernCounter].MoveState = NO_MOVEMENT;
+ ObjectsOfConcern[ConcernCounter].which_turn = (COUNT)~0;
+ }
+ --ConcernCounter;
+
+ UltraManeuverable = (BOOLEAN)(
+ RDPtr->characteristics.thrust_increment ==
+ RDPtr->characteristics.max_thrust
+ && MANEUVERABILITY (&RDPtr->cyborg_control) >= MEDIUM_SHIP
+ );
+
+ if (Ship.turn_wait == 0)
+ {
+ ShipMoved = FALSE;
+ StarShipPtr->ship_input_state &= ~(LEFT | RIGHT);
+ }
+ if (Ship.thrust_wait == 0)
+ {
+ ShipMoved = FALSE;
+ StarShipPtr->ship_input_state &= ~THRUST;
+ }
+
+ for (hElement = GetHeadElement ();
+ hElement != 0; hElement = hNextElement)
+ {
+ EVALUATE_DESC ed;
+
+ ed.MoveState = NO_MOVEMENT;
+
+ LockElement (hElement, &ed.ObjectPtr);
+ hNextElement = GetSuccElement (ed.ObjectPtr);
+ if (CollisionPossible (ed.ObjectPtr, &Ship))
+ {
+ SIZE dx, dy;
+
+ dx = ed.ObjectPtr->next.location.x
+ - Ship.next.location.x;
+ dy = ed.ObjectPtr->next.location.y
+ - Ship.next.location.y;
+ dx = WRAP_DELTA_X (dx);
+ dy = WRAP_DELTA_Y (dy);
+ if (GRAVITY_MASS (ed.ObjectPtr->mass_points))
+ {
+ COUNT maneuver_turn, ship_bounds;
+ RECT ship_footprint = {{0, 0}, {0, 0}};
+
+ if (UltraManeuverable)
+ maneuver_turn = 16;
+ else if (MANEUVERABILITY (&RDPtr->cyborg_control) <= MEDIUM_SHIP)
+ maneuver_turn = 48;
+ else
+ maneuver_turn = 32;
+
+ GetFrameRect (SetAbsFrameIndex (
+ Ship.IntersectControl.IntersectStamp.frame, 0
+ ), &ship_footprint);
+ ship_bounds = (COUNT)(ship_footprint.extent.width
+ + ship_footprint.extent.height);
+
+ if (!ShipMoved && (ed.which_turn =
+ PlotIntercept (ed.ObjectPtr, &Ship, maneuver_turn,
+ DISPLAY_TO_WORLD (30 + (ship_bounds * 3 /* << 2 */)))))
+ {
+ if (ed.which_turn > 1
+ || PlotIntercept (ed.ObjectPtr, &Ship, 1,
+ DISPLAY_TO_WORLD (35 + ship_bounds))
+ || PlotIntercept (ed.ObjectPtr, &Ship,
+ maneuver_turn << 1,
+ DISPLAY_TO_WORLD (40 + ship_bounds)) > 1)
+ {
+ ed.facing = ARCTAN (-dx, -dy);
+ if (UltraManeuverable)
+ ed.MoveState = AVOID;
+ else // Try a gravity whip
+ ed.MoveState = ENTICE;
+
+ ObjectsOfConcern[GRAVITY_MASS_INDEX] = ed;
+ }
+ else if (!UltraManeuverable &&
+ !IsVelocityZero (&Ship.velocity))
+ { // Try an orbital insertion, don't thrust
+ ++Ship.thrust_wait;
+ if (Ship.turn_wait)
+ ShipMoved = TRUE;
+ }
+ }
+ }
+ else if (ed.ObjectPtr->state_flags & PLAYER_SHIP)
+ {
+ GetElementStarShip (ed.ObjectPtr, &EnemyStarShipPtr);
+ EnemyRDPtr = EnemyStarShipPtr->RaceDescPtr;
+ if (EnemyRDPtr->cyborg_control.ManeuverabilityIndex == 0)
+ InitCyborg (EnemyStarShipPtr);
+
+ ed.which_turn = WORLD_TO_TURN (
+ square_root ((long)dx * dx + (long)dy * dy));
+ if (ed.which_turn >
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn)
+ {
+ UnlockElement (hElement);
+ continue;
+ }
+ else if (ed.which_turn == 0)
+ ed.which_turn = 1;
+
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr = ed.ObjectPtr;
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].facing =
+#ifdef MAYBE
+ OBJECT_CLOAKED (ed.ObjectPtr) ?
+ GetVelocityTravelAngle (&ed.ObjectPtr->velocity) :
+#endif /* MAYBE */
+ FACING_TO_ANGLE (EnemyStarShipPtr->ShipFacing);
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn = ed.which_turn;
+
+ if (ShipMoved
+ || ed.ObjectPtr->mass_points > MAX_SHIP_MASS
+ || (WEAPON_RANGE (&RDPtr->cyborg_control) < LONG_RANGE_WEAPON
+ && (WEAPON_RANGE (&RDPtr->cyborg_control) <= CLOSE_RANGE_WEAPON
+ || (WEAPON_RANGE (&EnemyRDPtr->cyborg_control) >= LONG_RANGE_WEAPON
+ && (EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags & SEEKING_WEAPON))
+ || (
+#ifdef OLD
+ MANEUVERABILITY (&RDPtr->cyborg_control) <
+ MANEUVERABILITY (&EnemyRDPtr->cyborg_control)
+#else /* !OLD */
+ RDPtr->characteristics.max_thrust <
+ EnemyRDPtr->characteristics.max_thrust
+#endif /* !OLD */
+ && WEAPON_RANGE (&RDPtr->cyborg_control) <
+ WEAPON_RANGE (&EnemyRDPtr->cyborg_control)))))
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].MoveState = PURSUE;
+ else
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].MoveState = ENTICE;
+
+ if ((EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags & IMMEDIATE_WEAPON)
+ && ship_weapons (ed.ObjectPtr, &Ship, 0))
+ {
+ ed.which_turn = 1;
+ ed.MoveState = AVOID;
+ ed.facing = ObjectsOfConcern[ENEMY_SHIP_INDEX].facing;
+
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX] = ed;
+ }
+ }
+ else if (ed.ObjectPtr->pParent == 0)
+ {
+ if (!(ed.ObjectPtr->state_flags & FINITE_LIFE))
+ {
+ ed.which_turn = WORLD_TO_TURN (
+ square_root ((long)dx * dx + (long)dy * dy)
+ );
+
+ if (ed.which_turn <
+ ObjectsOfConcern[FIRST_EMPTY_INDEX].which_turn)
+ {
+ ed.MoveState = PURSUE;
+ ed.facing = GetVelocityTravelAngle (
+ &ed.ObjectPtr->velocity
+ );
+
+ ObjectsOfConcern[FIRST_EMPTY_INDEX] = ed;
+ }
+ }
+ }
+ else if (!elementsOfSamePlayer (ed.ObjectPtr, &Ship)
+ && ed.ObjectPtr->preprocess_func != crew_preprocess
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn > 1
+ && ed.ObjectPtr->life_span > 0)
+ {
+ GetElementStarShip (ed.ObjectPtr, &EnemyStarShipPtr);
+ EnemyRDPtr = EnemyStarShipPtr->RaceDescPtr;
+ if (((EnemyRDPtr->ship_info.ship_flags & SEEKING_WEAPON)
+ && ed.ObjectPtr->next.image.farray !=
+ EnemyRDPtr->ship_data.special)
+ || ((EnemyRDPtr->ship_info.ship_flags & SEEKING_SPECIAL)
+ && ed.ObjectPtr->next.image.farray ==
+ EnemyRDPtr->ship_data.special))
+ {
+ if ((!(ed.ObjectPtr->state_flags & (FINITE_LIFE | CREW_OBJECT))
+ && RDPtr->characteristics.max_thrust > DISPLAY_TO_WORLD (8))
+ || NORMALIZE_ANGLE (GetVelocityTravelAngle (
+ &ed.ObjectPtr->velocity
+ ) - ARCTAN (-dx, -dy)
+ + QUADRANT) > HALF_CIRCLE)
+ ed.which_turn = 0;
+ else
+ {
+ ed.which_turn = WORLD_TO_TURN (
+ square_root ((long)dx * dx + (long)dy * dy)
+ );
+
+ ed.MoveState = ENTICE;
+ if (UltraManeuverable)
+ {
+ if (ed.which_turn == 0)
+ ed.which_turn = 1;
+ else if (ed.which_turn > 16)
+ ed.which_turn = 0;
+ }
+ else if (ed.which_turn == 0)
+ ed.which_turn = 1;
+ else if (ed.which_turn > 16
+ || (MANEUVERABILITY (
+ &RDPtr->cyborg_control
+ ) > MEDIUM_SHIP
+ && ed.which_turn > 8))
+ ed.which_turn = 0;
+ }
+ }
+ else if (!(StarShipPtr->control & AWESOME_RATING))
+ ed.which_turn = 0;
+ else
+ {
+ ed.which_turn =
+ PlotIntercept (ed.ObjectPtr,
+ &Ship, ed.ObjectPtr->life_span,
+ DISPLAY_TO_WORLD (40));
+ ed.MoveState = AVOID;
+ }
+
+ if (ed.which_turn > 0
+ && (ed.which_turn <
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn
+ || (ed.which_turn ==
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn
+ && ed.MoveState == AVOID)))
+ {
+ ed.facing = GetVelocityTravelAngle (
+ &ed.ObjectPtr->velocity
+ );
+
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX] = ed;
+ }
+ }
+ else if ((ed.ObjectPtr->state_flags & CREW_OBJECT)
+ && ((!(ed.ObjectPtr->state_flags & IGNORE_SIMILAR)
+ && elementsOfSamePlayer (ed.ObjectPtr, &Ship))
+ || ed.ObjectPtr->preprocess_func == crew_preprocess)
+ && ObjectsOfConcern[CREW_OBJECT_INDEX].which_turn > 1)
+ {
+ ed.which_turn = WORLD_TO_TURN (
+ square_root ((long)dx * dx + (long)dy * dy)
+ );
+
+ if (ed.which_turn == 0)
+ ed.which_turn = 1;
+
+ if (ObjectsOfConcern[CREW_OBJECT_INDEX].which_turn >
+ ed.which_turn
+ && (ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn > 32
+ || (ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn > 8
+ && StarShipPtr->hShip == ed.ObjectPtr->hTarget)))
+ {
+ ed.MoveState = PURSUE;
+ ed.facing = 0;
+ ObjectsOfConcern[CREW_OBJECT_INDEX] = ed;
+ }
+ }
+ }
+ UnlockElement (hElement);
+ }
+
+ RDPtr->cyborg_control.intelligence_func (&Ship, ObjectsOfConcern,
+ ConcernCounter);
+#ifdef DEBUG_CYBORG
+StarShipPtr->ship_input_state &= ~SPECIAL;
+#endif /* DEBUG_CYBORG */
+
+ StarShipPtr->ShipFacing = ShipFacing;
+ {
+ BATTLE_INPUT_STATE InputState;
+
+ InputState = 0;
+ if (StarShipPtr->ship_input_state & LEFT)
+ InputState |= BATTLE_LEFT;
+ else if (StarShipPtr->ship_input_state & RIGHT)
+ InputState |= BATTLE_RIGHT;
+ if (StarShipPtr->ship_input_state & THRUST)
+ InputState |= BATTLE_THRUST;
+ if (StarShipPtr->ship_input_state & WEAPON)
+ InputState |= BATTLE_WEAPON;
+ if (StarShipPtr->ship_input_state & SPECIAL)
+ InputState |= BATTLE_SPECIAL;
+
+ (void) context;
+ return (InputState);
+ }
+}
+
diff --git a/src/uqm/demo.c b/src/uqm/demo.c
new file mode 100644
index 0000000..78b26fa
--- /dev/null
+++ b/src/uqm/demo.c
@@ -0,0 +1,141 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "demo.h"
+#include "libs/declib.h"
+#include "setup.h"
+
+#if DEMO_MODE || CREATE_JOURNAL
+static DECODE_REF journal_fh;
+static char journal_buf[1024
+#if CREATE_JOURNAL
+ * 8
+#else /* DEMO_MODE */
+ * 2
+#endif
+ ];
+INPUT_REF DemoInput;
+#endif
+
+#if DEMO_MODE
+
+static INPUT_REF OldArrowInput;
+
+INPUT_STATE
+demo_input (INPUT_REF InputRef, INPUT_STATE InputState)
+{
+ if (InputState || AnyButtonPress () || cread (
+ &InputState, sizeof (InputState), 1, journal_fh
+ ) == 0)
+ {
+ cclose (journal_fh);
+ journal_fh = 0;
+
+ StopMusic ();
+ StopSound ();
+
+ FreeKernel ();
+ exit (1);
+ }
+
+ return (InputState);
+}
+
+#endif /* DEMO_MODE */
+
+#if CREATE_JOURNAL
+
+void
+JournalInput (INPUT_STATE InputState)
+{
+ if (ArrowInput != DemoInput && journal_fh)
+ cwrite (&InputState, sizeof (InputState), 1, journal_fh);
+}
+
+#endif /* CREATE_JOURNAL */
+
+#if DEMO_MODE || CREATE_JOURNAL
+
+void
+OpenJournal (void)
+{
+ DWORD start_seed;
+
+#if CREATE_JOURNAL
+ if (create_journal)
+ {
+ if (journal_fh = copen (journal_buf, MEMORY_STREAM, STREAM_WRITE))
+ {
+ start_seed = SeedRandomNumbers ();
+ cwrite (&start_seed, sizeof (start_seed), 1, journal_fh);
+ }
+ }
+ else
+#endif /* CREATE_JOURNAL */
+ {
+ uio_Stream *fp;
+
+ if (fp = res_OpenResFile ("starcon.jnl", "rb"))
+ {
+ ReadResFile (journal_buf, 1, sizeof (journal_buf), fp);
+ res_CloseResFile (fp);
+
+ if (journal_fh = copen (journal_buf, MEMORY_STREAM, STREAM_READ))
+ {
+ OldArrowInput = ArrowInput;
+ ArrowInput = DemoInput;
+ PlayerInput[0] = PlayerInput[1] = DemoInput;
+
+ FlushInput ();
+
+ cread (&start_seed, sizeof (start_seed), 1, journal_fh);
+ TFB_SeedRandom (start_seed);
+ }
+ }
+ }
+}
+
+BOOLEAN
+CloseJournal (void)
+{
+ if (journal_fh)
+ {
+ uio_Stream *fp;
+
+ cclose (journal_fh);
+ journal_fh = 0;
+
+ if (ArrowInput == DemoInput)
+ {
+ ArrowInput = OldArrowInput;
+ return (FALSE);
+ }
+#if CREATE_JOURNAL
+ else if (fp = res_OpenResFile ("starcon.jnl", "wb"))
+ {
+ WriteResFile (journal_buf, 1, sizeof (journal_buf), fp);
+ res_CloseResFile (fp);
+ }
+#endif /* CREATE_JOURNAL */
+ }
+
+ return (TRUE);
+}
+
+#endif
+
diff --git a/src/uqm/demo.h b/src/uqm/demo.h
new file mode 100644
index 0000000..25009c0
--- /dev/null
+++ b/src/uqm/demo.h
@@ -0,0 +1,55 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_DEMO_H_
+#define UQM_DEMO_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#ifndef DEMO_MODE
+#define DEMO_MODE 0
+#endif /* DEMO_MODE */
+#ifndef CREATE_JOURNAL
+#define CREATE_JOURNAL 0
+#endif /* CREATE_JOURNAL */
+
+#if !(DEMO_MODE || CREATE_JOURNAL)
+
+#define OpenJournal SeedRandomNumbers
+#define CloseJournal() TRUE
+#define JournalInput(is)
+
+#else
+
+extern void OpenJournal (void);
+extern BOOLEAN CloseJournal (void);
+#if !CREATE_JOURNAL
+#define JournalInput(is)
+#else /* CREATE_JOURNAL */
+extern void JournalInput (INPUT_STATE InputState);
+#endif /* CREATE_JOURNAL */
+
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_DEMO_H_ */
diff --git a/src/uqm/displist.c b/src/uqm/displist.c
new file mode 100644
index 0000000..bdde704
--- /dev/null
+++ b/src/uqm/displist.c
@@ -0,0 +1,274 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "displist.h"
+#include "libs/log.h"
+
+#ifdef QUEUE_TABLE
+#define NULL_HANDLE NULL
+#endif
+
+/*
+ * This file contains code for generic doubly linked lists.
+ * If QUEUE_TABLE is defined, each lists has its own preallocated
+ * pool of link structures. The size is specific on InitQueue(),
+ * and poses a hard limit on the number of elements in the list.
+ */
+
+BOOLEAN
+InitQueue (QUEUE *pq, COUNT num_elements, OBJ_SIZE size)
+{
+ SetHeadLink (pq, NULL_HANDLE);
+ SetTailLink (pq, NULL_HANDLE);
+ SetLinkSize (pq, size);
+#ifndef QUEUE_TABLE
+ return (TRUE);
+#else /* QUEUE_TABLE */
+ SetFreeList (pq, NULL_HANDLE);
+#if 0
+ log_add (log_Debug, "InitQueue(): num_elements = %d (%d)",
+ num_elements, (BYTE)num_elements);
+#endif
+ if (AllocQueueTab (pq, num_elements) != NULL)
+ {
+ do
+ FreeLink (pq, GetLinkAddr (pq, num_elements));
+ while (--num_elements);
+
+ return (TRUE);
+ }
+
+ return (FALSE);
+#endif /* QUEUE_TABLE */
+}
+
+BOOLEAN
+UninitQueue (QUEUE *pq)
+{
+#ifdef QUEUE_TABLE
+ SetHeadLink (pq, NULL_HANDLE);
+ SetTailLink (pq, NULL_HANDLE);
+ SetFreeList (pq, NULL_HANDLE);
+ FreeQueueTab (pq);
+
+ return (TRUE);
+#else /* !QUEUE_TABLE */
+ HLINK hLink;
+
+ while ((hLink = GetHeadLink (pq)) != NULL_HANDLE)
+ {
+ RemoveQueue (pq, hLink);
+ if (!FreeLink (pq, hLink))
+ return (FALSE);
+ }
+
+ return (TRUE);
+#endif /* QUEUE_TABLE */
+}
+
+// Empty the queue. The elements linked to in the queue are unchanged.
+void
+ReinitQueue (QUEUE *pq)
+{
+ SetHeadLink (pq, NULL_HANDLE);
+ SetTailLink (pq, NULL_HANDLE);
+#ifdef QUEUE_TABLE
+ {
+ COUNT num_elements;
+
+ SetFreeList (pq, NULL_HANDLE);
+
+ num_elements = SizeQueueTab (pq);
+ if (num_elements)
+ {
+ do
+ FreeLink (pq, GetLinkAddr (pq, num_elements));
+ while (--num_elements);
+ }
+ }
+#endif /* QUEUE_TABLE */
+}
+
+#ifdef QUEUE_TABLE
+HLINK
+AllocLink (QUEUE *pq)
+{
+ HLINK hLink;
+
+ hLink = GetFreeList (pq);
+ if (hLink)
+ {
+ LINK *LinkPtr;
+
+ LinkPtr = LockLink (pq, hLink);
+ SetFreeList (pq, _GetSuccLink (LinkPtr));
+ UnlockLink (pq, hLink);
+ }
+ else
+ log_add (log_Debug, "AllocLink(): No more elements");
+
+ return (hLink);
+}
+
+void
+FreeLink (QUEUE *pq, HLINK hLink)
+{
+ LINK *LinkPtr;
+
+ LinkPtr = LockLink (pq, hLink);
+ _SetSuccLink (LinkPtr, GetFreeList (pq));
+ UnlockLink (pq, hLink);
+
+ SetFreeList (pq, hLink);
+}
+#endif /* QUEUE_TABLE */
+
+void
+PutQueue (QUEUE *pq, HLINK hLink)
+{
+ LINK *LinkPtr;
+
+ if (GetHeadLink (pq) == 0)
+ SetHeadLink (pq, hLink);
+ else
+ {
+ HLINK hTail;
+ LINK *lpTail;
+
+ hTail = GetTailLink (pq);
+ lpTail = LockLink (pq, hTail);
+ _SetSuccLink (lpTail, hLink);
+ UnlockLink (pq, hTail);
+ }
+
+ LinkPtr = LockLink (pq, hLink);
+ _SetPredLink (LinkPtr, GetTailLink (pq));
+ _SetSuccLink (LinkPtr, NULL_HANDLE);
+ UnlockLink (pq, hLink);
+
+ SetTailLink (pq, hLink);
+}
+
+void
+InsertQueue (QUEUE *pq, HLINK hLink, HLINK hRefLink)
+{
+ if (hRefLink == 0)
+ PutQueue (pq, hLink);
+ else
+ {
+ LINK *LinkPtr;
+ LINK *RefLinkPtr;
+
+ LinkPtr = LockLink (pq, hLink);
+ RefLinkPtr = LockLink (pq, hRefLink);
+ _SetPredLink (LinkPtr, _GetPredLink (RefLinkPtr));
+ _SetPredLink (RefLinkPtr, hLink);
+ _SetSuccLink (LinkPtr, hRefLink);
+
+ if (GetHeadLink (pq) == hRefLink)
+ SetHeadLink (pq, hLink);
+ else
+ {
+ HLINK hPredLink;
+ LINK *PredLinkPtr;
+
+ hPredLink = _GetPredLink (LinkPtr);
+ PredLinkPtr = LockLink (pq, hPredLink);
+ _SetSuccLink (PredLinkPtr, hLink);
+ UnlockLink (pq, hPredLink);
+ }
+ UnlockLink (pq, hRefLink);
+ UnlockLink (pq, hLink);
+ }
+}
+
+void
+RemoveQueue (QUEUE *pq, HLINK hLink)
+{
+ LINK *LinkPtr;
+
+ LinkPtr = LockLink (pq, hLink);
+ if (GetHeadLink (pq) == hLink)
+ {
+ SetHeadLink (pq, _GetSuccLink (LinkPtr));
+ }
+ else
+ {
+ HLINK hPredLink;
+ LINK *PredLinkPtr;
+
+ hPredLink = _GetPredLink (LinkPtr);
+ PredLinkPtr = LockLink (pq, hPredLink);
+ _SetSuccLink (PredLinkPtr, _GetSuccLink (LinkPtr));
+ UnlockLink (pq, hPredLink);
+ }
+ if (GetTailLink (pq) == hLink)
+ {
+ SetTailLink (pq, _GetPredLink (LinkPtr));
+ }
+ else
+ {
+ HLINK hSuccLink, hPredLink;
+ LINK *SuccLinkPtr;
+
+ hSuccLink = _GetSuccLink (LinkPtr);
+ SuccLinkPtr = LockLink (pq, hSuccLink);
+ hPredLink = _GetPredLink (LinkPtr);
+ _SetPredLink (SuccLinkPtr, hPredLink);
+ UnlockLink (pq, hSuccLink);
+ }
+ UnlockLink (pq, hLink);
+}
+
+COUNT
+CountLinks (QUEUE *pq)
+{
+ COUNT LinkCount;
+ HLINK hLink, hNextLink;
+
+ LinkCount = 0;
+ for (hLink = GetHeadLink (pq); hLink; hLink = hNextLink)
+ {
+ LINK *LinkPtr;
+
+ ++LinkCount;
+
+ LinkPtr = LockLink (pq, hLink);
+ hNextLink = _GetSuccLink (LinkPtr);
+ UnlockLink (pq, hLink);
+ }
+
+ return (LinkCount);
+}
+
+void
+ForAllLinks (QUEUE *pq, void (*callback)(LINK *, void *), void *arg)
+{
+ HLINK hLink, hNextLink;
+
+ for (hLink = GetHeadLink (pq); hLink; hLink = hNextLink)
+ {
+ LINK *LinkPtr;
+ LinkPtr = LockLink (pq, hLink);
+ hNextLink = _GetSuccLink (LinkPtr);
+ (*callback) (LinkPtr, arg);
+ UnlockLink (pq, hLink);
+ }
+}
+
+
diff --git a/src/uqm/displist.h b/src/uqm/displist.h
new file mode 100644
index 0000000..d9c2fc0
--- /dev/null
+++ b/src/uqm/displist.h
@@ -0,0 +1,131 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_DISPLIST_H_
+#define UQM_DISPLIST_H_
+
+#include <assert.h>
+#include "port.h"
+#include "libs/compiler.h"
+#include "libs/memlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// Note that we MUST use the QUEUE_TABLE variant at this time, because
+// certain gameplay elements depend on it. Namely, the maximum number
+// of HyperSpace encounter globes chasing the player is defined by the
+// allocated size of the encounter_q. If switched to non-table variant,
+// the max number of encounters will be virtually unlimited the way the
+// code works now.
+#define QUEUE_TABLE
+
+typedef void* QUEUE_HANDLE;
+
+typedef UWORD OBJ_SIZE;
+typedef QUEUE_HANDLE HLINK;
+
+typedef struct link
+{
+ // Every queue element of any queue must have these
+ // two as the first members
+ HLINK pred;
+ HLINK succ;
+} LINK;
+
+typedef struct /* queue */
+{
+ HLINK head;
+ HLINK tail;
+#ifdef QUEUE_TABLE
+ BYTE *pq_tab;
+ HLINK free_list;
+#endif
+ COUNT object_size;
+#ifdef QUEUE_TABLE
+ BYTE num_objects;
+#endif /* QUEUE_TABLE */
+} QUEUE;
+
+#ifdef QUEUE_TABLE
+
+extern HLINK AllocLink (QUEUE *pq);
+extern void FreeLink (QUEUE *pq, HLINK hLink);
+
+static inline LINK *
+LockLink (const QUEUE *pq, HLINK h)
+{
+ if (h) // Apparently, h==0 is OK
+ { // Make sure the link is actually in our queue!
+ assert (pq->pq_tab && (BYTE*)h >= pq->pq_tab &&
+ (BYTE*)h < pq->pq_tab + pq->object_size * pq->num_objects);
+ }
+ return (LINK*)h;
+}
+
+static inline void
+UnlockLink (const QUEUE *pq, HLINK h)
+{
+ if (h) // Apparently, h==0 is OK
+ { // Make sure the link is actually in our queue!
+ assert (pq->pq_tab && (BYTE*)h >= pq->pq_tab &&
+ (BYTE*)h < pq->pq_tab + pq->object_size * pq->num_objects);
+ }
+}
+
+#define GetFreeList(pq) (pq)->free_list
+#define SetFreeList(pq, h) (pq)->free_list = (h)
+#define AllocQueueTab(pq,n) \
+ ((pq)->pq_tab = HMalloc (((COUNT)(pq)->object_size * \
+ (COUNT)((pq)->num_objects = (BYTE)(n)))))
+#define FreeQueueTab(pq) HFree ((pq)->pq_tab); (pq)->pq_tab = NULL
+#define SizeQueueTab(pq) (COUNT)((pq)->num_objects)
+#define GetLinkAddr(pq,i) (HLINK)((pq)->pq_tab + ((pq)->object_size * ((i) - 1)))
+#else /* !QUEUE_TABLE */
+#define AllocLink(pq) (HLINK)HMalloc ((pq)->object_size)
+#define LockLink(pq, h) ((LINK*)(h))
+#define UnlockLink(pq, h) ((void)(h))
+#define FreeLink(pq,h) HFree (h)
+#endif /* QUEUE_TABLE */
+
+#define SetLinkSize(pq,s) ((pq)->object_size = (COUNT)(s))
+#define GetLinkSize(pq) (COUNT)((pq)->object_size)
+#define GetHeadLink(pq) ((pq)->head)
+#define SetHeadLink(pq,h) ((pq)->head = (h))
+#define GetTailLink(pq) ((pq)->tail)
+#define SetTailLink(pq,h) ((pq)->tail = (h))
+#define _GetPredLink(lpE) ((lpE)->pred)
+#define _SetPredLink(lpE,h) ((lpE)->pred = (h))
+#define _GetSuccLink(lpE) ((lpE)->succ)
+#define _SetSuccLink(lpE,h) ((lpE)->succ = (h))
+
+extern BOOLEAN InitQueue (QUEUE *pq, COUNT num_elements, OBJ_SIZE size);
+extern BOOLEAN UninitQueue (QUEUE *pq);
+extern void ReinitQueue (QUEUE *pq);
+extern void PutQueue (QUEUE *pq, HLINK hLink);
+extern void InsertQueue (QUEUE *pq, HLINK hLink, HLINK hRefLink);
+extern void RemoveQueue (QUEUE *pq, HLINK hLink);
+extern COUNT CountLinks (QUEUE *pq);
+void ForAllLinks(QUEUE *pq, void (*callback)(LINK *, void *), void *arg);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_DISPLIST_H_ */
diff --git a/src/uqm/dummy.c b/src/uqm/dummy.c
new file mode 100644
index 0000000..9a95c36
--- /dev/null
+++ b/src/uqm/dummy.c
@@ -0,0 +1,207 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * This file seems to be a collection of functions that don't do
+ * much.
+ */
+
+#include "dummy.h"
+
+#include "coderes.h"
+#include "globdata.h"
+#include "races.h"
+
+#include "libs/compiler.h"
+#include "libs/log.h"
+#include "libs/memlib.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+
+
+typedef struct
+{
+ RACE_DESC data _ALIGNED_ANY;
+} CODERES_STRUCT;
+
+typedef enum
+{
+ ANDROSYN_CODE_RES,
+ ARILOU_CODE_RES,
+ BLACKURQ_CODE_RES,
+ CHENJESU_CODE_RES,
+ CHMMR_CODE_RES,
+ DRUUGE_CODE_RES,
+ HUMAN_CODE_RES,
+ ILWRATH_CODE_RES,
+ MELNORME_CODE_RES,
+ MMRNMHRM_CODE_RES,
+ MYCON_CODE_RES,
+ ORZ_CODE_RES,
+ PKUNK_CODE_RES,
+ SHOFIXTI_CODE_RES,
+ SLYLANDR_CODE_RES,
+ SPATHI_CODE_RES,
+ SUPOX_CODE_RES,
+ SYREEN_CODE_RES,
+ THRADD_CODE_RES,
+ UMGAH_CODE_RES,
+ URQUAN_CODE_RES,
+ UTWIG_CODE_RES,
+ VUX_CODE_RES,
+ YEHAT_CODE_RES,
+ ZOQFOT_CODE_RES,
+
+ SAMATRA_CODE_RES,
+ SIS_CODE_RES,
+ PROBE_CODE_RES
+} ShipCodeRes;
+
+typedef RACE_DESC *(*RaceDescInitFunc)(void);
+
+static RaceDescInitFunc
+CodeResToInitFunc(ShipCodeRes res)
+{
+ switch (res)
+ {
+ case ANDROSYN_CODE_RES: return &init_androsynth;
+ case ARILOU_CODE_RES: return &init_arilou;
+ case BLACKURQ_CODE_RES: return &init_black_urquan;
+ case CHENJESU_CODE_RES: return &init_chenjesu;
+ case CHMMR_CODE_RES: return &init_chmmr;
+ case DRUUGE_CODE_RES: return &init_druuge;
+ case HUMAN_CODE_RES: return &init_human;
+ case ILWRATH_CODE_RES: return &init_ilwrath;
+ case MELNORME_CODE_RES: return &init_melnorme;
+ case MMRNMHRM_CODE_RES: return &init_mmrnmhrm;
+ case MYCON_CODE_RES: return &init_mycon;
+ case ORZ_CODE_RES: return &init_orz;
+ case PKUNK_CODE_RES: return &init_pkunk;
+ case SHOFIXTI_CODE_RES: return &init_shofixti;
+ case SLYLANDR_CODE_RES: return &init_slylandro;
+ case SPATHI_CODE_RES: return &init_spathi;
+ case SUPOX_CODE_RES: return &init_supox;
+ case SYREEN_CODE_RES: return &init_syreen;
+ case THRADD_CODE_RES: return &init_thraddash;
+ case UMGAH_CODE_RES: return &init_umgah;
+ case URQUAN_CODE_RES: return &init_urquan;
+ case UTWIG_CODE_RES: return &init_utwig;
+ case VUX_CODE_RES: return &init_vux;
+ case YEHAT_CODE_RES: return &init_yehat;
+ case ZOQFOT_CODE_RES: return &init_zoqfotpik;
+ case SAMATRA_CODE_RES: return &init_samatra;
+ case SIS_CODE_RES: return &init_sis;
+ case PROBE_CODE_RES: return &init_probe;
+ default:
+ {
+ log_add (log_Warning, "Unknown SHIP identifier '%d'", res);
+ return NULL;
+ }
+ }
+}
+
+static void
+GetCodeResData (const char *ship_id, RESOURCE_DATA *resdata)
+{
+ BYTE which_res;
+ void *hData;
+
+ which_res = atoi (ship_id);
+ hData = HMalloc (sizeof (CODERES_STRUCT));
+ if (hData)
+ {
+ RaceDescInitFunc initFunc = CodeResToInitFunc (which_res);
+ RACE_DESC *RDPtr = (initFunc == NULL) ? NULL : (*initFunc)();
+ if (RDPtr == 0)
+ {
+ HFree (hData);
+ hData = 0;
+ }
+ else
+ {
+ CODERES_STRUCT *cs;
+
+ cs = (CODERES_STRUCT *) hData;
+ cs->data = *RDPtr; // Structure assignment.
+ }
+ }
+ resdata->ptr = hData;
+}
+
+static BOOLEAN
+_ReleaseCodeResData (void *data)
+{
+ HFree (data);
+ return TRUE;
+}
+
+BOOLEAN
+InstallCodeResType ()
+{
+ return (InstallResTypeVectors ("SHIP",
+ GetCodeResData, _ReleaseCodeResData, NULL));
+}
+
+
+void *
+LoadCodeResInstance (RESOURCE res)
+{
+ void *hData;
+
+ hData = res_GetResource (res);
+ if (hData)
+ res_DetachResource (res);
+
+ return hData;
+}
+
+
+BOOLEAN
+DestroyCodeRes (void *hCode)
+{
+ HFree (hCode);
+ return TRUE;
+}
+
+
+void*
+CaptureCodeRes (void *hCode, void *pData, void **ppLocData)
+{
+ CODERES_STRUCT *cs;
+
+ if (hCode == NULL)
+ {
+ log_add (log_Fatal, "dummy.c::CaptureCodeRes() hCode==NULL! FATAL!");
+ return(NULL);
+ }
+
+ cs = (CODERES_STRUCT *) hCode;
+ *ppLocData = &cs->data;
+
+ (void) pData; /* Satisfying compiler (unused parameter) */
+ return cs;
+}
+
+
+void *
+ReleaseCodeRes (void *CodeRef)
+{
+ return CodeRef;
+}
+
diff --git a/src/uqm/dummy.h b/src/uqm/dummy.h
new file mode 100644
index 0000000..5cb4221
--- /dev/null
+++ b/src/uqm/dummy.h
@@ -0,0 +1,52 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DUMMY_H
+#define DUMMY_H
+
+#include "races.h"
+
+#include "ships/androsyn/androsyn.h"
+#include "ships/arilou/arilou.h"
+#include "ships/blackurq/blackurq.h"
+#include "ships/chenjesu/chenjesu.h"
+#include "ships/chmmr/chmmr.h"
+#include "ships/druuge/druuge.h"
+#include "ships/human/human.h"
+#include "ships/ilwrath/ilwrath.h"
+#include "ships/melnorme/melnorme.h"
+#include "ships/mmrnmhrm/mmrnmhrm.h"
+#include "ships/mycon/mycon.h"
+#include "ships/orz/orz.h"
+#include "ships/pkunk/pkunk.h"
+#include "ships/shofixti/shofixti.h"
+#include "ships/slylandr/slylandr.h"
+#include "ships/spathi/spathi.h"
+#include "ships/supox/supox.h"
+#include "ships/syreen/syreen.h"
+#include "ships/thradd/thradd.h"
+#include "ships/umgah/umgah.h"
+#include "ships/urquan/urquan.h"
+#include "ships/utwig/utwig.h"
+#include "ships/vux/vux.h"
+#include "ships/yehat/yehat.h"
+#include "ships/zoqfot/zoqfot.h"
+#include "ships/lastbat/lastbat.h"
+#include "ships/sis_ship/sis_ship.h"
+#include "ships/probe/probe.h"
+
+#endif /* DUMMY_H */
+
diff --git a/src/uqm/element.h b/src/uqm/element.h
new file mode 100644
index 0000000..dd41340
--- /dev/null
+++ b/src/uqm/element.h
@@ -0,0 +1,242 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_ELEMENT_H_
+#define UQM_ELEMENT_H_
+
+#include "displist.h"
+#include "units.h"
+#include "velocity.h"
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+#define NORMAL_LIFE 1
+
+typedef HLINK HELEMENT;
+
+// Bits for ELEMENT_FLAGS:
+// bits 0 and 1 are now available
+#define PLAYER_SHIP (1 << 2)
+ // The ELEMENT is a player controlable ship, and not some bullet,
+ // crew, asteroid, fighter, etc. This does not mean that the ship
+ // is actually controlled by a human; it may be a computer.
+
+#define APPEARING (1 << 3)
+#define DISAPPEARING (1 << 4)
+#define CHANGING (1 << 5)
+ // The element's graphical representation has changed.
+
+#define NONSOLID (1 << 6)
+#define COLLISION (1 << 7)
+#define IGNORE_SIMILAR (1 << 8)
+#define DEFY_PHYSICS (1 << 9)
+
+#define FINITE_LIFE (1 << 10)
+
+#define PRE_PROCESS (1 << 11)
+ // PreProcess() is to be called for the ELEMENT.
+#define POST_PROCESS (1 << 12)
+
+#define IGNORE_VELOCITY (1 << 13)
+#define CREW_OBJECT (1 << 14)
+#define BACKGROUND_OBJECT (1 << 15)
+ // The BACKGROUND_OBJECT flag existed originally but wasn't used.
+ // It can now be used for objects that never influence the state
+ // of other elements; elements that have this flag set are not
+ // included in the checksum used for netplay games.
+ // It can be used for graphical mods that don't impede netplay.
+
+
+#define HYPERJUMP_LIFE 15
+
+#define NUM_EXPLOSION_FRAMES 12
+
+#define GAME_SOUND_PRIORITY 2
+
+typedef enum
+{
+ VIEW_STABLE,
+ VIEW_SCROLL,
+ VIEW_CHANGE
+} VIEW_STATE;
+
+typedef UWORD ELEMENT_FLAGS;
+
+#define NO_PRIM NUM_PRIMS
+
+typedef struct state
+{
+ POINT location;
+ struct
+ {
+ FRAME frame;
+ FRAME *farray;
+ } image;
+} STATE;
+
+
+typedef struct element ELEMENT;
+
+typedef void (ElementProcessFunc) (ELEMENT *ElementPtr);
+typedef void (ElementCollisionFunc) (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1);
+
+// Any physical object in the simulation.
+struct element
+{
+ // LINK elements; must be first
+ HELEMENT pred, succ;
+
+ ElementProcessFunc *preprocess_func;
+ ElementProcessFunc *postprocess_func;
+ ElementCollisionFunc *collision_func;
+ ElementProcessFunc *death_func;
+
+ // Player this element belongs to
+ // -1: neutral (planets, asteroids, crew, etc.)
+ // 0: Melee: bottom player; Full-game: the human player
+ // 1: Melee: top player; Full-game: the NPC opponent
+ SIZE playerNr;
+
+ ELEMENT_FLAGS state_flags;
+ union
+ {
+ COUNT life_span;
+ COUNT scan_node; /* Planetside: scan type and node id */
+ };
+ union
+ {
+ COUNT crew_level;
+ COUNT hit_points;
+ COUNT facing; /* Planetside: lava-spot direction of travel */
+ COUNT cycle;
+ /* Planetside: lightning cycle length */
+ };
+ union
+ {
+ BYTE mass_points;
+ /* Planetside:
+ * - for living bio: Index in CreatureData, possibly OR'ed
+ * with CREATURE_AWARE
+ * - for canned bio: value of creature
+ */
+ // TODO: Use a different name for Planetside bio, like
+ // BYTE bio_state;
+ };
+ union
+ {
+ BYTE turn_wait;
+ BYTE sys_loc; /* IP flagship: location in system */
+ };
+ union
+ {
+ BYTE thrust_wait;
+ BYTE blast_offset;
+ BYTE next_turn; /* Battle: animation interframe for some elements */
+ };
+ BYTE colorCycleIndex;
+ // Melee: used to cycle ion trails and warp shadows, and
+ // to cycle the ship color when fleeing.
+
+ VELOCITY_DESC velocity;
+ INTERSECT_CONTROL IntersectControl;
+ COUNT PrimIndex;
+ STATE current, next;
+
+ void *pParent;
+ // The ship this element belongs to.
+ HELEMENT hTarget;
+};
+
+#define NEUTRAL_PLAYER_NUM -1
+
+static inline BOOLEAN
+elementsOfSamePlayer (ELEMENT *ElementPtr0, ELEMENT *ElementPtr1)
+{
+ return ElementPtr0->playerNr == ElementPtr1->playerNr;
+}
+
+extern QUEUE disp_q;
+// The maximum number of elements is chosen to provide a slight margin.
+// Currently, it is maximum *known used* in Melee + 30
+#define MAX_DISPLAY_ELEMENTS 150
+
+#define MAX_DISPLAY_PRIMS 330
+extern COUNT DisplayFreeList;
+extern PRIMITIVE DisplayArray[MAX_DISPLAY_PRIMS];
+
+#define AllocDisplayPrim() DisplayFreeList; \
+ DisplayFreeList = GetSuccLink (GetPrimLinks (&DisplayArray[DisplayFreeList]))
+#define FreeDisplayPrim(p) SetPrimLinks (&DisplayArray[p], END_OF_LIST, DisplayFreeList); \
+ DisplayFreeList = (p)
+
+#define GetElementStarShip(e,ppsd) do { *(ppsd) = (e)->pParent; } while (0)
+#define SetElementStarShip(e,psd) do { (e)->pParent = psd; } while (0)
+
+#define MAX_CREW_SIZE 42
+#define MAX_ENERGY_SIZE 42
+#define MAX_SHIP_MASS 10
+#define GRAVITY_MASS(m) ((m) > MAX_SHIP_MASS * 10)
+#define GRAVITY_THRESHOLD (COUNT)255
+
+#define OBJECT_CLOAKED(eptr) \
+ (GetPrimType (&GLOBAL (DisplayArray[(eptr)->PrimIndex])) >= NUM_PRIMS \
+ || (GetPrimType (&GLOBAL (DisplayArray[(eptr)->PrimIndex])) == STAMPFILL_PRIM \
+ && sameColor (GetPrimColor (&GLOBAL (DisplayArray[(eptr)->PrimIndex])), BLACK_COLOR)))
+#define UNDEFINED_LEVEL 0
+
+extern HELEMENT AllocElement (void);
+extern void FreeElement (HELEMENT hElement);
+#define PutElement(h) PutQueue (&disp_q, h)
+#define InsertElement(h,i) InsertQueue (&disp_q, h, i)
+#define GetHeadElement() GetHeadLink (&disp_q)
+#define GetTailElement() GetTailLink (&disp_q)
+#define LockElement(h,ppe) (*(ppe) = (ELEMENT*)LockLink (&disp_q, h))
+#define UnlockElement(h) UnlockLink (&disp_q, h)
+#define GetPredElement(l) _GetPredLink (l)
+#define GetSuccElement(l) _GetSuccLink (l)
+extern void RemoveElement (HLINK hLink);
+
+// XXX: The following functions should not really be here
+extern void spawn_planet (void);
+extern void spawn_asteroid (ELEMENT *ElementPtr);
+extern void do_damage (ELEMENT *ElementPtr, SIZE damage);
+extern void crew_preprocess (ELEMENT *ElementPtr);
+extern void crew_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1);
+extern void AbandonShip (ELEMENT *ShipPtr, ELEMENT *TargetPtr,
+ COUNT crew_loss);
+extern BOOLEAN TimeSpaceMatterConflict (ELEMENT *ElementPtr);
+extern COUNT PlotIntercept (ELEMENT *ElementPtr0,
+ ELEMENT *ElementPtr1, COUNT max_turns, COUNT margin_of_error);
+
+extern void InitGalaxy (void);
+extern void MoveGalaxy (VIEW_STATE view_state, SIZE dx, SIZE dy);
+
+extern BOOLEAN CalculateGravity (ELEMENT *ElementPtr);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_ELEMENT_H_ */
diff --git a/src/uqm/encount.c b/src/uqm/encount.c
new file mode 100644
index 0000000..9ab75c5
--- /dev/null
+++ b/src/uqm/encount.c
@@ -0,0 +1,844 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "encount.h"
+
+#include "battle.h"
+#include "battlecontrols.h"
+#include "build.h"
+#include "colors.h"
+#include "starmap.h"
+#include "cons_res.h"
+#include "controls.h"
+#include "menustat.h"
+#include "gameopt.h"
+#include "gamestr.h"
+#include "globdata.h"
+#include "sis.h"
+ // for DrawStatusMessage(), SetStatusMessageMode()
+#include "init.h"
+#include "pickship.h"
+#include "intel.h"
+#include "nameref.h"
+#include "resinst.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/log.h"
+#include "libs/mathlib.h"
+#include "libs/inplib.h"
+#include "libs/misc.h"
+
+
+static void DrawFadeText (const UNICODE *str1, const UNICODE *str2,
+ BOOLEAN fade_in, RECT *pRect);
+
+
+static BOOLEAN
+DoSelectAction (MENU_STATE *pMS)
+{
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ pMS->CurState = ATTACK + 1;
+ return (FALSE);
+ }
+ if (!pMS->Initialized)
+ {
+ pMS->Initialized = TRUE;
+ pMS->InputFunc = DoSelectAction;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ switch (pMS->CurState)
+ {
+ case HAIL:
+ case ATTACK:
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ pMS->CurState = HAIL;
+ return (FALSE);
+ case ATTACK + 1:
+ // Clearing FlashRect is not necessary
+ if (!GameOptions ())
+ return FALSE;
+ DrawMenuStateStrings (PM_CONVERSE, pMS->CurState);
+ SetFlashRect (SFR_MENU_3DO);
+ break;
+ default:
+ printf ("Unknown option: %d\n", pMS->CurState);
+ }
+ }
+ DoMenuChooser (pMS, PM_CONVERSE);
+ return (TRUE);
+}
+
+static QUEUE *
+GetShipFragQueueForPlayer (COUNT playerNr)
+{
+ if (playerNr == RPG_PLAYER_NUM)
+ return &GLOBAL (built_ship_q);
+ else
+ return &GLOBAL (npc_built_ship_q);
+}
+
+// Called by comm code to intialize battle fleets during encounter
+void
+BuildBattle (COUNT which_player)
+{
+ QUEUE *pQueue;
+ HSHIPFRAG hStarShip, hNextShip;
+ HSTARSHIP hBuiltShip;
+ STARSHIP *BuiltShipPtr;
+
+ EncounterRace = -1;
+
+ if (GetHeadLink (&GLOBAL (npc_built_ship_q)) == 0)
+ {
+ SET_GAME_STATE (BATTLE_SEGUE, 0);
+ return;
+ }
+
+ if (which_player != RPG_PLAYER_NUM)
+ { // This function is called first for the NPC character
+ // and this is when a centerpiece is loaded
+ switch (LOBYTE (GLOBAL (CurrentActivity)))
+ {
+ case IN_LAST_BATTLE:
+ load_gravity_well (NUMBER_OF_PLANET_TYPES);
+ break;
+ case IN_HYPERSPACE:
+ load_gravity_well ((BYTE)((COUNT)TFB_Random ()
+ % NUMBER_OF_PLANET_TYPES));
+ break;
+ default:
+ SET_GAME_STATE (ESCAPE_COUNTER, 110);
+ load_gravity_well (GET_GAME_STATE (BATTLE_PLANET));
+ break;
+ }
+ }
+ pQueue = GetShipFragQueueForPlayer (which_player);
+
+ ReinitQueue (&race_q[which_player]);
+ for (hStarShip = GetHeadLink (pQueue);
+ hStarShip != 0; hStarShip = hNextShip)
+ {
+ SHIP_FRAGMENT *FragPtr;
+
+ FragPtr = LockShipFrag (pQueue, hStarShip);
+ hNextShip = _GetSuccLink (FragPtr);
+
+ hBuiltShip = Build (&race_q[which_player],
+ FragPtr->race_id == SAMATRA_SHIP ?
+ SA_MATRA_ID : FragPtr->SpeciesID);
+ if (hBuiltShip)
+ {
+ BuiltShipPtr = LockStarShip (&race_q[which_player], hBuiltShip);
+ BuiltShipPtr->captains_name_index = FragPtr->captains_name_index;
+ BuiltShipPtr->playerNr = which_player;
+ if (FragPtr->crew_level != INFINITE_FLEET)
+ BuiltShipPtr->crew_level = FragPtr->crew_level;
+ else /* if infinite ships */
+ BuiltShipPtr->crew_level = FragPtr->max_crew;
+ BuiltShipPtr->max_crew = FragPtr->max_crew;
+ BuiltShipPtr->race_strings = FragPtr->race_strings;
+ BuiltShipPtr->icons = FragPtr->icons;
+ BuiltShipPtr->index = FragPtr->index;
+ BuiltShipPtr->ship_cost = 0;
+ BuiltShipPtr->RaceDescPtr = 0;
+
+ UnlockStarShip (&race_q[which_player], hBuiltShip);
+ }
+
+ UnlockShipFrag (pQueue, hStarShip);
+ }
+
+ if (which_player == RPG_PLAYER_NUM
+ && (hBuiltShip = Build (&race_q[0], SIS_SHIP_ID)))
+ {
+ BuiltShipPtr = LockStarShip (&race_q[0], hBuiltShip);
+ BuiltShipPtr->captains_name_index = 0;
+ BuiltShipPtr->playerNr = RPG_PLAYER_NUM;
+ BuiltShipPtr->crew_level = 0;
+ BuiltShipPtr->max_crew = 0;
+ // Crew will be copied directly from
+ // GLOBAL_SIS (CrewEnlisted) later.
+ BuiltShipPtr->race_strings = 0;
+ BuiltShipPtr->icons = 0;
+ BuiltShipPtr->index = -1;
+ BuiltShipPtr->ship_cost = 0;
+ BuiltShipPtr->energy_counter = MAX_ENERGY_SIZE;
+ BuiltShipPtr->RaceDescPtr = 0;
+ UnlockStarShip (&race_q[0], hBuiltShip);
+ }
+}
+
+BOOLEAN
+FleetIsInfinite (COUNT playerNr)
+{
+ QUEUE *pQueue;
+ HSHIPFRAG hShipFrag;
+ SHIP_FRAGMENT *FragPtr;
+ BOOLEAN ret;
+
+ pQueue = GetShipFragQueueForPlayer (playerNr);
+ hShipFrag = GetHeadLink (pQueue);
+ if (!hShipFrag)
+ { // Ship queue is empty in SuperMelee or for RPG player w/o escorts
+ return FALSE;
+ }
+
+ FragPtr = LockShipFrag (pQueue, hShipFrag);
+ ret = (FragPtr->crew_level == INFINITE_FLEET);
+ UnlockShipFrag (pQueue, hShipFrag);
+
+ return ret;
+}
+
+void
+UpdateShipFragCrew (STARSHIP *StarShipPtr)
+{
+ QUEUE *frag_q;
+ HSHIPFRAG hShipFrag, hNextFrag;
+ SHIP_FRAGMENT *frag;
+ QUEUE *ship_q;
+ HSTARSHIP hStarShip, hNextShip;
+ STARSHIP *ship;
+
+ frag_q = GetShipFragQueueForPlayer (StarShipPtr->playerNr);
+ ship_q = &race_q[StarShipPtr->playerNr];
+
+ // Find a SHIP_FRAGMENT that corresponds to the given STARSHIP
+ // The ships and fragments are in the same order in two queues
+ // XXX: It would probably be simpler to keep HSHIPFRAG in STARSHIP struct
+ for (hShipFrag = GetHeadLink (frag_q), hStarShip = GetHeadLink (ship_q);
+ hShipFrag != 0 && hStarShip != 0;
+ hShipFrag = hNextFrag, hStarShip = hNextShip)
+ {
+ ship = LockStarShip (ship_q, hStarShip);
+ hNextShip = _GetSuccLink (ship);
+ frag = LockShipFrag (frag_q, hShipFrag);
+ hNextFrag = _GetSuccLink (frag);
+
+ if (ship == StarShipPtr)
+ {
+ assert (frag->crew_level != INFINITE_FLEET);
+
+ // Record crew left after the battle */
+ frag->crew_level = ship->crew_level;
+
+ UnlockShipFrag (frag_q, hShipFrag);
+ UnlockStarShip (ship_q, hStarShip);
+ break;
+ }
+
+ UnlockShipFrag (frag_q, hShipFrag);
+ UnlockStarShip (ship_q, hStarShip);
+ }
+}
+
+/*
+ * Encountering an alien.
+ * Draws the encounter screen, plays the red alert music, and
+ * waits for a decision of the player on how to handle the situation.
+ * Returns either HAIL or ATTACK.
+ */
+COUNT
+InitEncounter (void)
+{
+ COUNT i;
+ FRAME SegueFrame;
+ STAMP s;
+ TEXT t;
+ extern FRAME planet[];
+ MUSIC_REF MR;
+
+
+ SetContext (SpaceContext);
+ SetContextFont (TinyFont);
+
+ MR = LoadMusic (REDALERT_MUSIC);
+ PlayMusic (MR, FALSE, 1);
+ SegueFrame = CaptureDrawable (LoadGraphic (SEGUE_PMAP_ANIM));
+ WaitForSoundEnd (TFBSOUND_WAIT_ALL);
+ StopMusic ();
+ DestroyMusic (MR);
+ s.origin.x = s.origin.y = 0;
+
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+ s.frame = SegueFrame;
+ DrawStamp (&s);
+
+// t.baseline.x = SIS_SCREEN_WIDTH >> 1;
+ t.baseline.x = (SIS_SCREEN_WIDTH >> 1) + 1;
+ t.baseline.y = 10;
+ t.align = ALIGN_CENTER;
+
+ SetContextFont (MicroFont);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01));
+ if (inHQSpace ())
+ {
+ t.pStr = GAME_STRING (ENCOUNTER_STRING_BASE + 0);
+ // "ENCOUNTER IN"
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += 12;
+ t.pStr = GAME_STRING (ENCOUNTER_STRING_BASE + 1);
+ // "DEEP SPACE"
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ }
+ else
+ {
+ UNICODE buf[256];
+
+ t.pStr = GAME_STRING (ENCOUNTER_STRING_BASE + 2);
+ // "ENCOUNTER AT"
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += 12;
+ GetClusterName (CurStarDescPtr, buf);
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += 12;
+ t.pStr = GLOBAL_SIS (PlanetName);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ }
+ DrawSISMessage (NULL);
+
+ s.origin.x = SIS_SCREEN_WIDTH >> 1;
+ s.origin.y = SIS_SCREEN_HEIGHT >> 1;
+ s.frame = planet[0];
+ DrawStamp (&s);
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) != IN_LAST_BATTLE)
+ {
+#define NUM_DISPLAY_PTS (sizeof (display_pt) / sizeof (display_pt[0]))
+ HSHIPFRAG hStarShip, hNextShip;
+ POINT display_pt[] =
+ {
+ { 10, 51},
+ {-10, 51},
+ { 33, 40},
+ {-33, 40},
+ { 49, 18},
+ {-49, 18},
+ { 52, -6},
+ {-52, -6},
+ { 44, -27},
+ {-44, -27},
+ };
+
+ for (hStarShip = GetHeadLink (&GLOBAL (npc_built_ship_q)), i = 0;
+ hStarShip && i < 60; hStarShip = hNextShip, ++i)
+ {
+ RECT r;
+ SHIP_FRAGMENT *FragPtr;
+
+ FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ if (FragPtr->crew_level != INFINITE_FLEET)
+ hNextShip = _GetSuccLink (FragPtr);
+ else /* if infinite ships */
+ hNextShip = hStarShip;
+
+ s.origin = display_pt[i % NUM_DISPLAY_PTS];
+ if (i >= NUM_DISPLAY_PTS)
+ {
+ COUNT angle, radius;
+
+ radius = square_root ((long)s.origin.x * s.origin.x
+ + (long)s.origin.y * s.origin.y)
+ + ((i / NUM_DISPLAY_PTS) * 18);
+
+ angle = ARCTAN (s.origin.x, s.origin.y);
+ s.origin.x = COSINE (angle, radius);
+ s.origin.y = SINE (angle, radius);
+ }
+ s.frame = SetAbsFrameIndex (FragPtr->icons, 0);
+ GetFrameRect (s.frame, &r);
+ s.origin.x += (SIS_SCREEN_WIDTH >> 1) - (r.extent.width >> 1);
+ s.origin.y += (SIS_SCREEN_HEIGHT >> 1) - (r.extent.height >> 1);
+ DrawStamp (&s);
+
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ }
+ }
+
+ UnbatchGraphics ();
+ DestroyDrawable (ReleaseDrawable (SegueFrame));
+ ScreenTransition (3, NULL);
+
+
+ {
+ MENU_STATE MenuState;
+
+ MenuState.InputFunc = DoSelectAction;
+ MenuState.Initialized = FALSE;
+
+ DrawMenuStateStrings (PM_CONVERSE, MenuState.CurState = HAIL);
+ SetFlashRect (SFR_MENU_3DO);
+
+ DoInput (&MenuState, TRUE);
+
+ SetFlashRect (NULL);
+
+ return (MenuState.CurState);
+ }
+}
+
+static void
+DrawFadeText (const UNICODE *str1, const UNICODE *str2, BOOLEAN fade_in,
+ RECT *pRect)
+{
+ SIZE i;
+ DWORD TimeIn;
+ TEXT t1, t2;
+ static const Color fade_cycle[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0A, 0x0A, 0x0A), 0x1D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x09, 0x09, 0x09), 0x1E),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x08, 0x08, 0x08), 0x1F),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x06, 0x06, 0x06), 0x20),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x05, 0x05, 0x05), 0x21),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x04, 0x04, 0x04), 0x22),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x03, 0x03, 0x03), 0x23),
+ };
+#define NUM_FADES (sizeof (fade_cycle) / sizeof (fade_cycle[0]))
+
+ t1.baseline.x = pRect->corner.x + 100;
+ t1.baseline.y = pRect->corner.y + 45;
+ t1.align = ALIGN_CENTER;
+ t1.pStr = str1;
+ t1.CharCount = (COUNT)~0;
+ t2 = t1;
+ t2.baseline.y += 11;
+ t2.pStr = str2;
+
+ FlushInput ();
+ TimeIn = GetTimeCounter ();
+ if (fade_in)
+ {
+ for (i = 0; i < (SIZE) NUM_FADES; ++i)
+ {
+ if (AnyButtonPress (TRUE))
+ i = NUM_FADES - 1;
+
+ SetContextForeGroundColor (fade_cycle[i]);
+ font_DrawText (&t1);
+ font_DrawText (&t2);
+ SleepThreadUntil (TimeIn + (ONE_SECOND / 20));
+ TimeIn = GetTimeCounter ();
+ }
+ }
+ else
+ {
+ for (i = NUM_FADES - 1; i >= 0; --i)
+ {
+ if (AnyButtonPress (TRUE))
+ i = 0;
+
+ SetContextForeGroundColor (fade_cycle[i]);
+ font_DrawText (&t1);
+ font_DrawText (&t2);
+ SleepThreadUntil (TimeIn + (ONE_SECOND / 20));
+ TimeIn = GetTimeCounter ();
+ }
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ font_DrawText (&t1);
+ font_DrawText (&t2);
+ }
+}
+
+COUNT
+UninitEncounter (void)
+{
+ COUNT ships_killed;
+
+ ships_killed = 0;
+
+ free_gravity_well ();
+
+ if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0
+ || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE
+ || LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE)
+ goto ExitUninitEncounter;
+
+ if (GET_GAME_STATE (BATTLE_SEGUE) == 0)
+ {
+ ReinitQueue (&race_q[0]);
+ ReinitQueue (&race_q[1]);
+ }
+ else
+ {
+ BOOLEAN Sleepy;
+ SIZE VictoryState;
+ COUNT RecycleAmount = 0;
+ SIZE i;
+ RECT r;
+ RECT scavenge_r = {{0, 0}, {0, 0}};
+ TEXT t;
+ STAMP ship_s;
+ const UNICODE *str1 = NULL;
+ const UNICODE *str2 = NULL;
+ StatMsgMode prevMsgMode;
+ UNICODE buf[80];
+ HSHIPFRAG hStarShip;
+ SHIP_FRAGMENT *FragPtr;
+ static const Color fade_ship_cycle[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x07, 0x00, 0x00), 0x2F),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x0A), 0x27),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x14, 0x14), 0x25),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1F, 0x1F), 0x0F),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x14, 0x14), 0x25),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x0A), 0x27),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ };
+#define NUM_SHIP_FADES (sizeof (fade_ship_cycle) / \
+ sizeof (fade_ship_cycle[0]))
+
+ COUNT race_bounty[] =
+ {
+ RACE_SHIP_COST
+ };
+
+ SET_GAME_STATE (BATTLE_SEGUE, 0);
+ SET_GAME_STATE (BOMB_CARRIER, 0);
+
+ VictoryState = (
+ battle_counter[1] || !battle_counter[0]
+ || GET_GAME_STATE (URQUAN_PROTECTING_SAMATRA)
+ ) ? 0 : 1;
+
+ hStarShip = GetHeadLink (&GLOBAL (npc_built_ship_q));
+ FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ EncounterRace = FragPtr->race_id;
+ if (GetStarShipFromIndex (&GLOBAL (avail_race_q), EncounterRace) == 0)
+ {
+ /* Suppress the final tally and salvage info */
+ VictoryState = -1;
+ InitSISContexts ();
+ }
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+
+ prevMsgMode = SetStatusMessageMode (SMM_RES_UNITS);
+ ship_s.origin.x = 0;
+ ship_s.origin.y = 0;
+ Sleepy = TRUE;
+ for (i = 0; i < NUM_SIDES; ++i)
+ {
+ QUEUE *pQueue;
+ HSHIPFRAG hNextShip;
+
+ if (i == 0)
+ pQueue = &GLOBAL (built_ship_q);
+ else
+ {
+ if (VictoryState < 0)
+ VictoryState = 0;
+ else
+ {
+ DrawSISFrame ();
+ DrawSISMessage (NULL);
+ if (inHQSpace ())
+ DrawHyperCoords (GLOBAL (ShipStamp.origin));
+ else if (GLOBAL (ip_planet) == 0)
+ DrawHyperCoords (CurStarDescPtr->star_pt);
+ else
+ DrawSISTitle(GLOBAL_SIS (PlanetName));
+
+ SetContext (SpaceContext);
+ if (VictoryState)
+ DrawArmadaPickShip (TRUE, &scavenge_r);
+ }
+ pQueue = &GLOBAL (npc_built_ship_q);
+ }
+
+ ReinitQueue (&race_q[(NUM_SIDES - 1) - i]);
+
+ for (hStarShip = GetHeadLink (pQueue); hStarShip;
+ hStarShip = hNextShip)
+ {
+ FragPtr = LockShipFrag (pQueue, hStarShip);
+ hNextShip = _GetSuccLink (FragPtr);
+
+ if (FragPtr->crew_level == 0
+ || (VictoryState && i == NUM_SIDES - 1))
+ {
+ if (i == NUM_SIDES - 1)
+ {
+ ++ships_killed;
+ if (VictoryState)
+ {
+#define MAX_DEAD_DISPLAYED 5
+ COUNT j;
+
+ if (ships_killed == 1)
+ {
+ RecycleAmount = 0;
+
+ DrawStatusMessage (NULL);
+
+ ship_s.origin.x = scavenge_r.corner.x + 32;
+ ship_s.origin.y = scavenge_r.corner.y + 56;
+ ship_s.frame = IncFrameIndex (FragPtr->icons);
+ DrawStamp (&ship_s);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F));
+ SetContextFont (TinyFont);
+
+ utf8StringCopy (buf, sizeof buf,
+ GetStringAddress (FragPtr->race_strings));
+ // XXX: this will not work with UTF-8 strings
+ strupr (buf);
+
+ t.baseline.x = scavenge_r.corner.x + 100;
+ t.baseline.y = scavenge_r.corner.y + 68;
+ t.align = ALIGN_CENTER;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += 6;
+ t.pStr = GAME_STRING (
+ ENCOUNTER_STRING_BASE + 3);
+ // "BATTLE GROUP"
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ ship_s.frame = FragPtr->icons;
+
+ SetContextFont (MicroFont);
+ str1 = GAME_STRING (
+ ENCOUNTER_STRING_BASE + 4);
+ // "Enemy Ships"
+ str2 = GAME_STRING (
+ ENCOUNTER_STRING_BASE + 5),
+ // "Destroyed"
+ DrawFadeText (str1, str2, TRUE, &scavenge_r);
+ }
+
+ r.corner.y = scavenge_r.corner.y + 9;
+ r.extent.height = 22;
+
+ SetContextForeGroundColor (BLACK_COLOR);
+
+ r.extent.width = 34;
+ r.corner.x = scavenge_r.corner.x +
+ scavenge_r.extent.width
+ - (10 + r.extent.width);
+ DrawFilledRectangle (&r);
+
+ /* collect bounty ResUnits */
+ j = race_bounty[EncounterRace] >> 3;
+ RecycleAmount += j;
+ sprintf (buf, "%u", RecycleAmount);
+ t.baseline.x = r.corner.x + r.extent.width - 1;
+ t.baseline.y = r.corner.y + 14;
+ t.align = ALIGN_RIGHT;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x18), 0x50));
+ font_DrawText (&t);
+ DeltaSISGauges (0, 0, j);
+
+ if ((VictoryState++ - 1) % MAX_DEAD_DISPLAYED)
+ ship_s.origin.x += 17;
+ else
+ {
+ SetContextForeGroundColor (BLACK_COLOR);
+
+ r.corner.x = scavenge_r.corner.x + 10;
+ r.extent.width = 104;
+ DrawFilledRectangle (&r);
+
+ ship_s.origin.x = r.corner.x + 2;
+ ship_s.origin.y = scavenge_r.corner.y + 12;
+ }
+
+ if (Sleepy)
+ {
+ TimeCount Time = GetTimeCounter ();
+ for (j = 0; j < NUM_SHIP_FADES; ++j)
+ {
+ Sleepy = (BOOLEAN)!AnyButtonPress (TRUE) &&
+ !(GLOBAL (CurrentActivity) & CHECK_ABORT);
+ if (!Sleepy)
+ break;
+
+ SetContextForeGroundColor (fade_ship_cycle[j]);
+ DrawFilledStamp (&ship_s);
+
+ SleepThreadUntil (Time + (ONE_SECOND / 15));
+ Time = GetTimeCounter ();
+ }
+ }
+ DrawStamp (&ship_s);
+ }
+ }
+
+ UnlockShipFrag (pQueue, hStarShip);
+ RemoveQueue (pQueue, hStarShip);
+ FreeShipFrag (pQueue, hStarShip);
+
+ continue;
+ }
+
+ UnlockShipFrag (pQueue, hStarShip);
+ }
+ }
+ SetStatusMessageMode (prevMsgMode);
+
+ if (VictoryState)
+ {
+#ifdef NEVER
+ DestroyDrawable (ReleaseDrawable (s.frame));
+#endif /* NEVER */
+
+ WaitForAnyButton (TRUE, ONE_SECOND * 3, FALSE);
+ if (!CurrentInputState.key[PlayerControls[0]][KEY_ESCAPE])
+ {
+ DrawFadeText (str1, str2, FALSE, &scavenge_r);
+ if (!CurrentInputState.key[PlayerControls[0]][KEY_ESCAPE])
+ {
+ SetContextForeGroundColor (BLACK_COLOR);
+ r.corner.x = scavenge_r.corner.x + 10;
+ r.extent.width = 132;
+ DrawFilledRectangle (&r);
+ sprintf (buf, "%u %s", RecycleAmount,
+ GAME_STRING (STATUS_STRING_BASE + 1)); // "RU"
+ t.baseline.x = r.corner.x + (r.extent.width >> 1);
+ t.baseline.y = r.corner.y + 14;
+ t.align = ALIGN_CENTER;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x18), 0x50));
+ font_DrawText (&t);
+
+ str1 = GAME_STRING (ENCOUNTER_STRING_BASE + 6);
+ // "Debris"
+ str2 = GAME_STRING (ENCOUNTER_STRING_BASE + 7);
+ // "Scavenged"
+ DrawFadeText (str1, str2, TRUE, &scavenge_r);
+ WaitForAnyButton (TRUE, ONE_SECOND * 2, FALSE);
+ if (!CurrentInputState.key[PlayerControls[0]][KEY_ESCAPE])
+ DrawFadeText (str1, str2, FALSE, &scavenge_r);
+ }
+ }
+
+ DrawStatusMessage (NULL);
+ }
+
+ if (ships_killed && EncounterRace == THRADDASH_SHIP
+ && !GET_GAME_STATE (THRADD_MANNER))
+ {
+ if ((ships_killed += GET_GAME_STATE (THRADDASH_BODY_COUNT)) >
+ THRADDASH_BODY_THRESHOLD)
+ ships_killed = THRADDASH_BODY_THRESHOLD;
+ SET_GAME_STATE (THRADDASH_BODY_COUNT, ships_killed);
+ }
+ }
+ExitUninitEncounter:
+
+ return (ships_killed);
+}
+
+void
+EncounterBattle (void)
+{
+ ACTIVITY OldActivity;
+ extern UWORD nth_frame;
+ InputContext *savedPlayerInput = NULL;
+
+
+ SET_GAME_STATE (BATTLE_SEGUE, 1);
+
+ OldActivity = GLOBAL (CurrentActivity);
+ if (LOBYTE (OldActivity) == IN_LAST_BATTLE)
+ GLOBAL (CurrentActivity) = MAKE_WORD (IN_LAST_BATTLE, 0);
+ else
+ GLOBAL (CurrentActivity) = MAKE_WORD (IN_ENCOUNTER, 0);
+
+// FreeSC2Data ();
+// DestroyFont (ReleaseFont (MicroFont));
+ WaitForSoundEnd (TFBSOUND_WAIT_ALL);
+// DestroySound (ReleaseSound (MenuSounds));
+
+ if (GLOBAL (glob_flags) & CYBORG_ENABLED)
+ {
+ BYTE cur_speed;
+
+ cur_speed = (BYTE)(GLOBAL (glob_flags) & COMBAT_SPEED_MASK)
+ >> COMBAT_SPEED_SHIFT;
+ if (cur_speed == 1)
+ cur_speed = 0; /* normal speed */
+ else if (cur_speed == 2)
+ ++cur_speed; /* 4x speed, 3 of 4 frames skipped */
+ else /* if (cur_speed == 3) */
+ cur_speed = (BYTE)~0; /* maximum speed - no rendering */
+ nth_frame = MAKE_WORD (1, cur_speed);
+ PlayerControl[0] = CYBORG_CONTROL | AWESOME_RATING;
+ savedPlayerInput = PlayerInput[0];
+ PlayerInput[0] = NULL;
+ if (!SetPlayerInput (0)) {
+ log_add (log_Fatal, "Could not set cyborg player input.");
+ explode (); // Does not return;
+ }
+ }
+
+ GameSounds = CaptureSound (LoadSound (GAME_SOUNDS));
+
+ Battle (NULL);
+
+ DestroySound (ReleaseSound (GameSounds));
+ GameSounds = 0;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ GLOBAL_SIS (CrewEnlisted) = (COUNT)~0;
+
+ if (GLOBAL (glob_flags) & CYBORG_ENABLED)
+ {
+ nth_frame = MAKE_WORD (0, 0);
+ PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING;
+ ClearPlayerInput (0);
+ PlayerInput[0] = savedPlayerInput;
+ }
+
+// MicroFont = CaptureFont (
+// LoadFont (MICRO_FONT)
+// );
+// MenuSounds = CaptureSound (LoadSound (MENU_SOUNDS));
+// LoadSC2Data ();
+
+ GLOBAL (CurrentActivity) = OldActivity;
+
+}
+
diff --git a/src/uqm/encount.h b/src/uqm/encount.h
new file mode 100644
index 0000000..b623a41
--- /dev/null
+++ b/src/uqm/encount.h
@@ -0,0 +1,119 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_ENCOUNT_H_
+#define UQM_ENCOUNT_H_
+
+typedef struct brief_ship_info BRIEF_SHIP_INFO;
+typedef struct encounter ENCOUNTER;
+
+// XXX: temporary, for CONVERSATION
+#include "commglue.h"
+#include "displist.h"
+#include "libs/gfxlib.h"
+#include "planets/planets.h"
+#include "element.h"
+#include "races.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+typedef HLINK HENCOUNTER;
+
+#define MAX_HYPER_SHIPS 7
+
+// ENCOUNTER.flags
+// XXX: Currently, the flags are combined with num_ships into a single BYTE
+// in the savegames: num_ships occupy the low nibble and flags the high one.
+// Bits 4 and 5 are available for more flags in the savegames,
+// and bits 0-3 available in the game but will not be saved.
+#define ONE_SHOT_ENCOUNTER (1 << 7)
+#define ENCOUNTER_REFORMING (1 << 6)
+#define ENCOUNTER_SHIPS_MASK 0x0f
+#define ENCOUNTER_FLAGS_MASK 0xf0
+
+struct brief_ship_info
+{
+ // The only field actually used right now is crew_level
+ BYTE race_id;
+ COUNT crew_level;
+ COUNT max_crew;
+ BYTE max_energy;
+
+};
+
+struct encounter
+{
+ // LINK elements; must be first
+ HENCOUNTER pred, succ;
+
+ HELEMENT hElement;
+
+ SIZE transition_state;
+ POINT origin;
+ COUNT radius;
+ BYTE race_id;
+ BYTE num_ships;
+ BYTE flags;
+ // See ENCOUNTER.flags above
+ POINT loc_pt;
+
+ BRIEF_SHIP_INFO ShipList[MAX_HYPER_SHIPS];
+ // Only the crew_level member is currently used
+
+ SDWORD log_x, log_y;
+};
+
+#define AllocEncounter() AllocLink (&GLOBAL (encounter_q))
+#define PutEncounter(h) PutQueue (&GLOBAL (encounter_q), h)
+#define InsertEncounter(h,i) InsertQueue (&GLOBAL (encounter_q), h, i)
+#define GetHeadEncounter() GetHeadLink (&GLOBAL (encounter_q))
+#define GetTailEncounter() GetTailLink (&GLOBAL (encounter_q))
+#define LockEncounter(h,ppe) (*(ppe) = (ENCOUNTER*)LockLink (&GLOBAL (encounter_q), h))
+#define UnlockEncounter(h) UnlockLink (&GLOBAL (encounter_q), h)
+#define RemoveEncounter(h) RemoveQueue (&GLOBAL (encounter_q), h)
+#define FreeEncounter(h) FreeLink (&GLOBAL (encounter_q), h)
+#define GetPredEncounter(l) _GetPredLink (l)
+#define GetSuccEncounter(l) _GetSuccLink (l)
+
+enum
+{
+ HAIL = 0,
+ ATTACK
+};
+
+extern void EncounterBattle (void);
+extern void BuildBattle (COUNT which_player);
+extern COUNT InitEncounter (void);
+extern COUNT UninitEncounter (void);
+extern BOOLEAN FleetIsInfinite (COUNT playerNr);
+extern void UpdateShipFragCrew (STARSHIP *);
+
+// Last race the player battled with, or -1 if no battle took place.
+// Set to -1 by some funcs to inhibit IP groups from intercepting
+// the flagship.
+extern SIZE EncounterRace;
+extern BYTE EncounterGroup;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_ENCOUNT_H_ */
diff --git a/src/uqm/flash.c b/src/uqm/flash.c
new file mode 100644
index 0000000..2c73ed0
--- /dev/null
+++ b/src/uqm/flash.c
@@ -0,0 +1,805 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// NOTE: A lot of this code is untested. Only highlite and overlay flash
+// areas, drawing directly to the screen, using a cache, are
+// currently in use.
+
+// NOTE:
+// - If you change the properties of the original CONTEXT, specifically the
+// dimensions and origin, you'll need to call Flash_preUpdate() before and
+// Flash_postUpdate() after that change. Note that this may change which
+// part of the screen is flashing.
+
+// TODO:
+// - During a few frames during the sequence, the frame to be displayed
+// is equal to a frame which was supplied as a parameter to the flash
+// sequence (instead of generated during the sequence). It is not
+// necessary to make a copy in this case. Instead, the original can be
+// used. I had code to do this, but this doesn't work anymore with the
+// addition of startNumer, endNumer, and denom. This code is still
+// present, but disabled with BEGIN_AND_END_FRAME_EXCEPTIONS.
+
+#define FLASH_INTERNAL
+#include "flash.h"
+
+#include "libs/log.h"
+#include "libs/memlib.h"
+#include "libs/threadlib.h"
+
+
+static FlashContext *Flash_create (CONTEXT gfxContext);
+static void Flash_fixState (FlashContext *context);
+static void Flash_nextState (FlashContext *context);
+static void Flash_clearCache (FlashContext *context);
+static void Flash_initCache (FlashContext *context);
+static void Flash_grabOriginal (FlashContext *context);
+static void Flash_blendFraction (FlashContext *context, int numer, int denom,
+ int *resNumer, int *resDenom);
+static void Flash_makeFrame (FlashContext *context,
+ FRAME dest, int numer, int denom);
+static inline void Flash_prepareCacheFrame (FlashContext *context,
+ COUNT index);
+static void Flash_drawFrame (FlashContext *context, FRAME frame);
+static void Flash_drawCacheFrame (FlashContext *context, COUNT index);
+static inline void Flash_drawUncachedFrame (FlashContext *context,
+ int numer, int denom);
+static inline void Flash_drawCachedFrame (FlashContext *context,
+ int numer, int denom);
+static void Flash_drawCurrentFrame (FlashContext *context);
+
+static CONTEXT workGfxContext;
+ // Off-screen internal drawing context
+
+static FlashContext *
+Flash_create (CONTEXT gfxContext)
+{
+ FlashContext *context = HMalloc (sizeof (FlashContext));
+
+ context->gfxContext = gfxContext;
+
+ context->original = 0;
+
+ context->startNumer = 0;
+ context->endNumer = 1;
+ context->denom = 1;
+
+ context->fadeInTime = Flash_DEFAULT_FADE_IN_TIME;
+ context->onTime = Flash_DEFAULT_ON_TIME;
+ context->fadeOutTime = Flash_DEFAULT_FADE_OUT_TIME;
+ context->offTime = Flash_DEFAULT_OFF_TIME;
+
+ context->frameTime = 0;
+
+ context->state = FlashState_off;
+ context->lastStateTime = 0;
+ context->lastFrameTime = 0;
+
+ context->started = false;
+ context->paused = false;
+
+ context->cache = 0;
+ context->cacheSize = Flash_DEFAULT_CACHE_SIZE;
+
+ context->lastFrameIndex = (COUNT) -1;
+
+ // TODO: Delete the context somewhere
+ if (!workGfxContext)
+ workGfxContext = CreateContext ("Flash.workGfxContext");
+
+ return context;
+}
+
+// 'startNumer / denom' is the brightness in the start state of the sequence.
+// 'endNumer / denom' is the brightness in the end state of the sequence.
+// These numbers are relative to the brighness of the original image.
+FlashContext *
+Flash_createHighlight (CONTEXT gfxContext, const RECT *rect)
+{
+ FlashContext *context = Flash_create (gfxContext);
+
+ if (rect == NULL)
+ {
+ // No rectangle specified. It should be specified later with
+ // Flash_setRect(), before calling Flash_start().
+ context->rect.corner.x = 0;
+ context->rect.corner.y = 0;
+ context->rect.extent.width = 0;
+ context->rect.extent.height = 0;
+ }
+ else
+ context->rect = *rect;
+ context->type = FlashType_highlight;
+
+ return context;
+}
+
+FlashContext *
+Flash_createTransition (CONTEXT gfxContext, const POINT *origin,
+ FRAME first, FRAME final)
+{
+ FlashContext *context = Flash_create (gfxContext);
+
+ context->type = FlashType_transition;
+
+ context->u.transition.first = first;
+ context->u.transition.final = final;
+ GetFrameRect (final, &context->rect);
+ context->rect.corner = *origin;
+
+ return context;
+}
+
+FlashContext *
+Flash_createOverlay (CONTEXT gfxContext, const POINT *origin, FRAME overlay)
+{
+ FlashContext *context = Flash_create (gfxContext);
+
+ context->type = FlashType_overlay;
+
+ if (origin == NULL || overlay == NULL) {
+ // No overlay specified. It should be specified later with
+ // Flash_setOverlay(), before calling Flash_start().
+ context->u.overlay.frame = NULL;
+ context->rect.corner.x = 0;
+ context->rect.corner.y = 0;
+ context->rect.extent.width = 0;
+ context->rect.extent.height = 0;
+ } else
+ Flash_setOverlay (context, origin, overlay);
+
+ return context;
+}
+
+// Set the current state. 'timeSpentInState' determines how much time should
+// be considered to be already spent in this state.
+void
+Flash_setState (FlashContext *context, FlashState state,
+ TimeCount timeSpentInState)
+{
+ TimeCount now;
+
+ now = GetTimeCounter ();
+
+ context->state = state;
+ Flash_fixState (context);
+
+ context->lastStateTime = now - timeSpentInState;
+ context->lastFrameTime = now;
+
+ if (context->started)
+ Flash_drawCurrentFrame (context);
+}
+
+void
+Flash_start (FlashContext *context)
+{
+ if (context->started)
+ {
+ log_add (log_Warning, "Flash_start() called on already started "
+ "FlashContext.\n");
+ return;
+ }
+
+ Flash_initCache (context);
+
+ context->started = true;
+ context->paused = false;
+
+ Flash_grabOriginal (context);
+ context->lastFrameIndex = 0;
+
+ Flash_fixState (context);
+ Flash_drawCurrentFrame (context);
+}
+
+void
+Flash_terminate (FlashContext *context)
+{
+ if (context->started)
+ {
+ // Restore the flash rectangle:
+ Flash_drawFrame (context, context->original);
+
+ Flash_clearCache (context);
+ HFree (context->cache);
+ DestroyDrawable (ReleaseDrawable (context->original));
+ }
+
+ HFree (context);
+}
+
+void
+Flash_pause (FlashContext *context)
+{
+ context->paused = true;
+}
+
+void
+Flash_continue (FlashContext *context)
+{
+ context->paused = false;
+}
+
+// Change the state to the next state as long as the current state has
+// a zero-time duration.
+static void
+Flash_fixState (FlashContext *context)
+{
+ TimeCount stateTime = 0;
+
+ for (;;) {
+ switch (context->state) {
+ case FlashState_fadeIn:
+ stateTime = context->fadeInTime;
+ break;
+ case FlashState_on:
+ stateTime = context->onTime;
+ break;
+ case FlashState_fadeOut:
+ stateTime = context->fadeOutTime;
+ break;
+ case FlashState_off:
+ stateTime = context->offTime;
+ break;
+ }
+ if (stateTime != 0)
+ break;
+ context->state = (context->state + 1) & 0x3;
+ }
+}
+
+static void
+Flash_nextState (FlashContext *context)
+{
+ context->state = (context->state + 1) & 0x3;
+ Flash_fixState (context);
+}
+
+void
+Flash_process (FlashContext *context)
+{
+ TimeCount now;
+
+ if (!context->started || context->paused)
+ return;
+
+ now = GetTimeCounter ();
+
+ switch (context->state)
+ {
+ case FlashState_fadeIn:
+ if (now >= context->lastStateTime + context->fadeInTime)
+ {
+ Flash_nextState (context);
+ context->lastStateTime = now;
+ }
+ context->lastFrameTime = now;
+ break;
+ case FlashState_on:
+ if (now < context->lastStateTime + context->onTime)
+ return;
+ Flash_nextState (context);
+ context->lastStateTime = now;
+ break;
+ case FlashState_fadeOut:
+ if (now >= context->lastStateTime + context->fadeOutTime)
+ {
+ Flash_nextState (context);
+ context->lastStateTime = now;
+ }
+ context->lastFrameTime = now;
+ break;
+ case FlashState_off:
+ if (now < context->lastStateTime + context->offTime)
+ return;
+ Flash_nextState (context);
+ context->lastStateTime = now;
+ break;
+ }
+
+ Flash_drawCurrentFrame (context);
+}
+
+void
+Flash_setSpeed (FlashContext *context, TimeCount fadeInTime,
+ TimeCount onTime, TimeCount fadeOutTime, TimeCount offTime)
+{
+ context->fadeInTime = fadeInTime;
+ context->onTime = onTime;
+ context->fadeOutTime = fadeOutTime;
+ context->offTime = offTime;
+}
+
+// Determines how the brightness of the flashing changes.
+// For highlights:
+// 'startNumer / denom' is the brightness, at the start state of the flash.
+// 'endNumer / denom' is the brightness, at the end state of the flash.
+// For overlays:
+// 'startNumer / denom' is the brightness of the image to overlay, at
+// the start state of the flash.
+// 'endNumer / denom' is the brightness of the image to overlay, at
+// the end state of the flash.
+// For transitions:
+// 'startNumer / denom' is the brightness of the second image, at
+// the start state of the flash; '1 - startNumer / denom' is the
+// brightness of the first image at the start state of the flash.
+// 'endNumer / denom' is the brightness of the second image, at
+// the end state of the flash; '1 - endNumer / denom' is the
+// brightness of the first image at the end state of the flash.
+// These numbers are relative to the brighness of each original image.
+void
+Flash_setMergeFactors(FlashContext *context, int startNumer, int endNumer,
+ int denom) {
+ if (context->started)
+ {
+ Flash_drawFrame (context, context->original);
+ Flash_clearCache (context);
+ }
+
+ context->startNumer = startNumer;
+ context->endNumer = endNumer;
+ context->denom = denom;
+
+ if (context->started)
+ {
+ Flash_grabOriginal (context);
+ Flash_drawCurrentFrame (context);
+ }
+}
+
+// Set the time between updates of the flash area.
+void
+Flash_setFrameTime (FlashContext *context, TimeCount frameTime)
+{
+ context->frameTime = frameTime;
+}
+
+// Returns the time when the flash area is to be updated.
+TimeCount
+Flash_nextTime (FlashContext *context)
+{
+ if (!context->started || context->paused)
+ return (TimeCount) -1;
+
+ if (context->state == FlashState_fadeIn ||
+ context->state == FlashState_fadeOut)
+ {
+ // When we're fading in or out, we need updates during
+ // the fade.
+ return context->lastFrameTime + context->frameTime;
+ }
+ else
+ {
+ // When the flash area is completely on or off, we don't
+ // need an update until we're ready to change state again.
+ if (context->state == FlashState_on)
+ return context->lastStateTime + context->onTime;
+ else /* context->state == FlashState_off */
+ return context->lastStateTime + context->offTime;
+ }
+}
+
+static void
+Flash_clearCache (FlashContext *context)
+{
+ COUNT i;
+
+#ifdef BEGIN_AND_END_FRAME_EXCEPTIONS
+ if (context->type == FlashType_transition ||
+ context->type == FlashType_overlay)
+ {
+ // First frame is not allocated by the flash code, so
+ // we shouldn't free it.
+ context->cache[0] = (FRAME) 0;
+ }
+ if (context->type == FlashType_transition)
+ {
+ // Final frame is not allocated by the flash code, so
+ // we shouldn't free it.
+ context->cache[context->cacheSize - 1] = (FRAME) 0;
+ }
+#endif /* BEGIN_AND_END_FRMAE_EXCEPTIONS */
+
+ for (i = 0; i < context->cacheSize; i++)
+ {
+ if (context->cache[i] != (FRAME) 0)
+ {
+ DestroyDrawable (ReleaseDrawable (context->cache[i]));
+ context->cache[i] = (FRAME) 0;
+ }
+ }
+}
+
+void
+Flash_setRect (FlashContext *context, const RECT *rect)
+{
+ assert(context->type == FlashType_highlight);
+
+ if (context->started)
+ {
+ Flash_drawFrame (context, context->original);
+ Flash_clearCache (context);
+ }
+
+ context->rect = *rect;
+ context->lastFrameIndex = (COUNT) -1;
+
+ if (context->started)
+ {
+ Flash_grabOriginal (context);
+ Flash_drawCurrentFrame (context);
+ }
+}
+
+void
+Flash_getRect (FlashContext *context, RECT *rect)
+{
+ assert (!context->type == FlashType_highlight);
+
+ *rect = context->rect;
+}
+
+void
+Flash_setOverlay (FlashContext *context, const POINT *origin, FRAME overlay)
+{
+ assert(context->type == FlashType_overlay);
+
+ if (context->started)
+ {
+ Flash_drawFrame (context, context->original);
+ Flash_clearCache (context);
+ }
+
+ context->u.overlay.frame = overlay;
+ GetFrameRect (overlay, &context->rect);
+ context->rect.corner.x += origin->x;
+ context->rect.corner.y += origin->y;
+
+ if (context->started)
+ {
+ Flash_grabOriginal (context);
+ Flash_drawCurrentFrame (context);
+ }
+}
+
+// Call before you update the graphics in the currently flashing area,
+// or before you change the dimensions or origin of the graphics context.
+void
+Flash_preUpdate (FlashContext *context)
+{
+ if (context->started)
+ {
+ Flash_drawFrame (context, context->original);
+ Flash_clearCache (context);
+ }
+}
+
+// Call after you update the graphics in the currently flashing area,
+// or after you change the dimensions or origin of the graphics context.
+void
+Flash_postUpdate (FlashContext *context)
+{
+ if (context->started)
+ {
+ Flash_grabOriginal (context);
+ Flash_drawCurrentFrame (context);
+ }
+}
+
+// Pre: context->original has been initialised.
+static void
+Flash_initCache (FlashContext *context)
+{
+ COUNT i;
+
+ context->cache = HMalloc (context->cacheSize * sizeof (FRAME));
+ for (i = 0; i < context->cacheSize; i++)
+ context->cache[i] = (FRAME) 0;
+}
+
+void
+Flash_setCacheSize (FlashContext *context, COUNT size)
+{
+ assert (size == 0 || size >= 2);
+
+ if (context->cache != NULL)
+ {
+ Flash_clearCache (context);
+ HFree (context->cache);
+ context->cache = NULL;
+ }
+
+ context->cacheSize = size;
+
+ if (size != 0)
+ Flash_initCache (context);
+}
+
+COUNT
+Flash_getCacheSize (const FlashContext *context)
+{
+ return context->cacheSize;
+}
+
+static void
+Flash_grabOriginal (FlashContext *context)
+{
+ CONTEXT oldGfxContext;
+
+ if (context->original != (FRAME) 0)
+ DestroyDrawable (ReleaseDrawable (context->original));
+
+ oldGfxContext = SetContext (context->gfxContext);
+ context->original = CaptureDrawable (CopyContextRect (&context->rect));
+ SetContext (oldGfxContext);
+ FlushGraphics ();
+ // CopyContextRect() may have queued the command to read
+ // a rectangle from the screen; a FlushGraphics()
+ // is necessary to ensure that it can actually be used.
+}
+
+static inline void
+Flash_blendFraction (FlashContext *context, int numer, int denom,
+ int *resNumer, int *resDenom)
+{
+ // This function merges two fractions (F0 and F1),
+ // based on another fraction (P) (yielding R).
+ // F0 = context->u.highlight.startNumer / context->u.highlight.denom
+ // F1 = context->u.highlight.endNumer / context->u.highlight.denom
+ // P = *numer / *denom
+ // R = P * F1 + (1 - P) * F0
+ // = numer * context->endNumer / (denom * context->denom) +
+ // (denom - numer) * startNumer / denom * context->denom
+
+ assert (numer >= 0 && numer <= denom);
+
+ *resNumer = numer * context->endNumer +
+ (denom - numer) * context->startNumer;
+ *resDenom = denom * context->denom;
+}
+
+static void
+Flash_makeFrame (FlashContext *context, FRAME dest, int numer, int denom)
+{
+ CONTEXT oldGfxContext;
+ STAMP s;
+ int blendedNumer;
+ int blendedDenom;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+
+ Flash_blendFraction (context, numer, denom, &blendedNumer, &blendedDenom);
+
+ oldGfxContext = SetContext (workGfxContext);
+ SetContextFGFrame (dest);
+
+ switch (context->type) {
+ case FlashType_highlight:
+ {
+ // Clear the destination just in case
+ SetContextBackGroundColor (BUILD_COLOR_RGBA (0, 0, 0, 0));
+ ClearDrawable ();
+ // Draw the frame at modulated strength (0 < strength <= 128)
+ SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ADDITIVE,
+ DRAW_FACTOR_1 * blendedNumer / blendedDenom));
+ s.frame = context->original;
+ DrawStamp (&s);
+ break;
+ }
+ case FlashType_transition:
+ {
+ FRAME first;
+ FRAME final;
+
+ first = context->u.transition.first;
+ if (first == (FRAME) 0)
+ first = context->original;
+ final = context->u.transition.final;
+ if (final == (FRAME) 0)
+ final = context->original;
+
+ // Draw the first frame at full strength
+ SetContextDrawMode (DRAW_REPLACE_MODE);
+ s.frame = first;
+ DrawStamp (&s);
+ // Merge in the final frame
+ SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ALPHA,
+ DRAW_FACTOR_1 * blendedNumer / blendedDenom));
+ s.frame = final;
+ DrawStamp (&s);
+ break;
+ }
+ case FlashType_overlay:
+ {
+ POINT oldOrigin;
+
+ // Draw the original at full strength
+ SetContextDrawMode (DRAW_REPLACE_MODE);
+ s.frame = context->original;
+ DrawStamp (&s);
+ // Add or subtract the overlay at partial strength
+ SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ADDITIVE,
+ DRAW_FACTOR_1 * blendedNumer / blendedDenom));
+ s.frame = context->u.overlay.frame;
+ // Offset the draw origin to hit the right area
+ oldOrigin = SetContextOrigin (GetFrameHot (s.frame));
+ DrawStamp (&s);
+ SetContextOrigin (oldOrigin);
+ break;
+ }
+ }
+
+ SetContext (oldGfxContext);
+}
+
+// Prepare an entry in the cache.
+static inline void
+Flash_prepareCacheFrame (FlashContext *context, COUNT index)
+{
+ if (context->cache[index] != (FRAME) 0)
+ return;
+
+#ifdef BEGIN_AND_END_FRAME_EXCEPTIONS
+ if (index == 0 && context->type == FlashType_overlay)
+ context->cache[index] = context->original;
+ else if (index == 0 && context->type == FlashType_transition)
+ context->cache[index] = context->u.transition.first != (FRAME) 0 ?
+ context->u.transition.first : context->original;
+ else if (index == context->cacheSize - 1 &&
+ context->type == FlashType_transition)
+ context->cache[index] = context->u.transition.final != (FRAME) 0 ?
+ context->u.transition.final : context->original;
+ else
+#endif /* BEGIN_AND_END_FRMAE_EXCEPTIONS */
+ {
+ context->cache[index] = CaptureDrawable (CreateDrawable (WANT_PIXMAP,
+ context->rect.extent.width, context->rect.extent.height, 1));
+ Flash_makeFrame (context, context->cache[index],
+ index, context->cacheSize - 1);
+ }
+}
+
+static void
+Flash_drawFrame (FlashContext *context, FRAME frame)
+{
+ CONTEXT oldGfxContext;
+ STAMP stamp;
+
+ oldGfxContext = SetContext (context->gfxContext);
+
+ stamp.origin = context->rect.corner;
+ stamp.frame = frame;
+ DrawStamp(&stamp);
+
+ SetContext (oldGfxContext);
+}
+
+static void
+Flash_drawCacheFrame (FlashContext *context, COUNT index)
+{
+ FRAME frame;
+
+ if (context->lastFrameIndex == index)
+ return;
+
+ frame = context->cache[index];
+ Flash_drawFrame (context, frame);
+ context->lastFrameIndex = index;
+}
+
+static inline void
+Flash_drawUncachedFrame (FlashContext *context, int numer, int denom)
+{
+#ifdef BEGIN_AND_END_FRAME_EXCEPTIONS
+ // 'lastFrameIndex' is 0 for the first image, 1 for the final
+ // image, and 2 otherwise.
+
+ if (numer == 0 && context->type == FlashType_overlay)
+ {
+ if (context->lastFrameIndex != 0)
+ return;
+
+ Flash_drawFrame (context, context->original);
+ context->lastFrameIndex = 0;
+ return;
+ }
+ else if (numer == 0 && context->type == FlashType_transition)
+ {
+ if (context->lastFrameIndex == 0)
+ return;
+
+ Flash_drawFrame (context, context->u.transition.first);
+ context->lastFrameIndex = 0;
+ return;
+ }
+ else if (numer == denom && context->type == FlashType_transition)
+ {
+ if (context->lastFrameIndex == 1)
+ return;
+
+ Flash_drawFrame (context, context->u.transition.final);
+ context->lastFrameIndex = 1;
+ return;
+ }
+
+ context->lastFrameIndex = 2;
+#endif /* BEGIN_AND_END_FRMAE_EXCEPTIONS */
+
+ {
+ // Painting to the screen; we need a temporary frame to draw to.
+ FRAME work;
+
+ work = CaptureDrawable (CreateDrawable (WANT_PIXMAP,
+ context->rect.extent.width, context->rect.extent.height, 1));
+ Flash_makeFrame (context, work, numer, denom);
+ Flash_drawFrame (context, work);
+
+ DestroyDrawable (ReleaseDrawable (work));
+ }
+}
+
+static inline void
+Flash_drawCachedFrame (FlashContext *context, int numer, int denom)
+{
+ COUNT cachePos;
+
+ cachePos = ((context->cacheSize - 1) * numer + (denom / 2)) / denom;
+ Flash_prepareCacheFrame (context, cachePos);
+ Flash_drawCacheFrame (context, cachePos);
+}
+
+static void
+Flash_drawCurrentFrame (FlashContext *context)
+{
+ int numer;
+ int denom;
+
+ if (context->state == FlashState_off)
+ {
+ numer = 0;
+ denom = 1;
+ }
+ else if (context->state == FlashState_on)
+ {
+ numer = 1;
+ denom = 1;
+ }
+ else
+ {
+ TimeCount now = GetTimeCounter ();
+
+ if (context->state == FlashState_fadeIn)
+ denom = (int) context->fadeInTime;
+ else
+ denom = (int) context->fadeOutTime;
+
+ numer = (int) (now - context->lastStateTime);
+
+ if (numer > denom)
+ numer = denom;
+
+ if (context->state == FlashState_fadeOut)
+ numer = (int) context->fadeOutTime - numer;
+ }
+
+ if (context->cacheSize == 0)
+ Flash_drawUncachedFrame (context, numer, denom);
+ else
+ Flash_drawCachedFrame (context, numer, denom);
+}
+
diff --git a/src/uqm/flash.h b/src/uqm/flash.h
new file mode 100644
index 0000000..0488fd3
--- /dev/null
+++ b/src/uqm/flash.h
@@ -0,0 +1,223 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_FLASH_H_
+#define UQM_FLASH_H_
+
+/*
+ * This code can draw three kinds of flashing areas.
+ * - a rectangular highlight area. The brightness of the area oscilates.
+ * - an overlay; an image is laid over an area, with oscilating brightness.
+ * - a transition/cross-fade between two images.
+ *
+ * NB. The graphics lock should not be held when any of the Flash functions
+ * is called.
+ *
+ *
+ * Example:
+ *
+ * // We create the flash context; it is used to manipulate the flash
+ * // rectangle while it exists.
+ * FlashContext *fc = Flash_createHighlight (gfxContext, rect);
+ *
+ * // Specify how bright the flash is at the beginning and ending of the
+ * // sequence.
+ * Flash_setMergeFactors(fc, 2, 3, 2);
+ *
+ * // We change the flashing speed from the defaults.
+ * Flash_setSpeed (fc, ONE_SECOND, ONE_SECOND, ONE_SECOND, ONE_SECOND);
+ *
+ * // During cross-fades, update 8 times per second.
+ * Flash_setFrameTime (fc, ONE_SECOND / 8);
+ *
+ * // We start the flashing. The default is to start from the "off" state.
+ * Flash_start (fc);
+ *
+ * // Some other stuff happens
+ * ...
+ *
+ * // The user has activated the selection. We pause for instance while
+ * // a pop-up window is active.
+ * Flash_pause (fc);
+ *
+ * // We set the flashing rectangle full on, to indicate the current
+ * // selection while the popup is active.
+ * Flash_setState (FlashState_on);
+ * ...
+ * // Continue the flashing.
+ * Flash_continue (fc);
+ * ...
+ * // Modifying the graphics of the area that is flashing:
+ * void Flash_preUpdate (fc);
+ * ... // do drawing
+ * void Flash_postUpdate (fc);
+ * ...
+ * // We're done. Terminating the flash restores the flash area to its
+ * // original state.
+ * Flash_terminate (fc);
+ *
+ *
+ * Periodically, Flash_process() should be called on the flash context,
+ * so that the flashing square is updated.
+ * You can use Flash_nextTime() to determine when the next update is needed,
+ * or just call Flash_process() to try (it does no updates if not needed).
+ *
+ * Limitations:
+ *
+ * * Functions that draw to the gfxContext or read the original gfxContext
+ * contents, which is most of them, must be called with gfxContext having
+ * the same clip-rect as it did when other drawing functions were called.
+ * Otherwise, original contents restoration may draw to the wrong area, or
+ * the wrong area may be read.
+ * There may be cases where one would *want* that to happen, and such
+ * cases are not covered by this limitation.
+ * * Multiple flashes may be used simultaneously, but don't let them
+ * overlap; artifacts would occur.
+ */
+
+#include "libs/gfxlib.h"
+#include "libs/timelib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef enum {
+ FlashState_fadeIn = 0,
+ // Someway between on and off, going towards on.
+ FlashState_on = 1,
+ // The overlay image is visible at 100% opacity.
+ FlashState_fadeOut = 2,
+ // Someway between on and off, going towards off.
+ FlashState_off = 3,
+ // The overlay image is not visible.
+} FlashState;
+
+typedef struct FlashContext FlashContext;
+
+#ifdef FLASH_INTERNAL
+typedef enum {
+ FlashType_highlight,
+ FlashType_transition,
+ FlashType_overlay,
+} FlashType;
+
+struct FlashContext {
+ CONTEXT gfxContext;
+ // The graphics context used for drawing.
+
+ RECT rect;
+ // The rectangle to flash.
+
+ FRAME original;
+ // The original contents of the flash area.
+
+ FlashType type;
+ // The type of flash animation.
+
+ union {
+ /*struct {
+ } highlight;*/
+ struct {
+ FRAME first;
+ // The first image from the transition (cross-fade).
+ // (FRAME) 0 means that the original is to be used.
+ FRAME final;
+ // The last image from the transition.
+ // (FRAME) 0 means that the original is to be used.
+ } transition;
+ struct {
+ FRAME frame;
+ } overlay;
+ } u;
+
+ int startNumer;
+ // Numerator for the merge factor for the on state.
+ int endNumer;
+ // Numerator for the merge factor for the off state.
+ int denom;
+ // Denominator for the merge factor.
+
+ TimeCount fadeInTime;
+ TimeCount onTime;
+ TimeCount fadeOutTime;
+ TimeCount offTime;
+
+ TimeCount frameTime;
+
+ FlashState state;
+ TimeCount lastStateTime;
+ // Time of the last state change.
+ TimeCount lastFrameTime;
+ // Time of the last frame draw.
+
+ BOOLEAN started;
+ BOOLEAN paused;
+
+ FRAME *cache;
+ COUNT cacheSize;
+
+ COUNT lastFrameIndex;
+ // Last frame drawn; used to determine whether a frame needs to
+ // be redawn. If a cache is used, this is the index in the cache.
+ // If no cache is used, this is either 0, 1, or 2, for
+ // the respectively first, last, or other frame for the flash
+ // animation.
+};
+
+# define Flash_DEFAULT_FADE_IN_TIME 0
+# define Flash_DEFAULT_ON_TIME (ONE_SECOND / 8)
+# define Flash_DEFAULT_FADE_OUT_TIME 0
+# define Flash_DEFAULT_OFF_TIME (ONE_SECOND / 8)
+
+# define Flash_DEFAULT_CACHE_SIZE 9
+#endif /* FLASH_INTERNAL */
+
+
+FlashContext *Flash_createHighlight (CONTEXT gfxContext, const RECT *rect);
+FlashContext *Flash_createTransition (CONTEXT gfxContext,
+ const POINT *origin, FRAME first, FRAME final);
+FlashContext *Flash_createOverlay (CONTEXT gfxContext,
+ const POINT *origin, FRAME overlay);
+
+void Flash_setState (FlashContext *context, FlashState state,
+ TimeCount timeSpentInState);
+void Flash_start (FlashContext *context);
+void Flash_terminate (FlashContext *context);
+void Flash_pause (FlashContext *context);
+void Flash_continue (FlashContext *context);
+void Flash_process (FlashContext *context);
+void Flash_setSpeed (FlashContext *context, TimeCount fadeInTime,
+ TimeCount onTime, TimeCount fadeOutTime, TimeCount offTime);
+void Flash_setMergeFactors(FlashContext *context, int startNumer,
+ int endNumer, int denom);
+void Flash_setFrameTime (FlashContext *context, TimeCount frameTime);
+TimeCount Flash_nextTime (FlashContext *context);
+void Flash_setRect (FlashContext *context, const RECT *rect);
+void Flash_getRect (FlashContext *context, RECT *rect);
+void Flash_setOverlay(FlashContext *context, const POINT *origin,
+ FRAME overlay);
+void Flash_preUpdate (FlashContext *context);
+void Flash_postUpdate (FlashContext *context);
+void Flash_setCacheSize (FlashContext *context, COUNT size);
+COUNT Flash_getCacheSize (const FlashContext *context);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_FLASH_H_ */
diff --git a/src/uqm/fmv.c b/src/uqm/fmv.c
new file mode 100644
index 0000000..b567901
--- /dev/null
+++ b/src/uqm/fmv.c
@@ -0,0 +1,134 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "fmv.h"
+
+#include "controls.h"
+#include "hyper.h"
+#include "options.h"
+#include "master.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "settings.h"
+#include "setup.h"
+#include "libs/vidlib.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/inplib.h"
+
+void
+DoShipSpin (COUNT index, MUSIC_REF hMusic)
+{
+#ifdef WANT_SHIP_SPINS
+ char vnbuf[32];
+ RECT old_r;
+
+ LoadIntoExtraScreen (NULL);
+#if 0
+ /* This is cut out right now but should be part of the 3DO side */
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 4));
+ FlushColorXForms ();
+#endif
+
+ if (hMusic)
+ StopMusic ();
+
+ FreeHyperData ();
+
+ // TODO: It would be nice to have better resource names for these.
+ sprintf (vnbuf, "slides.spins.%02u", (unsigned)index);
+ ShowPresentation (vnbuf);
+
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 4));
+ FlushColorXForms ();
+
+ GetContextClipRect (&old_r);
+ SetContextClipRect (NULL);
+ DrawFromExtraScreen (NULL);
+ SetContextClipRect (&old_r);
+
+ if (hMusic)
+ PlayMusic (hMusic, TRUE, 1);
+
+ SleepThreadUntil (FadeScreen (FadeAllToColor, ONE_SECOND / 4));
+ FlushColorXForms ();
+#else
+ (void) index; /* Satisfy compiler */
+ (void) hMusic; /* Satisfy compiler */
+#endif /* WANT_SHIP_SPINS */
+}
+
+void
+SplashScreen (void (* DoProcessing)(DWORD TimeOut))
+{
+ STAMP s;
+ DWORD TimeOut;
+
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 120));
+ SetContext (ScreenContext);
+ s.origin.x = s.origin.y = 0;
+ s.frame = CaptureDrawable (LoadGraphic (TITLE_ANIM));
+ DrawStamp (&s);
+ DestroyDrawable (ReleaseDrawable (s.frame));
+
+ TimeOut = FadeScreen (FadeAllToColor, ONE_SECOND / 2);
+
+ if (DoProcessing)
+ DoProcessing (TimeOut);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ return;
+ }
+
+ /* There was a forcible setting of CHECK_ABORT here. I cannot
+ * find any purpose for this that DoRestart doesn't handle
+ * better (forcing all other threads but this one to quit out,
+ * I believe), and have thus removed it. It was interfering
+ * with the proper operation of the quit operation.
+ * --Michael */
+
+ WaitForAnyButton (FALSE, ONE_SECOND * 3, TRUE);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ return;
+ }
+ GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
+
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2));
+}
+
+void
+Introduction (void)
+{
+ ShowPresentation (INTROPRES_STRTAB);
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2));
+}
+
+void
+Victory (void)
+{
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2));
+
+ /* by default we do 3DO cinematics; or PC slides when 3DO files are
+ * not present */
+ ShowPresentation (FINALPRES_STRTAB);
+
+ FadeScreen (FadeAllToBlack, 0);
+}
+
+
+
diff --git a/src/uqm/fmv.h b/src/uqm/fmv.h
new file mode 100644
index 0000000..6ff4dd8
--- /dev/null
+++ b/src/uqm/fmv.h
@@ -0,0 +1,41 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_FMV_H_
+#define UQM_FMV_H_
+
+#include "libs/compiler.h"
+#include "libs/sndlib.h"
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define WANT_SHIP_SPINS
+
+extern void SplashScreen (void (* DoProcessing)(DWORD TimeOut));
+extern void Introduction (void);
+extern void Victory (void);
+extern void DoShipSpin (COUNT index, MUSIC_REF hMusic);
+
+extern BOOLEAN ShowPresentation (RESOURCE presentation);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_FMV_H_ */
diff --git a/src/uqm/galaxy.c b/src/uqm/galaxy.c
new file mode 100644
index 0000000..7e14584
--- /dev/null
+++ b/src/uqm/galaxy.c
@@ -0,0 +1,464 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* background starfield - used to generate agalaxy.asm */
+
+#include "element.h"
+#include "globdata.h"
+#include "init.h"
+#include "process.h"
+#include "units.h"
+#include "options.h"
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+
+extern COUNT zoom_out;
+extern PRIM_LINKS DisplayLinks;
+
+
+#define BIG_STAR_COUNT 30
+#define MED_STAR_COUNT 60
+#define SML_STAR_COUNT 90
+#define NUM_STARS (BIG_STAR_COUNT \
+ + MED_STAR_COUNT \
+ + SML_STAR_COUNT)
+
+POINT SpaceOrg;
+static POINT log_star_array[NUM_STARS];
+
+#define NUM_STAR_PLANES 3
+
+typedef struct
+{
+ COUNT min_star_index;
+ COUNT num_stars;
+ POINT *star_array;
+ POINT *pmin_star;
+ POINT *plast_star;
+} STAR_BLOCK;
+
+STAR_BLOCK StarBlock[NUM_STAR_PLANES] =
+{
+ {
+ 0, BIG_STAR_COUNT,
+ &log_star_array[0],
+ NULL, NULL,
+ },
+ {
+ 0, MED_STAR_COUNT,
+ &log_star_array[BIG_STAR_COUNT],
+ NULL, NULL,
+ },
+ {
+ 0, SML_STAR_COUNT,
+ &log_star_array[BIG_STAR_COUNT + MED_STAR_COUNT],
+ NULL, NULL,
+ },
+};
+
+static void
+SortStarBlock (STAR_BLOCK *pStarBlock)
+{
+ COUNT i;
+
+ for (i = 0; i < pStarBlock->num_stars; ++i)
+ {
+ COUNT j;
+
+ for (j = pStarBlock->num_stars - 1; j > i; --j)
+ {
+ if (pStarBlock->star_array[i].y > pStarBlock->star_array[j].y)
+ {
+ POINT temp;
+
+ temp = pStarBlock->star_array[i];
+ pStarBlock->star_array[i] = pStarBlock->star_array[j];
+ pStarBlock->star_array[j] = temp;
+ }
+ }
+ }
+
+ pStarBlock->min_star_index = 0;
+ pStarBlock->pmin_star = &pStarBlock->star_array[0];
+ pStarBlock->plast_star =
+ &pStarBlock->star_array[pStarBlock->num_stars - 1];
+}
+
+static void
+WrapStarBlock (SIZE plane, SIZE dx, SIZE dy)
+{
+ COUNT i;
+ POINT *ppt;
+ SIZE offs_y;
+ COUNT num_stars;
+ STAR_BLOCK *pStarBlock;
+
+ pStarBlock = &StarBlock[plane];
+
+ i = pStarBlock->min_star_index;
+ ppt = pStarBlock->pmin_star;
+ num_stars = pStarBlock->num_stars;
+ if (dy < 0)
+ {
+ COUNT first;
+
+ first = i;
+
+ dy = -dy;
+ offs_y = (LOG_SPACE_HEIGHT << plane) - dy;
+
+ while (ppt->y < dy)
+ {
+ ppt->y += offs_y;
+ ppt->x += dx;
+ if (++i < num_stars)
+ ++ppt;
+ else
+ {
+ i = 0;
+ ppt = &pStarBlock->star_array[0];
+ }
+
+ if (i == first)
+ return;
+ }
+ pStarBlock->min_star_index = i;
+ pStarBlock->pmin_star = ppt;
+
+ if (first <= i)
+ {
+ i = num_stars - i;
+ do
+ {
+ ppt->y -= dy;
+ ppt->x += dx;
+ ++ppt;
+ } while (--i);
+ ppt = &pStarBlock->star_array[0];
+ }
+
+ if (first > i)
+ {
+ i = first - i;
+ do
+ {
+ ppt->y -= dy;
+ ppt->x += dx;
+ ++ppt;
+ } while (--i);
+ }
+ }
+ else
+ {
+ COUNT last;
+
+ --ppt;
+ if (i-- == 0)
+ {
+ i = num_stars - 1;
+ ppt = pStarBlock->plast_star;
+ }
+
+ last = i;
+
+ if (dy > 0)
+ {
+ offs_y = (LOG_SPACE_HEIGHT << plane) - dy;
+
+ while (ppt->y >= offs_y)
+ {
+ ppt->y -= offs_y;
+ ppt->x += dx;
+ if (i-- > 0)
+ --ppt;
+ else
+ {
+ i = num_stars - 1;
+ ppt = pStarBlock->plast_star;
+ }
+
+ if (i == last)
+ return;
+ }
+
+ pStarBlock->pmin_star = ppt + 1;
+ if ((pStarBlock->min_star_index = i + 1) == num_stars)
+ {
+ pStarBlock->min_star_index = 0;
+ pStarBlock->pmin_star = &pStarBlock->star_array[0];
+ }
+ }
+
+ if (last >= i)
+ {
+ ++i;
+ do
+ {
+ ppt->y += dy;
+ ppt->x += dx;
+ --ppt;
+ } while (--i);
+ i = num_stars - 1;
+ ppt = pStarBlock->plast_star;
+ }
+
+ if (last < i)
+ {
+ i = i - last;
+ do
+ {
+ ppt->y += dy;
+ ppt->x += dx;
+ --ppt;
+ } while (--i);
+ }
+ }
+}
+
+void
+InitGalaxy (void)
+{
+ COUNT i, factor;
+ POINT *ppt;
+ PRIM_LINKS Links;
+
+ log_add (log_Debug, "InitGalaxy(): transition_width = %d, "
+ "transition_height = %d",
+ TRANSITION_WIDTH, TRANSITION_HEIGHT);
+
+ Links = MakeLinks (END_OF_LIST, END_OF_LIST);
+ factor = ONE_SHIFT + MAX_REDUCTION + (BACKGROUND_SHIFT - 3);
+ for (i = 0, ppt = log_star_array; i < NUM_STARS; ++i, ++ppt)
+ {
+ COUNT p;
+
+ p = AllocDisplayPrim ();
+
+ if (i == BIG_STAR_COUNT || i == BIG_STAR_COUNT + MED_STAR_COUNT)
+ ++factor;
+
+ ppt->x = (COORD)((UWORD)TFB_Random () % SPACE_WIDTH) << factor;
+ ppt->y = (COORD)((UWORD)TFB_Random () % SPACE_HEIGHT) << factor;
+
+ if (i < BIG_STAR_COUNT + MED_STAR_COUNT)
+ {
+ SetPrimType (&DisplayArray[p], STAMP_PRIM);
+ SetPrimColor (&DisplayArray[p],
+ BUILD_COLOR (MAKE_RGB15 (0x0B, 0x0B, 0x1F), 0x09));
+ DisplayArray[p].Object.Stamp.frame = stars_in_space;
+ }
+ else
+ {
+ SetPrimType (&DisplayArray[p], POINT_PRIM);
+ if (!inHQSpace ())
+ SetPrimColor (&DisplayArray[p],
+ BUILD_COLOR (MAKE_RGB15 (0x15, 0x15, 0x15), 0x07));
+ else if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ SetPrimColor (&DisplayArray[p],
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x8C));
+ else
+ SetPrimColor (&DisplayArray[p],
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x0E, 0x00), 0x8C));
+ }
+
+ InsertPrim (&Links, p, GetPredLink (Links));
+ }
+
+ SortStarBlock (&StarBlock[0]);
+ SortStarBlock (&StarBlock[1]);
+ SortStarBlock (&StarBlock[2]);
+}
+
+static BOOLEAN
+CmpMovePoints (const POINT *pt1, const POINT *pt2, SIZE dx, SIZE dy,
+ SIZE reduction)
+{
+ if (optMeleeScale == TFB_SCALE_STEP)
+ {
+ return (int)pt1->x != (int)((pt2->x - dx) >> reduction)
+ || (int)pt1->y != (int)((pt2->y - dy) >> reduction);
+ }
+ else
+ {
+ return (int)pt1->x != (int)(((pt2->x - dx) << ZOOM_SHIFT) / reduction)
+ || (int)pt1->y != (int)(((pt2->y - dy) << ZOOM_SHIFT) / reduction);
+ }
+}
+
+void
+MoveGalaxy (VIEW_STATE view_state, SIZE dx, SIZE dy)
+{
+ PRIMITIVE *pprim;
+ static const COUNT star_counts[] =
+ {
+ BIG_STAR_COUNT,
+ MED_STAR_COUNT,
+ SML_STAR_COUNT
+ };
+ static const COUNT star_frame_ofs[] = { 32 + 26, 26, 0 };
+
+ if (view_state != VIEW_STABLE)
+ {
+ COUNT reduction;
+ COUNT i;
+ COUNT iss;
+ POINT *ppt;
+ int wrap_around;
+
+ reduction = zoom_out;
+
+ if (view_state == VIEW_CHANGE)
+ {
+ if (inHQSpace ())
+ {
+ for (iss = 0, pprim = DisplayArray; iss < 2; ++iss)
+ {
+ for (i = star_counts[iss]; i > 0; --i, ++pprim)
+ {
+ pprim->Object.Stamp.frame = SetAbsFrameIndex (
+ stars_in_space,
+ (COUNT)(TFB_Random () & 31)
+ + star_frame_ofs[iss]);
+ }
+ }
+ }
+ else
+ {
+ GRAPHICS_PRIM star_object[2];
+ FRAME star_frame[2];
+
+ star_frame[0] = IncFrameIndex (stars_in_space);
+ star_frame[1] = stars_in_space;
+
+ if (optMeleeScale == TFB_SCALE_STEP)
+ { /* on PC, the closest stars are images when zoomed out */
+ star_object[0] = STAMP_PRIM;
+ if (reduction > 0)
+ {
+ star_object[1] = POINT_PRIM;
+ star_frame[0] = star_frame[1];
+ }
+ else
+ {
+ star_object[1] = STAMP_PRIM;
+ }
+ }
+ else
+ { /* on 3DO, the closest stars are pixels when zoomed out */
+ star_object[1] = POINT_PRIM;
+ if (reduction > (1 << ZOOM_SHIFT))
+ {
+ star_object[0] = POINT_PRIM;
+ }
+ else
+ {
+ star_object[0] = STAMP_PRIM;
+ }
+ }
+
+ for (iss = 0, pprim = DisplayArray; iss < 2; ++iss)
+ {
+ for (i = star_counts[iss]; i > 0; --i, ++pprim)
+ {
+ SetPrimType (pprim, star_object[iss]);
+ pprim->Object.Stamp.frame = star_frame[iss];
+ }
+ }
+ }
+ }
+
+ if (inHQSpace ())
+ {
+ for (i = BIG_STAR_COUNT + MED_STAR_COUNT, pprim = DisplayArray;
+ i > 0; --i, ++pprim)
+ {
+ COUNT base_index;
+
+ base_index = GetFrameIndex (pprim->Object.Stamp.frame) - 26;
+ pprim->Object.Stamp.frame =
+ SetAbsFrameIndex (pprim->Object.Stamp.frame,
+ ((base_index & ~31) + ((base_index + 1) & 31)) + 26);
+ }
+
+ dx <<= 3;
+ dy <<= 3;
+ }
+
+ WrapStarBlock (2, dx, dy);
+ WrapStarBlock (1, dx, dy);
+ WrapStarBlock (0, dx, dy);
+
+ if (!inHQSpace ())
+ {
+ dx = SpaceOrg.x;
+ dy = SpaceOrg.y;
+ if (optMeleeScale == TFB_SCALE_STEP)
+ reduction += ONE_SHIFT;
+ else
+ reduction <<= ONE_SHIFT;
+ }
+ else
+ {
+ dx = (COORD)(LOG_SPACE_WIDTH >> 1)
+ - (LOG_SPACE_WIDTH >> ((MAX_REDUCTION + 1)
+ - MAX_VIS_REDUCTION));
+ dy = (COORD)(LOG_SPACE_HEIGHT >> 1)
+ - (LOG_SPACE_HEIGHT >> ((MAX_REDUCTION + 1)
+ - MAX_VIS_REDUCTION));
+ if (optMeleeScale == TFB_SCALE_STEP)
+ reduction = MAX_VIS_REDUCTION + ONE_SHIFT;
+ else
+ reduction = MAX_ZOOM_OUT << ONE_SHIFT;
+ }
+
+ ppt = log_star_array;
+ for (iss = 0, pprim = DisplayArray, wrap_around = LOG_SPACE_WIDTH;
+ iss < 3 &&
+ (view_state == VIEW_CHANGE || CmpMovePoints (
+ &pprim->Object.Point, ppt, dx, dy, reduction));
+ ++iss, wrap_around <<= 1, dx <<= 1, dy <<= 1)
+ {
+ for (i = star_counts[iss]; i > 0; --i, ++pprim, ++ppt)
+ {
+ // ppt->x &= (LOG_SPACE_WIDTH - 1);
+ ppt->x = WRAP_VAL (ppt->x, wrap_around);
+ if (optMeleeScale == TFB_SCALE_STEP)
+ {
+ pprim->Object.Point.x = (ppt->x - dx) >> reduction;
+ pprim->Object.Point.y = (ppt->y - dy) >> reduction;
+ }
+ else
+ {
+ pprim->Object.Point.x = ((ppt->x - dx) << ZOOM_SHIFT)
+ / reduction;
+ pprim->Object.Point.y = ((ppt->y - dy) << ZOOM_SHIFT)
+ / reduction;
+ }
+ }
+ if (optMeleeScale == TFB_SCALE_STEP)
+ ++reduction;
+ else
+ reduction <<= 1;
+ }
+ }
+
+ DisplayLinks = MakeLinks (NUM_STARS - 1, 0);
+}
diff --git a/src/uqm/gameev.c b/src/uqm/gameev.c
new file mode 100644
index 0000000..83df601
--- /dev/null
+++ b/src/uqm/gameev.c
@@ -0,0 +1,729 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "gameev.h"
+
+#include "build.h"
+#include "clock.h"
+#include "starmap.h"
+#include "gendef.h"
+#include "globdata.h"
+#include "hyper.h"
+#include "libs/compiler.h"
+#include "libs/mathlib.h"
+
+
+static void arilou_entrance_event (void);
+static void arilou_exit_event (void);
+static void check_race_growth (void);
+static void black_urquan_genocide (void);
+static void pkunk_mission (void);
+static void thradd_mission (void);
+static void ilwrath_mission (void);
+static void utwig_supox_mission (void);
+static void mycon_mission (void);
+
+
+void
+AddInitialGameEvents (void) {
+ AddEvent (RELATIVE_EVENT, 0, 1, 0, HYPERSPACE_ENCOUNTER_EVENT);
+ AddEvent (ABSOLUTE_EVENT, 3, 17, START_YEAR, ARILOU_ENTRANCE_EVENT);
+ AddEvent (RELATIVE_EVENT, 0, 0, YEARS_TO_KOHRAH_VICTORY,
+ KOHR_AH_VICTORIOUS_EVENT);
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, SLYLANDRO_RAMP_UP);
+}
+
+void
+EventHandler (BYTE selector)
+{
+ switch (selector)
+ {
+ case ARILOU_ENTRANCE_EVENT:
+ arilou_entrance_event ();
+ break;
+ case ARILOU_EXIT_EVENT:
+ arilou_exit_event ();
+ break;
+ case HYPERSPACE_ENCOUNTER_EVENT:
+ check_race_growth ();
+ if (inHyperSpace ())
+ check_hyperspace_encounter ();
+
+ AddEvent (RELATIVE_EVENT, 0, 1, 0, HYPERSPACE_ENCOUNTER_EVENT);
+ break;
+ case KOHR_AH_VICTORIOUS_EVENT:
+ if (GET_GAME_STATE (UTWIG_SUPOX_MISSION))
+ {
+ AddEvent (RELATIVE_EVENT, 0, 0, 1, KOHR_AH_GENOCIDE_EVENT);
+ break;
+ }
+ /* FALLTHROUGH */
+ case KOHR_AH_GENOCIDE_EVENT:
+ if (!GET_GAME_STATE (KOHR_AH_FRENZY)
+ && LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && CurStarDescPtr
+ && CurStarDescPtr->Index == SAMATRA_DEFINED)
+ AddEvent (RELATIVE_EVENT, 0, 7, 0, KOHR_AH_GENOCIDE_EVENT);
+ else
+ black_urquan_genocide ();
+ break;
+ case ADVANCE_PKUNK_MISSION:
+ pkunk_mission ();
+ break;
+ case ADVANCE_THRADD_MISSION:
+ thradd_mission ();
+ break;
+ case ZOQFOT_DISTRESS_EVENT:
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && CurStarDescPtr
+ && CurStarDescPtr->Index == ZOQFOT_DEFINED)
+ AddEvent (RELATIVE_EVENT, 0, 7, 0, ZOQFOT_DISTRESS_EVENT);
+ else
+ {
+ SET_GAME_STATE (ZOQFOT_DISTRESS, 1);
+ AddEvent (RELATIVE_EVENT, 6, 0, 0, ZOQFOT_DEATH_EVENT);
+ }
+ break;
+ case ZOQFOT_DEATH_EVENT:
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && CurStarDescPtr
+ && CurStarDescPtr->Index == ZOQFOT_DEFINED)
+ AddEvent (RELATIVE_EVENT, 0, 7, 0, ZOQFOT_DEATH_EVENT);
+ else if (GET_GAME_STATE (ZOQFOT_DISTRESS))
+ {
+ HFLEETINFO hZoqFot;
+ FLEET_INFO *ZoqFotPtr;
+
+ hZoqFot = GetStarShipFromIndex (&GLOBAL (avail_race_q),
+ ZOQFOTPIK_SHIP);
+ ZoqFotPtr = LockFleetInfo (&GLOBAL (avail_race_q), hZoqFot);
+ ZoqFotPtr->actual_strength = 0;
+ ZoqFotPtr->allied_state = DEAD_GUY;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hZoqFot);
+
+ SET_GAME_STATE (ZOQFOT_DISTRESS, 2);
+ }
+ break;
+ case SHOFIXTI_RETURN_EVENT:
+ SetRaceAllied (SHOFIXTI_SHIP, TRUE);
+ GLOBAL (CrewCost) -= 2;
+ /* crew is not an issue anymore */
+ SET_GAME_STATE (CREW_PURCHASED0, 0);
+ SET_GAME_STATE (CREW_PURCHASED1, 0);
+ break;
+ case ADVANCE_UTWIG_SUPOX_MISSION:
+ utwig_supox_mission ();
+ break;
+ case SPATHI_SHIELD_EVENT:
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && CurStarDescPtr
+ && CurStarDescPtr->Index == SPATHI_DEFINED)
+ AddEvent (RELATIVE_EVENT, 0, 7, 0, SPATHI_SHIELD_EVENT);
+ else
+ {
+ HFLEETINFO hSpathi;
+ FLEET_INFO *SpathiPtr;
+
+ hSpathi = GetStarShipFromIndex (&GLOBAL (avail_race_q),
+ SPATHI_SHIP);
+ SpathiPtr = LockFleetInfo (&GLOBAL (avail_race_q), hSpathi);
+
+ if (SpathiPtr->actual_strength)
+ {
+ SetRaceAllied (SPATHI_SHIP, FALSE);
+ SET_GAME_STATE (SPATHI_SHIELDED_SELVES, 1);
+ SpathiPtr->actual_strength = 0;
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hSpathi);
+ }
+ break;
+ case ADVANCE_ILWRATH_MISSION:
+ ilwrath_mission ();
+ break;
+ case ADVANCE_MYCON_MISSION:
+ mycon_mission ();
+ break;
+ case ARILOU_UMGAH_CHECK:
+ SET_GAME_STATE (ARILOU_CHECKED_UMGAH, 2);
+ break;
+ case YEHAT_REBEL_EVENT:
+ {
+ HFLEETINFO hRebel, hRoyalist;
+ FLEET_INFO *RebelPtr;
+ FLEET_INFO *RoyalistPtr;
+
+ hRebel = GetStarShipFromIndex (&GLOBAL (avail_race_q),
+ YEHAT_REBEL_SHIP);
+ RebelPtr = LockFleetInfo (&GLOBAL (avail_race_q), hRebel);
+ hRoyalist = GetStarShipFromIndex (&GLOBAL (avail_race_q),
+ YEHAT_SHIP);
+ RoyalistPtr = LockFleetInfo (&GLOBAL (avail_race_q), hRoyalist);
+ RoyalistPtr->actual_strength = RoyalistPtr->actual_strength *
+ 2 / 3;
+ RebelPtr->actual_strength = RoyalistPtr->actual_strength;
+ RebelPtr->loc.x = 5150;
+ RebelPtr->loc.y = 0;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hRoyalist);
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hRebel);
+ StartSphereTracking (YEHAT_REBEL_SHIP);
+ break;
+ }
+ case SLYLANDRO_RAMP_UP:
+ if (!GET_GAME_STATE (DESTRUCT_CODE_ON_SHIP))
+ {
+ BYTE ramp_factor;
+
+ ramp_factor = GET_GAME_STATE (SLYLANDRO_MULTIPLIER);
+ if (++ramp_factor <= 4)
+ {
+ SET_GAME_STATE (SLYLANDRO_MULTIPLIER, ramp_factor);
+ AddEvent (RELATIVE_EVENT, 0, 182, 0, SLYLANDRO_RAMP_UP);
+ }
+ }
+ break;
+ case SLYLANDRO_RAMP_DOWN:
+ {
+ BYTE ramp_factor;
+
+ ramp_factor = GET_GAME_STATE (SLYLANDRO_MULTIPLIER);
+ if (--ramp_factor)
+ AddEvent (RELATIVE_EVENT, 0, 23, 0, SLYLANDRO_RAMP_DOWN);
+ SET_GAME_STATE (SLYLANDRO_MULTIPLIER, ramp_factor);
+ break;
+ }
+ }
+}
+
+void
+SetRaceDest (BYTE which_race, COORD x, COORD y, BYTE days_left, BYTE
+ func_index)
+{
+ HFLEETINFO hFleet;
+ FLEET_INFO *FleetPtr;
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), which_race);
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+
+ FleetPtr->dest_loc.x = x;
+ FleetPtr->dest_loc.y = y;
+ FleetPtr->days_left = days_left;
+ FleetPtr->func_index = func_index;
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+}
+
+
+
+static void
+arilou_entrance_event (void)
+{
+ SET_GAME_STATE (ARILOU_SPACE, OPENING);
+ AddEvent (RELATIVE_EVENT, 0, 3, 0, ARILOU_EXIT_EVENT);
+}
+
+static void
+arilou_exit_event (void)
+{
+ COUNT month_index, year_index;
+
+ year_index = GLOBAL (GameClock.year_index);
+ if ((month_index = GLOBAL (GameClock.month_index) % 12) == 0)
+ ++year_index;
+ ++month_index;
+
+ SET_GAME_STATE (ARILOU_SPACE, CLOSING);
+ AddEvent (ABSOLUTE_EVENT,
+ month_index, 17, year_index, ARILOU_ENTRANCE_EVENT);
+}
+
+static void
+check_race_growth (void)
+{
+ HFLEETINFO hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip; hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ if (FleetPtr->actual_strength
+ && FleetPtr->actual_strength != INFINITE_RADIUS)
+ {
+ SIZE delta_strength;
+
+ delta_strength = (SBYTE)FleetPtr->growth;
+ if (FleetPtr->growth_err_term <= FleetPtr->growth_fract)
+ {
+ if (delta_strength <= 0)
+ --delta_strength;
+ else
+ ++delta_strength;
+ }
+ FleetPtr->growth_err_term -= FleetPtr->growth_fract;
+
+ delta_strength += FleetPtr->actual_strength;
+ if (delta_strength <= 0)
+ {
+ delta_strength = 0;
+ FleetPtr->allied_state = DEAD_GUY;
+ }
+ else if (delta_strength > MAX_FLEET_STRENGTH)
+ delta_strength = MAX_FLEET_STRENGTH;
+
+ FleetPtr->actual_strength = (COUNT)delta_strength;
+ if (FleetPtr->actual_strength && FleetPtr->days_left)
+ {
+ FleetPtr->loc.x += (FleetPtr->dest_loc.x - FleetPtr->loc.x)
+ / FleetPtr->days_left;
+ FleetPtr->loc.y += (FleetPtr->dest_loc.y - FleetPtr->loc.y)
+ / FleetPtr->days_left;
+
+ if (--FleetPtr->days_left == 0
+ && FleetPtr->func_index != (BYTE) ~0)
+ EventHandler (FleetPtr->func_index);
+ }
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+}
+
+static void
+black_urquan_genocide (void)
+{
+ BYTE Index;
+ long best_dist;
+ SIZE best_dx, best_dy;
+ HFLEETINFO hStarShip, hNextShip;
+ HFLEETINFO hBlackUrquan;
+ FLEET_INFO *BlackUrquanPtr;
+
+ hBlackUrquan = GetStarShipFromIndex (&GLOBAL (avail_race_q),
+ BLACK_URQUAN_SHIP);
+ BlackUrquanPtr = LockFleetInfo (&GLOBAL (avail_race_q), hBlackUrquan);
+
+ best_dist = -1;
+ best_dx = SOL_X - BlackUrquanPtr->loc.x;
+ best_dy = SOL_Y - BlackUrquanPtr->loc.y;
+ for (Index = 0, hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip; ++Index, hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ if (Index != BLACK_URQUAN_SHIP
+ && Index != URQUAN_SHIP
+ && FleetPtr->actual_strength != INFINITE_RADIUS)
+ {
+ SIZE dx, dy;
+
+ dx = FleetPtr->loc.x - BlackUrquanPtr->loc.x;
+ dy = FleetPtr->loc.y - BlackUrquanPtr->loc.y;
+ if (dx == 0 && dy == 0)
+ {
+ // Arrived at the victim's home world. Cleanse it.
+ FleetPtr->allied_state = DEAD_GUY;
+ FleetPtr->actual_strength = 0;
+ }
+ else if (FleetPtr->actual_strength)
+ {
+ long dist;
+
+ dist = (long)dx * dx + (long)dy * dy;
+ if (best_dist < 0 || dist < best_dist || Index == DRUUGE_SHIP)
+ {
+ best_dist = dist;
+ best_dx = dx;
+ best_dy = dy;
+
+ if (Index == DRUUGE_SHIP)
+ hNextShip = 0;
+ }
+ }
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+
+ if (best_dist < 0 && best_dx == 0 && best_dy == 0)
+ {
+ // All spheres of influence are gone - game over.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ GLOBAL_SIS (CrewEnlisted) = (COUNT)~0;
+
+ SET_GAME_STATE (KOHR_AH_KILLED_ALL, 1);
+ }
+ else
+ {
+ // Moving towards new race to cleanse.
+ COUNT speed;
+
+ if (best_dist < 0)
+ best_dist = (long)best_dx * best_dx + (long)best_dy * best_dy;
+
+ speed = square_root (best_dist) / 158;
+ if (speed == 0)
+ speed = 1;
+ else if (speed > 255)
+ speed = 255;
+
+ SET_GAME_STATE (KOHR_AH_FRENZY, 1);
+ SET_GAME_STATE (KOHR_AH_VISITS, 0);
+ SET_GAME_STATE (KOHR_AH_REASONS, 0);
+ SET_GAME_STATE (KOHR_AH_PLEAD, 0);
+ SET_GAME_STATE (KOHR_AH_INFO, 0);
+ SET_GAME_STATE (URQUAN_VISITS, 0);
+ SetRaceDest (BLACK_URQUAN_SHIP,
+ BlackUrquanPtr->loc.x + best_dx,
+ BlackUrquanPtr->loc.y + best_dy,
+ (BYTE)speed, KOHR_AH_GENOCIDE_EVENT);
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hBlackUrquan);
+}
+
+static void
+pkunk_mission (void)
+{
+ HFLEETINFO hPkunk;
+ FLEET_INFO *PkunkPtr;
+
+ hPkunk = GetStarShipFromIndex (&GLOBAL (avail_race_q), PKUNK_SHIP);
+ PkunkPtr = LockFleetInfo (&GLOBAL (avail_race_q), hPkunk);
+
+ if (PkunkPtr->actual_strength)
+ {
+ BYTE MissionState;
+
+ MissionState = GET_GAME_STATE (PKUNK_MISSION);
+ if (PkunkPtr->days_left == 0 && MissionState)
+ {
+ if ((MissionState & 1)
+ /* made it to Yehat space */
+ || (PkunkPtr->loc.x == 4970
+ && PkunkPtr->loc.y == 400))
+ PkunkPtr->actual_strength = 0;
+ else if (PkunkPtr->loc.x == 502
+ && PkunkPtr->loc.y == 401
+ && GET_GAME_STATE (PKUNK_ON_THE_MOVE))
+ {
+ SET_GAME_STATE (PKUNK_ON_THE_MOVE, 0);
+ AddEvent (RELATIVE_EVENT, 3, 0, 0, ADVANCE_PKUNK_MISSION);
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hPkunk);
+ return;
+ }
+ }
+
+ if (PkunkPtr->actual_strength == 0)
+ {
+ SET_GAME_STATE (YEHAT_ABSORBED_PKUNK, 1);
+ PkunkPtr->allied_state = DEAD_GUY;
+ StartSphereTracking (YEHAT_SHIP);
+ }
+ else
+ {
+ COORD x, y;
+
+ if (!(MissionState & 1))
+ {
+ x = 4970;
+ y = 400;
+ }
+ else
+ {
+ x = 502;
+ y = 401;
+ }
+ SET_GAME_STATE (PKUNK_ON_THE_MOVE, 1);
+ SET_GAME_STATE (PKUNK_SWITCH, 0);
+ SetRaceDest (PKUNK_SHIP, x, y,
+ (BYTE)((365 >> 1) - PkunkPtr->days_left),
+ ADVANCE_PKUNK_MISSION);
+ }
+ SET_GAME_STATE (PKUNK_MISSION, MissionState + 1);
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hPkunk);
+}
+
+static void
+thradd_mission (void)
+{
+ BYTE MissionState;
+ HFLEETINFO hThradd;
+ FLEET_INFO *ThraddPtr;
+
+ hThradd = GetStarShipFromIndex (&GLOBAL (avail_race_q), THRADDASH_SHIP);
+ ThraddPtr = LockFleetInfo (&GLOBAL (avail_race_q), hThradd);
+
+ MissionState = GET_GAME_STATE (THRADD_MISSION);
+ if (ThraddPtr->actual_strength && MissionState < 3)
+ {
+ COORD x, y;
+
+ if (MissionState < 2)
+ { /* attacking */
+ x = 4879;
+ y = 7201;
+ }
+ else
+ { /* returning */
+ x = 2535;
+ y = 8358;
+ }
+
+ if (MissionState == 1)
+ { /* arrived at Kohr-Ah, engaging */
+ SIZE strength_loss;
+
+ strength_loss = (SIZE)(ThraddPtr->actual_strength >> 1);
+ ThraddPtr->growth = (BYTE)(-strength_loss / 14);
+ ThraddPtr->growth_fract = (BYTE)(((strength_loss % 14) << 8) / 14);
+ ThraddPtr->growth_err_term = 255 >> 1;
+ }
+ else
+ {
+ if (MissionState != 0)
+ { /* stop losses */
+ ThraddPtr->growth = 0;
+ ThraddPtr->growth_fract = 0;
+ }
+ }
+ SetRaceDest (THRADDASH_SHIP, x, y, 14, ADVANCE_THRADD_MISSION);
+ }
+ ++MissionState;
+ SET_GAME_STATE (THRADD_MISSION, MissionState);
+
+ if (MissionState == 4 && GET_GAME_STATE (ILWRATH_FIGHT_THRADDASH))
+ { /* returned home - notify the Ilwrath */
+ AddEvent (RELATIVE_EVENT, 0, 0, 0, ADVANCE_ILWRATH_MISSION);
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hThradd);
+}
+
+static void
+ilwrath_mission (void)
+{
+ BYTE ThraddState;
+ HFLEETINFO hIlwrath, hThradd;
+ FLEET_INFO *IlwrathPtr;
+ FLEET_INFO *ThraddPtr;
+
+ hIlwrath = GetStarShipFromIndex (&GLOBAL (avail_race_q), ILWRATH_SHIP);
+ IlwrathPtr = LockFleetInfo (&GLOBAL (avail_race_q), hIlwrath);
+ hThradd = GetStarShipFromIndex (&GLOBAL (avail_race_q), THRADDASH_SHIP);
+ ThraddPtr = LockFleetInfo (&GLOBAL (avail_race_q), hThradd);
+
+ if (IlwrathPtr->loc.x == ((2500 + 2535) >> 1)
+ && IlwrathPtr->loc.y == ((8070 + 8358) >> 1))
+ {
+ IlwrathPtr->actual_strength = 0;
+ ThraddPtr->actual_strength = 0;
+ IlwrathPtr->allied_state = DEAD_GUY;
+ ThraddPtr->allied_state = DEAD_GUY;
+ }
+ else if (IlwrathPtr->actual_strength)
+ {
+ if (!GET_GAME_STATE (ILWRATH_FIGHT_THRADDASH)
+ && (IlwrathPtr->dest_loc.x != 2500
+ || IlwrathPtr->dest_loc.y != 8070))
+ {
+ SetRaceDest (ILWRATH_SHIP, 2500, 8070, 90,
+ ADVANCE_ILWRATH_MISSION);
+ }
+ else
+ {
+#define MADD_LENGTH 128
+ SIZE strength_loss;
+
+ if (IlwrathPtr->days_left == 0)
+ { /* arrived for battle */
+ SET_GAME_STATE (ILWRATH_FIGHT_THRADDASH, 1);
+ SET_GAME_STATE (HELIX_UNPROTECTED, 1);
+ strength_loss = (SIZE)IlwrathPtr->actual_strength;
+ IlwrathPtr->growth = (BYTE)(-strength_loss / MADD_LENGTH);
+ IlwrathPtr->growth_fract =
+ (BYTE)(((strength_loss % MADD_LENGTH) << 8) / MADD_LENGTH);
+ SetRaceDest (ILWRATH_SHIP,
+ (2500 + 2535) >> 1, (8070 + 8358) >> 1,
+ MADD_LENGTH - 1, ADVANCE_ILWRATH_MISSION);
+
+ strength_loss = (SIZE)ThraddPtr->actual_strength;
+ ThraddPtr->growth = (BYTE)(-strength_loss / MADD_LENGTH);
+ ThraddPtr->growth_fract =
+ (BYTE)(((strength_loss % MADD_LENGTH) << 8) / MADD_LENGTH);
+
+ SET_GAME_STATE (THRADD_VISITS, 0);
+ if (ThraddPtr->allied_state == GOOD_GUY)
+ SetRaceAllied (THRADDASH_SHIP, FALSE);
+ }
+
+ ThraddState = GET_GAME_STATE (THRADD_MISSION);
+ if (ThraddState == 0 || ThraddState > 3)
+ { /* never went to Kohr-Ah or returned */
+ SetRaceDest (THRADDASH_SHIP,
+ (2500 + 2535) >> 1, (8070 + 8358) >> 1,
+ IlwrathPtr->days_left + 1, (BYTE)~0);
+ }
+ else if (ThraddState < 3)
+ { /* recall on the double */
+ SetRaceDest (THRADDASH_SHIP, 2535, 8358, 10,
+ ADVANCE_THRADD_MISSION);
+ SET_GAME_STATE (THRADD_MISSION, 3);
+ }
+ }
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hThradd);
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hIlwrath);
+}
+
+static void
+utwig_supox_mission (void)
+{
+ BYTE MissionState;
+ HFLEETINFO hUtwig, hSupox;
+ FLEET_INFO *UtwigPtr;
+ FLEET_INFO *SupoxPtr;
+
+ hUtwig = GetStarShipFromIndex (&GLOBAL (avail_race_q), UTWIG_SHIP);
+ UtwigPtr = LockFleetInfo (&GLOBAL (avail_race_q), hUtwig);
+ hSupox = GetStarShipFromIndex (&GLOBAL (avail_race_q), SUPOX_SHIP);
+ SupoxPtr = LockFleetInfo (&GLOBAL (avail_race_q), hSupox);
+
+ MissionState = GET_GAME_STATE (UTWIG_SUPOX_MISSION);
+ if (UtwigPtr->actual_strength && MissionState < 5)
+ {
+ if (MissionState == 1)
+ {
+ SIZE strength_loss;
+
+ AddEvent (RELATIVE_EVENT, 0, (160 >> 1), 0,
+ ADVANCE_UTWIG_SUPOX_MISSION);
+
+ strength_loss = (SIZE)(UtwigPtr->actual_strength >> 1);
+ UtwigPtr->growth = (BYTE)(-strength_loss / 160);
+ UtwigPtr->growth_fract =
+ (BYTE)(((strength_loss % 160) << 8) / 160);
+ UtwigPtr->growth_err_term = 255 >> 1;
+
+ strength_loss = (SIZE)(SupoxPtr->actual_strength >> 1);
+ if (strength_loss)
+ {
+ SupoxPtr->growth = (BYTE)(-strength_loss / 160);
+ SupoxPtr->growth_fract =
+ (BYTE)(((strength_loss % 160) << 8) / 160);
+ SupoxPtr->growth_err_term = 255 >> 1;
+ }
+
+ SET_GAME_STATE (UTWIG_WAR_NEWS, 0);
+ SET_GAME_STATE (SUPOX_WAR_NEWS, 0);
+ }
+ else if (MissionState == 2)
+ {
+ AddEvent (RELATIVE_EVENT, 0, (160 >> 1), 0,
+ ADVANCE_UTWIG_SUPOX_MISSION);
+ ++MissionState;
+ }
+ else
+ {
+ COORD ux, uy, sx, sy;
+
+ if (MissionState == 0)
+ {
+ ux = 7208;
+ uy = 7000;
+
+ sx = 6479;
+ sy = 7541;
+ }
+ else
+ {
+ ux = 8534;
+ uy = 8797;
+
+ sx = 7468;
+ sy = 9246;
+
+ UtwigPtr->growth = 0;
+ UtwigPtr->growth_fract = 0;
+ SupoxPtr->growth = 0;
+ SupoxPtr->growth_fract = 0;
+
+ SET_GAME_STATE (UTWIG_WAR_NEWS, 0);
+ SET_GAME_STATE (SUPOX_WAR_NEWS, 0);
+ }
+ SET_GAME_STATE (UTWIG_VISITS, 0);
+ SET_GAME_STATE (UTWIG_INFO, 0);
+ SET_GAME_STATE (SUPOX_VISITS, 0);
+ SET_GAME_STATE (SUPOX_INFO, 0);
+ SetRaceDest (UTWIG_SHIP, ux, uy, 21, ADVANCE_UTWIG_SUPOX_MISSION);
+ SetRaceDest (SUPOX_SHIP, sx, sy, 21, (BYTE)~0);
+ }
+ }
+ SET_GAME_STATE (UTWIG_SUPOX_MISSION, MissionState + 1);
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hSupox);
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hUtwig);
+}
+
+static void
+mycon_mission (void)
+{
+ HFLEETINFO hMycon;
+ FLEET_INFO *MyconPtr;
+
+ hMycon = GetStarShipFromIndex (&GLOBAL (avail_race_q), MYCON_SHIP);
+ MyconPtr = LockFleetInfo (&GLOBAL (avail_race_q), hMycon);
+
+ if (MyconPtr->actual_strength)
+ {
+ if (MyconPtr->growth)
+ {
+ // Head back.
+ SET_GAME_STATE (MYCON_KNOW_AMBUSH, 1);
+ SetRaceDest (MYCON_SHIP, 6392, 2200, 30, (BYTE)~0);
+
+ MyconPtr->growth = 0;
+ MyconPtr->growth_fract = 0;
+ }
+ else if (MyconPtr->loc.x != 6858 || MyconPtr->loc.y != 577)
+ SetRaceDest (MYCON_SHIP, 6858, 577, 30, ADVANCE_MYCON_MISSION);
+ // To Organon.
+ else
+ {
+ // Endure losses at Organon.
+ SIZE strength_loss;
+
+ AddEvent (RELATIVE_EVENT, 0, 14, 0, ADVANCE_MYCON_MISSION);
+ strength_loss = (SIZE)(MyconPtr->actual_strength >> 1);
+ MyconPtr->growth = (BYTE)(-strength_loss / 14);
+ MyconPtr->growth_fract = (BYTE)(((strength_loss % 14) << 8) / 14);
+ MyconPtr->growth_err_term = 255 >> 1;
+ }
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hMycon);
+}
+
diff --git a/src/uqm/gameev.h b/src/uqm/gameev.h
new file mode 100644
index 0000000..8c2e137
--- /dev/null
+++ b/src/uqm/gameev.h
@@ -0,0 +1,68 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_GAMEEV_H_
+#define UQM_GAMEEV_H_
+
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+enum
+{
+ ARILOU_ENTRANCE_EVENT = 0,
+ ARILOU_EXIT_EVENT,
+ HYPERSPACE_ENCOUNTER_EVENT,
+ KOHR_AH_VICTORIOUS_EVENT,
+ ADVANCE_PKUNK_MISSION,
+ ADVANCE_THRADD_MISSION,
+ ZOQFOT_DISTRESS_EVENT,
+ ZOQFOT_DEATH_EVENT,
+ SHOFIXTI_RETURN_EVENT,
+ ADVANCE_UTWIG_SUPOX_MISSION,
+ KOHR_AH_GENOCIDE_EVENT,
+ SPATHI_SHIELD_EVENT,
+ ADVANCE_ILWRATH_MISSION,
+ ADVANCE_MYCON_MISSION,
+ ARILOU_UMGAH_CHECK,
+ YEHAT_REBEL_EVENT,
+ SLYLANDRO_RAMP_UP,
+ SLYLANDRO_RAMP_DOWN,
+
+ NUM_EVENTS
+};
+
+typedef enum
+{
+ CLOSING = 0,
+ OPENING
+} ARILOU_GATE_STATE;
+
+extern void AddInitialGameEvents (void);
+extern void EventHandler (BYTE selector);
+extern void SetRaceDest (BYTE which_race, COORD x, COORD y, BYTE days_left,
+ BYTE func_index);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_GAMEEV_H_ */
diff --git a/src/uqm/gameinp.c b/src/uqm/gameinp.c
new file mode 100644
index 0000000..66e667f
--- /dev/null
+++ b/src/uqm/gameinp.c
@@ -0,0 +1,496 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "controls.h"
+#include "battlecontrols.h"
+#include "init.h"
+#include "intel.h"
+ // For computer_intelligence
+#ifdef NETPLAY
+# include "supermelee/netplay/netmelee.h"
+#endif
+#include "settings.h"
+#include "sounds.h"
+#include "tactrans.h"
+#include "uqmdebug.h"
+#include "libs/async.h"
+#include "libs/inplib.h"
+#include "libs/timelib.h"
+#include "libs/threadlib.h"
+
+
+#define ACCELERATION_INCREMENT (ONE_SECOND / 12)
+#define MENU_REPEAT_DELAY (ONE_SECOND / 2)
+
+
+typedef struct
+{
+ BOOLEAN (*InputFunc) (void *pInputState);
+} INPUT_STATE_DESC;
+
+/* These static variables are the values that are set by the controllers. */
+
+typedef struct
+{
+ DWORD key [NUM_TEMPLATES][NUM_KEYS];
+ DWORD menu [NUM_MENU_KEYS];
+} MENU_ANNOTATIONS;
+
+
+CONTROL_TEMPLATE PlayerControls[NUM_PLAYERS];
+CONTROLLER_INPUT_STATE CurrentInputState, PulsedInputState;
+static CONTROLLER_INPUT_STATE CachedInputState, OldInputState;
+static MENU_ANNOTATIONS RepeatDelays, Times;
+static DWORD GestaltRepeatDelay, GestaltTime;
+static BOOLEAN OldGestalt, CachedGestalt;
+static DWORD _max_accel, _min_accel, _step_accel;
+static BOOLEAN _gestalt_keys;
+
+static MENU_SOUND_FLAGS sound_0, sound_1;
+
+volatile CONTROLLER_INPUT_STATE ImmediateInputState;
+
+volatile BOOLEAN ExitRequested;
+volatile BOOLEAN GamePaused;
+
+static InputFrameCallback *inputCallback;
+
+static void
+_clear_menu_state (void)
+{
+ int i, j;
+ for (i = 0; i < NUM_TEMPLATES; i++)
+ {
+ for (j = 0; j < NUM_KEYS; j++)
+ {
+ PulsedInputState.key[i][j] = 0;
+ CachedInputState.key[i][j] = 0;
+ }
+ }
+ for (i = 0; i < NUM_MENU_KEYS; i++)
+ {
+ PulsedInputState.menu[i] = 0;
+ CachedInputState.menu[i] = 0;
+ }
+ CachedGestalt = FALSE;
+}
+
+void
+ResetKeyRepeat (void)
+{
+ DWORD initTime = GetTimeCounter ();
+ int i, j;
+ for (i = 0; i < NUM_TEMPLATES; i++)
+ {
+ for (j = 0; j < NUM_KEYS; j++)
+ {
+ RepeatDelays.key[i][j] = _max_accel;
+ Times.key[i][j] = initTime;
+ }
+ }
+ for (i = 0; i < NUM_MENU_KEYS; i++)
+ {
+ RepeatDelays.menu[i] = _max_accel;
+ Times.menu[i] = initTime;
+ }
+ GestaltRepeatDelay = _max_accel;
+ GestaltTime = initTime;
+}
+
+static void
+_check_for_pulse (int *current, int *cached, int *old, DWORD *accel,
+ DWORD *newtime, DWORD *oldtime)
+{
+ if (*cached && *old)
+ {
+ if (*newtime - *oldtime < *accel)
+ {
+ *current = 0;
+ }
+ else
+ {
+ *current = *cached;
+ if (*accel > _min_accel)
+ *accel -= _step_accel;
+ if (*accel < _min_accel)
+ *accel = _min_accel;
+ *oldtime = *newtime;
+ }
+ }
+ else
+ {
+ *current = *cached;
+ *oldtime = *newtime;
+ *accel = _max_accel;
+ }
+}
+
+/* BUG: If a key from a currently unused control template is held,
+ * this will affect the gestalt repeat rate. This isn't a problem
+ * *yet*, but it will be once the user gets to define control
+ * templates on his own --McM */
+static void
+_check_gestalt (DWORD NewTime)
+{
+ BOOLEAN CurrentGestalt;
+ int i, j;
+ OldGestalt = CachedGestalt;
+
+ CachedGestalt = 0;
+ CurrentGestalt = 0;
+ for (i = 0; i < NUM_TEMPLATES; i++)
+ {
+ for (j = 0; j < NUM_KEYS; j++)
+ {
+ CachedGestalt |= ImmediateInputState.key[i][j];
+ CurrentGestalt |= PulsedInputState.key[i][j];
+ }
+ }
+ for (i = 0; i < NUM_MENU_KEYS; i++)
+ {
+ CachedGestalt |= ImmediateInputState.menu[i];
+ CurrentGestalt |= PulsedInputState.menu[i];
+ }
+
+ if (OldGestalt && CachedGestalt)
+ {
+ if (NewTime - GestaltTime < GestaltRepeatDelay)
+ {
+ for (i = 0; i < NUM_TEMPLATES; i++)
+ {
+ for (j = 0; j < NUM_KEYS; j++)
+ {
+ PulsedInputState.key[i][j] = 0;
+ }
+ }
+ for (i = 0; i < NUM_MENU_KEYS; i++)
+ {
+ PulsedInputState.menu[i] = 0;
+ }
+ }
+ else
+ {
+ for (i = 0; i < NUM_TEMPLATES; i++)
+ {
+ for (j = 0; j < NUM_KEYS; j++)
+ {
+ PulsedInputState.key[i][j] = CachedInputState.key[i][j];
+ }
+ }
+ for (i = 0; i < NUM_MENU_KEYS; i++)
+ {
+ PulsedInputState.menu[i] = CachedInputState.menu[i];
+ }
+ if (GestaltRepeatDelay > _min_accel)
+ GestaltRepeatDelay -= _step_accel;
+ if (GestaltRepeatDelay < _min_accel)
+ GestaltRepeatDelay = _min_accel;
+ GestaltTime = NewTime;
+ }
+ }
+ else
+ {
+ for (i = 0; i < NUM_TEMPLATES; i++)
+ {
+ for (j = 0; j < NUM_KEYS; j++)
+ {
+ PulsedInputState.key[i][j] = CachedInputState.key[i][j];
+ }
+ }
+ for (i = 0; i < NUM_MENU_KEYS; i++)
+ {
+ PulsedInputState.menu[i] = CachedInputState.menu[i];
+ }
+ GestaltTime = NewTime;
+ GestaltRepeatDelay = _max_accel;
+ }
+}
+
+void
+UpdateInputState (void)
+{
+ DWORD NewTime;
+ /* First, if the game is, in fact, paused, we stall until
+ * unpaused. Every thread with control over game logic calls
+ * UpdateInputState routinely, so we handle pause and exit
+ * state updates here. */
+
+ // Automatically pause and enter low-activity state while inactive,
+ // for example, window minimized.
+ if (!GameActive)
+ SleepGame ();
+
+ if (GamePaused)
+ PauseGame ();
+
+ if (ExitRequested)
+ ConfirmExit ();
+
+ CurrentInputState = ImmediateInputState;
+ OldInputState = CachedInputState;
+ CachedInputState = ImmediateInputState;
+ BeginInputFrame ();
+ NewTime = GetTimeCounter ();
+ if (_gestalt_keys)
+ {
+ _check_gestalt (NewTime);
+ }
+ else
+ {
+ int i, j;
+ for (i = 0; i < NUM_TEMPLATES; i++)
+ {
+ for (j = 0; j < NUM_KEYS; j++)
+ {
+ _check_for_pulse (&PulsedInputState.key[i][j],
+ &CachedInputState.key[i][j], &OldInputState.key[i][j],
+ &RepeatDelays.key[i][j], &NewTime, &Times.key[i][j]);
+ }
+ }
+ for (i = 0; i < NUM_MENU_KEYS; i++)
+ {
+ _check_for_pulse (&PulsedInputState.menu[i],
+ &CachedInputState.menu[i], &OldInputState.menu[i],
+ &RepeatDelays.menu[i], &NewTime, &Times.menu[i]);
+ }
+ }
+
+ if (CurrentInputState.menu[KEY_PAUSE])
+ GamePaused = TRUE;
+
+ if (CurrentInputState.menu[KEY_EXIT])
+ ExitRequested = TRUE;
+
+#if defined(DEBUG) || defined(USE_DEBUG_KEY)
+ if (PulsedInputState.menu[KEY_DEBUG])
+ debugKeyPressedSynchronous ();
+#endif
+}
+
+InputFrameCallback *
+SetInputCallback (InputFrameCallback *callback)
+{
+ InputFrameCallback *old = inputCallback;
+
+ // Replacing an existing callback with another is not a problem,
+ // but currently this should never happen, which is why the assert.
+ assert (!old || !callback);
+ inputCallback = callback;
+
+ return old;
+}
+
+void
+SetMenuRepeatDelay (DWORD min, DWORD max, DWORD step, BOOLEAN gestalt)
+{
+ _min_accel = min;
+ _max_accel = max;
+ _step_accel = step;
+ _gestalt_keys = gestalt;
+ //_clear_menu_state ();
+ ResetKeyRepeat ();
+}
+
+void
+SetDefaultMenuRepeatDelay (void)
+{
+ _min_accel = ACCELERATION_INCREMENT;
+ _max_accel = MENU_REPEAT_DELAY;
+ _step_accel = ACCELERATION_INCREMENT;
+ _gestalt_keys = FALSE;
+ //_clear_menu_state ();
+ ResetKeyRepeat ();
+}
+
+void
+FlushInput (void)
+{
+ TFB_ResetControls ();
+ _clear_menu_state ();
+}
+
+static MENU_SOUND_FLAGS
+MenuKeysToSoundFlags (const CONTROLLER_INPUT_STATE *state)
+{
+ MENU_SOUND_FLAGS soundFlags;
+
+ soundFlags = MENU_SOUND_NONE;
+ if (state->menu[KEY_MENU_UP])
+ soundFlags |= MENU_SOUND_UP;
+ if (state->menu[KEY_MENU_DOWN])
+ soundFlags |= MENU_SOUND_DOWN;
+ if (state->menu[KEY_MENU_LEFT])
+ soundFlags |= MENU_SOUND_LEFT;
+ if (state->menu[KEY_MENU_RIGHT])
+ soundFlags |= MENU_SOUND_RIGHT;
+ if (state->menu[KEY_MENU_SELECT])
+ soundFlags |= MENU_SOUND_SELECT;
+ if (state->menu[KEY_MENU_CANCEL])
+ soundFlags |= MENU_SOUND_CANCEL;
+ if (state->menu[KEY_MENU_SPECIAL])
+ soundFlags |= MENU_SOUND_SPECIAL;
+ if (state->menu[KEY_MENU_PAGE_UP])
+ soundFlags |= MENU_SOUND_PAGEUP;
+ if (state->menu[KEY_MENU_PAGE_DOWN])
+ soundFlags |= MENU_SOUND_PAGEDOWN;
+ if (state->menu[KEY_MENU_DELETE])
+ soundFlags |= MENU_SOUND_DELETE;
+ if (state->menu[KEY_MENU_BACKSPACE])
+ soundFlags |= MENU_SOUND_DELETE;
+
+ return soundFlags;
+}
+
+void
+DoInput (void *pInputState, BOOLEAN resetInput)
+{
+ if (resetInput)
+ FlushInput ();
+
+ do
+ {
+ MENU_SOUND_FLAGS soundFlags;
+ Async_process ();
+ TaskSwitch ();
+
+ UpdateInputState ();
+
+#if DEMO_MODE || CREATE_JOURNAL
+ if (ArrowInput != DemoInput)
+#endif
+ {
+#if CREATE_JOURNAL
+ JournalInput (InputState);
+#endif /* CREATE_JOURNAL */
+ }
+
+ soundFlags = MenuKeysToSoundFlags (&PulsedInputState);
+
+ if (MenuSounds && (soundFlags & (sound_0 | sound_1)))
+ {
+ SOUND S;
+
+ S = MenuSounds;
+ if (soundFlags & sound_1)
+ S = SetAbsSoundIndex (S, MENU_SOUND_SUCCESS);
+
+ PlaySoundEffect (S, 0, NotPositional (), NULL, 0);
+ }
+
+ if (inputCallback)
+ inputCallback ();
+
+ } while (((INPUT_STATE_DESC*)pInputState)->InputFunc (pInputState));
+
+ if (resetInput)
+ FlushInput ();
+}
+
+void
+SetMenuSounds (MENU_SOUND_FLAGS s0, MENU_SOUND_FLAGS s1)
+{
+ sound_0 = s0;
+ sound_1 = s1;
+}
+
+void
+GetMenuSounds (MENU_SOUND_FLAGS *s0, MENU_SOUND_FLAGS *s1)
+{
+ *s0 = sound_0;
+ *s1 = sound_1;
+}
+
+static BATTLE_INPUT_STATE
+ControlInputToBattleInput (const int *keyState)
+{
+ BATTLE_INPUT_STATE InputState = 0;
+
+ if (keyState[KEY_UP])
+ InputState |= BATTLE_THRUST;
+ if (keyState[KEY_LEFT])
+ InputState |= BATTLE_LEFT;
+ if (keyState[KEY_RIGHT])
+ InputState |= BATTLE_RIGHT;
+ if (keyState[KEY_WEAPON])
+ InputState |= BATTLE_WEAPON;
+ if (keyState[KEY_SPECIAL])
+ InputState |= BATTLE_SPECIAL;
+ if (keyState[KEY_ESCAPE])
+ InputState |= BATTLE_ESCAPE;
+ if (keyState[KEY_DOWN])
+ InputState |= BATTLE_DOWN;
+
+ return InputState;
+}
+
+BATTLE_INPUT_STATE
+CurrentInputToBattleInput (COUNT player)
+{
+ return ControlInputToBattleInput(
+ CurrentInputState.key[PlayerControls[player]]);
+}
+
+BATTLE_INPUT_STATE
+PulsedInputToBattleInput (COUNT player)
+{
+ return ControlInputToBattleInput(
+ PulsedInputState.key[PlayerControls[player]]);
+}
+
+BOOLEAN
+AnyButtonPress (BOOLEAN CheckSpecial)
+{
+ int i, j;
+ (void) CheckSpecial; // Ignored
+ UpdateInputState ();
+ for (i = 0; i < NUM_TEMPLATES; i++)
+ {
+ for (j = 0; j < NUM_KEYS; j++)
+ {
+ if (CurrentInputState.key[i][j])
+ return TRUE;
+ }
+ }
+ for (i = 0; i < NUM_MENU_KEYS; i++)
+ {
+ if (CurrentInputState.menu[i])
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOLEAN
+ConfirmExit (void)
+{
+ DWORD old_max_accel, old_min_accel, old_step_accel;
+ BOOLEAN old_gestalt_keys, result;
+
+ old_max_accel = _max_accel;
+ old_min_accel = _min_accel;
+ old_step_accel = _step_accel;
+ old_gestalt_keys = _gestalt_keys;
+
+ SetDefaultMenuRepeatDelay ();
+
+ result = DoConfirmExit ();
+
+ SetMenuRepeatDelay (old_min_accel, old_max_accel, old_step_accel,
+ old_gestalt_keys);
+ return result;
+}
+
diff --git a/src/uqm/gameopt.c b/src/uqm/gameopt.c
new file mode 100644
index 0000000..ba42a3b
--- /dev/null
+++ b/src/uqm/gameopt.c
@@ -0,0 +1,1347 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gameopt.h"
+
+#include "build.h"
+#include "colors.h"
+#include "controls.h"
+#include "starmap.h"
+#include "menustat.h"
+#include "sis.h"
+#include "units.h"
+#include "gamestr.h"
+#include "options.h"
+#include "save.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "util.h"
+#include "libs/graphics/gfx_common.h"
+
+#include <ctype.h>
+
+extern FRAME PlayFrame;
+
+#define MAX_SAVED_GAMES 50
+#define SUMMARY_X_OFFS 14
+#define SUMMARY_SIDE_OFFS 7
+#define SAVES_PER_PAGE 5
+
+#define MAX_NAME_SIZE SIS_NAME_SIZE
+
+static COUNT lastUsedSlot;
+
+static NamingCallback *namingCB;
+
+void
+ConfirmSaveLoad (STAMP *MsgStamp)
+{
+ RECT r, clip_r;
+ TEXT t;
+
+ SetContextFont (StarConFont);
+ GetContextClipRect (&clip_r);
+
+ t.baseline.x = clip_r.extent.width >> 1;
+ t.baseline.y = (clip_r.extent.height >> 1) + 3;
+ t.align = ALIGN_CENTER;
+ t.CharCount = (COUNT)~0;
+ if (MsgStamp)
+ t.pStr = GAME_STRING (SAVEGAME_STRING_BASE + 0);
+ // "Saving . . ."
+ else
+ t.pStr = GAME_STRING (SAVEGAME_STRING_BASE + 1);
+ // "Loading . . ."
+ TextRect (&t, &r, NULL);
+ r.corner.x -= 4;
+ r.corner.y -= 4;
+ r.extent.width += 8;
+ r.extent.height += 8;
+ if (MsgStamp)
+ {
+ *MsgStamp = SaveContextFrame (&r);
+ }
+ DrawStarConBox (&r, 2,
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19),
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F),
+ TRUE, BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x0F));
+ font_DrawText (&t);
+}
+
+enum
+{
+ SAVE_GAME = 0,
+ LOAD_GAME,
+ QUIT_GAME,
+ SETTINGS,
+ EXIT_GAME_MENU,
+};
+
+enum
+{
+ SOUND_ON_SETTING,
+ SOUND_OFF_SETTING,
+ MUSIC_ON_SETTING,
+ MUSIC_OFF_SETTING,
+ CYBORG_OFF_SETTING,
+ CYBORG_NORMAL_SETTING,
+ CYBORG_DOUBLE_SETTING,
+ CYBORG_SUPER_SETTING,
+ CHANGE_CAPTAIN_SETTING,
+ CHANGE_SHIP_SETTING,
+ EXIT_SETTINGS_MENU,
+};
+
+static void
+FeedbackSetting (BYTE which_setting)
+{
+ UNICODE buf[128];
+ const char *tmpstr;
+
+ buf[0] = '\0';
+ // pre-terminate buffer in case snprintf() overflows
+ buf[sizeof (buf) - 1] = '\0';
+
+ switch (which_setting)
+ {
+ case SOUND_ON_SETTING:
+ case SOUND_OFF_SETTING:
+ snprintf (buf, sizeof (buf) - 1, "%s %s",
+ GAME_STRING (OPTION_STRING_BASE + 0),
+ GLOBAL (glob_flags) & SOUND_DISABLED
+ ? GAME_STRING (OPTION_STRING_BASE + 3) :
+ GAME_STRING (OPTION_STRING_BASE + 4));
+ break;
+ case MUSIC_ON_SETTING:
+ case MUSIC_OFF_SETTING:
+ snprintf (buf, sizeof (buf) - 1, "%s %s",
+ GAME_STRING (OPTION_STRING_BASE + 1),
+ GLOBAL (glob_flags) & MUSIC_DISABLED
+ ? GAME_STRING (OPTION_STRING_BASE + 3) :
+ GAME_STRING (OPTION_STRING_BASE + 4));
+ break;
+ case CYBORG_OFF_SETTING:
+ case CYBORG_NORMAL_SETTING:
+ case CYBORG_DOUBLE_SETTING:
+ case CYBORG_SUPER_SETTING:
+ if (optWhichMenu == OPT_PC &&
+ which_setting > CYBORG_NORMAL_SETTING)
+ {
+ if (which_setting == CYBORG_DOUBLE_SETTING)
+ tmpstr = "+";
+ else
+ tmpstr = "++";
+ }
+ else
+ tmpstr = "";
+ snprintf (buf, sizeof (buf) - 1, "%s %s%s",
+ GAME_STRING (OPTION_STRING_BASE + 2),
+ !(GLOBAL (glob_flags) & CYBORG_ENABLED)
+ ? GAME_STRING (OPTION_STRING_BASE + 3) :
+ GAME_STRING (OPTION_STRING_BASE + 4),
+ tmpstr);
+ break;
+ case CHANGE_CAPTAIN_SETTING:
+ case CHANGE_SHIP_SETTING:
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (NAMING_STRING_BASE + 0));
+ break;
+ }
+
+ DrawStatusMessage (buf);
+}
+
+#define DDSHS_NORMAL 0
+#define DDSHS_EDIT 1
+#define DDSHS_BLOCKCUR 2
+
+static const RECT captainNameRect = {
+ /* .corner = */ {
+ /* .x = */ 3,
+ /* .y = */ 10
+ }, /* .extent = */ {
+ /* .width = */ SHIP_NAME_WIDTH - 2,
+ /* .height = */ SHIP_NAME_HEIGHT
+ }
+};
+static const RECT shipNameRect = {
+ /* .corner = */ {
+ /* .x = */ 2,
+ /* .y = */ 20
+ }, /* .extent = */ {
+ /* .width = */ SHIP_NAME_WIDTH,
+ /* .height = */ SHIP_NAME_HEIGHT
+ }
+};
+
+
+static BOOLEAN
+DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos,
+ COUNT state)
+{
+ RECT r;
+ TEXT lf;
+ Color BackGround, ForeGround;
+ FONT Font;
+
+ {
+ if (nameCaptain)
+ { // Naming the captain
+ Font = TinyFont;
+ r = captainNameRect;
+ lf.baseline.x = r.corner.x + (r.extent.width >> 1) - 1;
+
+ BackGround = BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09);
+ ForeGround = BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B);
+ }
+ else
+ { // Naming the flagship
+ Font = StarConFont;
+ r = shipNameRect;
+ lf.baseline.x = r.corner.x + (r.extent.width >> 1);
+
+ BackGround = BUILD_COLOR (MAKE_RGB15 (0x0F, 0x00, 0x00), 0x2D);
+ ForeGround = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D);
+ }
+
+ lf.baseline.y = r.corner.y + r.extent.height - 1;
+ lf.align = ALIGN_CENTER;
+ }
+
+ SetContext (StatusContext);
+ SetContextFont (Font);
+ lf.pStr = Str;
+ lf.CharCount = (COUNT)~0;
+
+ if (!(state & DDSHS_EDIT))
+ { // normal state
+ if (nameCaptain)
+ DrawCaptainsName ();
+ else
+ DrawFlagshipName (TRUE);
+ }
+ else
+ { // editing state
+ COUNT i;
+ RECT text_r;
+ BYTE char_deltas[MAX_NAME_SIZE];
+ BYTE *pchar_deltas;
+
+ TextRect (&lf, &text_r, char_deltas);
+ if ((text_r.extent.width + 2) >= r.extent.width)
+ { // the text does not fit the input box size and so
+ // will not fit when displayed later
+ // disallow the change
+ return (FALSE);
+ }
+
+ PreUpdateFlashRect ();
+
+ SetContextForeGroundColor (BackGround);
+ DrawFilledRectangle (&r);
+
+ pchar_deltas = char_deltas;
+ for (i = CursorPos; i > 0; --i)
+ text_r.corner.x += *pchar_deltas++;
+ if (CursorPos < lf.CharCount) /* end of line */
+ --text_r.corner.x;
+
+ if (state & DDSHS_BLOCKCUR)
+ { // Use block cursor for keyboardless systems
+ if (CursorPos == lf.CharCount)
+ { // cursor at end-line -- use insertion point
+ text_r.extent.width = 1;
+ }
+ else if (CursorPos + 1 == lf.CharCount)
+ { // extra pixel for last char margin
+ text_r.extent.width = (SIZE)*pchar_deltas + 2;
+ }
+ else
+ { // normal mid-line char
+ text_r.extent.width = (SIZE)*pchar_deltas + 1;
+ }
+ }
+ else
+ { // Insertion point cursor
+ text_r.extent.width = 1;
+ }
+
+ text_r.corner.y = r.corner.y;
+ text_r.extent.height = r.extent.height;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&text_r);
+
+ SetContextForeGroundColor (ForeGround);
+ font_DrawText (&lf);
+
+ PostUpdateFlashRect ();
+ }
+
+ return (TRUE);
+}
+
+static BOOLEAN
+OnNameChange (TEXTENTRY_STATE *pTES)
+{
+ bool nameCaptain = (bool) pTES->CbParam;
+ COUNT hl = DDSHS_EDIT;
+
+ if (pTES->JoystickMode)
+ hl |= DDSHS_BLOCKCUR;
+
+ return DrawNameString (nameCaptain, pTES->BaseStr, pTES->CursorPos, hl);
+}
+
+static void
+NameCaptainOrShip (bool nameCaptain)
+{
+ UNICODE buf[MAX_NAME_SIZE] = "";
+ TEXTENTRY_STATE tes;
+ UNICODE *Setting;
+
+ SetContext (StatusContext);
+ SetFlashRect (nameCaptain ? &captainNameRect : &shipNameRect);
+
+ DrawNameString (nameCaptain, buf, 0, DDSHS_EDIT);
+
+ DrawStatusMessage (GAME_STRING (NAMING_STRING_BASE + 0));
+
+ if (nameCaptain)
+ {
+ Setting = GLOBAL_SIS (CommanderName);
+ tes.MaxSize = sizeof (GLOBAL_SIS (CommanderName));
+ }
+ else
+ {
+ Setting = GLOBAL_SIS (ShipName);
+ tes.MaxSize = sizeof (GLOBAL_SIS (ShipName));
+ }
+
+ // text entry setup
+ tes.Initialized = FALSE;
+ tes.BaseStr = buf;
+ tes.CursorPos = 0;
+ tes.CbParam = (void*) nameCaptain;
+ tes.ChangeCallback = OnNameChange;
+ tes.FrameCallback = 0;
+
+ if (DoTextEntry (&tes))
+ utf8StringCopy (Setting, tes.MaxSize, buf);
+ else
+ utf8StringCopy (buf, sizeof (buf), Setting);
+
+ SetFlashRect (SFR_MENU_3DO);
+
+ DrawNameString (nameCaptain, buf, 0, DDSHS_NORMAL);
+
+ if (namingCB)
+ namingCB ();
+}
+
+static BOOLEAN
+DrawSaveNameString (UNICODE *Str, COUNT CursorPos, COUNT state, COUNT gameIndex)
+{
+ RECT r;
+ TEXT lf;
+ Color BackGround, ForeGround;
+ FONT Font;
+ UNICODE fullStr[256], dateStr[80];
+
+ DateToString (dateStr, sizeof dateStr, GLOBAL(GameClock.month_index),
+ GLOBAL(GameClock.day_index), GLOBAL(GameClock.year_index));
+ strncat (dateStr, ": ", sizeof(dateStr) - strlen(dateStr) -1);
+ snprintf (fullStr, sizeof fullStr, "%s%s", dateStr, Str);
+
+ SetContextForeGroundColor (BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33));
+ r.extent.width = 15;
+ if (MAX_SAVED_GAMES > 99)
+ r.extent.width += 5;
+ r.extent.height = 11;
+ r.corner.x = 8;
+ r.corner.y = (160 + ((gameIndex % SAVES_PER_PAGE) * 13));
+ DrawRectangle (&r);
+
+ r.extent.width = (204 - SAFE_X);
+ r.corner.x = (30 + SAFE_X);
+ DrawRectangle (&r);
+
+ Font = TinyFont;
+ lf.baseline.x = r.corner.x + 3;
+ lf.baseline.y = r.corner.y + 8;
+
+ BackGround = BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33);
+ ForeGround = BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01);
+
+ lf.align = ALIGN_LEFT;
+
+ SetContextFont (Font);
+ lf.pStr = fullStr;
+ lf.CharCount = (COUNT)~0;
+
+ if (!(state & DDSHS_EDIT))
+ {
+ TEXT t;
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ t.baseline.x = r.corner.x + 3;
+ t.baseline.y = r.corner.y + 8;
+ t.align = ALIGN_LEFT;
+ t.pStr = Str;
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (CAPTAIN_NAME_TEXT_COLOR);
+ font_DrawText (&lf);
+ }
+ else
+ { // editing state
+ COUNT i, FullCursorPos;
+ RECT text_r;
+ BYTE char_deltas[256];
+ BYTE *pchar_deltas;
+
+ TextRect (&lf, &text_r, char_deltas);
+ if ((text_r.extent.width + 2) >= r.extent.width)
+ { // the text does not fit the input box size and so
+ // will not fit when displayed later
+ // disallow the change
+ return (FALSE);
+ }
+
+ PreUpdateFlashRect ();
+
+ SetContextForeGroundColor (BackGround);
+ DrawFilledRectangle (&r);
+
+ pchar_deltas = char_deltas;
+
+ FullCursorPos = CursorPos + strlen(dateStr) - 1;
+ for (i = FullCursorPos; i > 0; --i)
+ text_r.corner.x += *pchar_deltas++;
+
+ if (FullCursorPos < lf.CharCount) /* end of line */
+ --text_r.corner.x;
+
+ if (state & DDSHS_BLOCKCUR)
+ { // Use block cursor for keyboardless systems
+ if (FullCursorPos == lf.CharCount)
+ { // cursor at end-line -- use insertion point
+ text_r.extent.width = 1;
+ }
+ else if (FullCursorPos + 1 == lf.CharCount)
+ { // extra pixel for last char margin
+ text_r.extent.width = (SIZE)*pchar_deltas + 2;
+ }
+ else
+ { // normal mid-line char
+ text_r.extent.width = (SIZE)*pchar_deltas + 1;
+ }
+ }
+ else
+ { // Insertion point cursor
+ text_r.extent.width = 1;
+ }
+
+ text_r.corner.y = r.corner.y;
+ text_r.extent.height = r.extent.height;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&text_r);
+
+ SetContextForeGroundColor (ForeGround);
+ font_DrawText (&lf);
+ PostUpdateFlashRect ();
+ }
+
+ return (TRUE);
+}
+
+static BOOLEAN
+OnSaveNameChange (TEXTENTRY_STATE *pTES)
+{
+ COUNT hl = DDSHS_EDIT;
+ COUNT *gameIndex = pTES->CbParam;
+
+ if (pTES->JoystickMode)
+ hl |= DDSHS_BLOCKCUR;
+
+ return DrawSaveNameString (pTES->BaseStr, pTES->CursorPos, hl, *gameIndex);
+}
+
+static BOOLEAN
+NameSaveGame (COUNT gameIndex, UNICODE *buf)
+{
+ TEXTENTRY_STATE tes;
+ COUNT CursPos = strlen(buf);
+ COUNT *gIndex = HMalloc (sizeof (COUNT));
+ RECT r;
+ *gIndex = gameIndex;
+
+ DrawSaveNameString (buf, CursPos, DDSHS_EDIT, gameIndex);
+
+ tes.MaxSize = SAVE_NAME_SIZE;
+
+ // text entry setup
+ tes.Initialized = FALSE;
+ tes.BaseStr = buf;
+ tes.CursorPos = CursPos;
+ tes.CbParam = gIndex;
+ tes.ChangeCallback = OnSaveNameChange;
+ tes.FrameCallback = 0;
+ r.extent.width = (204 - SAFE_X);
+ r.extent.height = 11;
+ r.corner.x = (30 + SAFE_X);
+ r.corner.y = (160 + ((gameIndex % SAVES_PER_PAGE) * 13));
+ SetFlashRect (&r);
+
+ if (!DoTextEntry (&tes))
+ buf[0] = 0;
+
+ SetFlashRect(NULL);
+
+ DrawSaveNameString (buf, CursPos, DDSHS_NORMAL, gameIndex);
+
+ if (namingCB)
+ namingCB ();
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ HFree (gIndex);
+
+ SetFlashRect (NULL);
+
+ if (tes.Success)
+ return (TRUE);
+ else
+ return (FALSE);
+}
+
+void
+SetNamingCallback (NamingCallback *callback)
+{
+ namingCB = callback;
+}
+
+static BOOLEAN
+DoSettings (MENU_STATE *pMS)
+{
+ BYTE cur_speed;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ cur_speed = (GLOBAL (glob_flags) & COMBAT_SPEED_MASK) >> COMBAT_SPEED_SHIFT;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL]
+ || (PulsedInputState.menu[KEY_MENU_SELECT]
+ && pMS->CurState == EXIT_SETTINGS_MENU))
+ {
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ switch (pMS->CurState)
+ {
+ case SOUND_ON_SETTING:
+ case SOUND_OFF_SETTING:
+ ToggleSoundEffect ();
+ pMS->CurState ^= 1;
+ DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
+ break;
+ case MUSIC_ON_SETTING:
+ case MUSIC_OFF_SETTING:
+ ToggleMusic ();
+ pMS->CurState ^= 1;
+ DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
+ break;
+ case CHANGE_CAPTAIN_SETTING:
+ case CHANGE_SHIP_SETTING:
+ NameCaptainOrShip (pMS->CurState == CHANGE_CAPTAIN_SETTING);
+ break;
+ default:
+ if (cur_speed++ < NUM_COMBAT_SPEEDS - 1)
+ GLOBAL (glob_flags) |= CYBORG_ENABLED;
+ else
+ {
+ cur_speed = 0;
+ GLOBAL (glob_flags) &= ~CYBORG_ENABLED;
+ }
+ GLOBAL (glob_flags) =
+ ((GLOBAL (glob_flags) & ~COMBAT_SPEED_MASK)
+ | (cur_speed << COMBAT_SPEED_SHIFT));
+ pMS->CurState = CYBORG_OFF_SETTING + cur_speed;
+ DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
+ }
+
+ FeedbackSetting (pMS->CurState);
+ }
+ else if (DoMenuChooser (pMS, PM_SOUND_ON))
+ FeedbackSetting (pMS->CurState);
+
+ return TRUE;
+}
+
+static void
+SettingsMenu (void)
+{
+ MENU_STATE MenuState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+ MenuState.CurState = SOUND_ON_SETTING;
+
+ DrawMenuStateStrings (PM_SOUND_ON, MenuState.CurState);
+ FeedbackSetting (MenuState.CurState);
+
+ MenuState.InputFunc = DoSettings;
+ DoInput (&MenuState, FALSE);
+
+ DrawStatusMessage (NULL);
+}
+
+typedef struct
+{
+ SUMMARY_DESC summary[MAX_SAVED_GAMES];
+ BOOLEAN saving;
+ // TRUE when saving, FALSE when loading
+ BOOLEAN success;
+ // TRUE when load/save succeeded
+ FRAME SummaryFrame;
+
+} PICK_GAME_STATE;
+
+static void
+DrawBlankSavegameDisplay (PICK_GAME_STATE *pickState)
+{
+ STAMP s;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
+ GetFrameCount (pickState->SummaryFrame) - 1);
+ DrawStamp (&s);
+}
+
+static void
+DrawSaveLoad (PICK_GAME_STATE *pickState)
+{
+ STAMP s;
+
+ s.origin.x = SUMMARY_X_OFFS + 1;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
+ GetFrameCount (pickState->SummaryFrame) - 2);
+ if (pickState->saving)
+ s.frame = DecFrameIndex (s.frame);
+ DrawStamp (&s);
+}
+
+static void
+DrawSavegameCargo (SIS_STATE *sisState)
+{
+ COUNT i;
+ STAMP s;
+ TEXT t;
+ UNICODE buf[40];
+ static const Color cargo_color[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x02, 0x0E, 0x13), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x19, 0x00, 0x00), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x10, 0x10, 0x10), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x03, 0x05, 0x1E), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x18, 0x00), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x1B, 0x00), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1E, 0x0D, 0x00), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x14, 0x00, 0x14), 0x05),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x19), 0x00),
+ };
+#define ELEMENT_ORG_Y 17
+#define ELEMENT_SPACING_Y 12
+#define ELEMENT_SPACING_X 36
+
+ SetContext (SpaceContext);
+ BatchGraphics ();
+ SetContextFont (StarConFont);
+
+ // setup element icons
+ s.frame = SetAbsFrameIndex (MiscDataFrame,
+ (NUM_SCANDOT_TRANSITIONS << 1) + 3);
+ s.origin.x = 7 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 3;
+ s.origin.y = ELEMENT_ORG_Y;
+ // setup element amounts
+ t.baseline.x = 33 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 3;
+ t.baseline.y = ELEMENT_ORG_Y + 3;
+ t.align = ALIGN_RIGHT;
+ t.pStr = buf;
+
+ // draw element icons and amounts
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i)
+ {
+ if (i == NUM_ELEMENT_CATEGORIES / 2)
+ {
+ s.origin.x += ELEMENT_SPACING_X;
+ s.origin.y = ELEMENT_ORG_Y;
+ t.baseline.x += ELEMENT_SPACING_X;
+ t.baseline.y = ELEMENT_ORG_Y + 3;
+ }
+ // draw element icon
+ DrawStamp (&s);
+ s.frame = SetRelFrameIndex (s.frame, 5);
+ s.origin.y += ELEMENT_SPACING_Y;
+ // print element amount
+ SetContextForeGroundColor (cargo_color[i]);
+ snprintf (buf, sizeof buf, "%u", sisState->ElementAmounts[i]);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += ELEMENT_SPACING_Y;
+ }
+
+ // draw Bio icon
+ s.origin.x = 24 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS;
+ s.origin.y = 68;
+ s.frame = SetAbsFrameIndex (s.frame, 68);
+ DrawStamp (&s);
+ // print Bio amount
+ t.baseline.x = 50 + SUMMARY_X_OFFS;
+ t.baseline.y = s.origin.y + 3;
+ SetContextForeGroundColor (cargo_color[i]);
+ snprintf (buf, sizeof buf, "%u", sisState->TotalBioMass);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ UnbatchGraphics ();
+}
+
+static void
+DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex)
+{
+ SUMMARY_DESC *pSD = pickState->summary + gameIndex;
+ RECT r;
+ STAMP s;
+
+ BatchGraphics ();
+
+ if (pSD->year_index == 0)
+ {
+ // Unused save slot, draw 'Empty Game' message.
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
+ GetFrameCount (pickState->SummaryFrame) - 4);
+ DrawStamp (&s);
+ }
+ else
+ {
+ // Game slot used, draw information about save game.
+ COUNT i;
+ RECT OldRect;
+ TEXT t;
+ QUEUE player_q;
+ CONTEXT OldContext;
+ SIS_STATE SaveSS;
+ UNICODE buf[256];
+ POINT starPt;
+
+ // Save the states because we will hack them
+ SaveSS = GlobData.SIS_state;
+ player_q = GLOBAL (built_ship_q);
+
+ OldContext = SetContext (StatusContext);
+ // Hack StatusContext so we can use standard SIS display funcs
+ GetContextClipRect (&OldRect);
+ r.corner.x = SIS_ORG_X + ((SIS_SCREEN_WIDTH - STATUS_WIDTH) >> 1) +
+ SAFE_X - 16 + SUMMARY_X_OFFS;
+// r.corner.x = SIS_ORG_X + ((SIS_SCREEN_WIDTH - STATUS_WIDTH) >> 1);
+ r.corner.y = SIS_ORG_Y;
+ r.extent.width = STATUS_WIDTH;
+ r.extent.height = STATUS_HEIGHT;
+ SetContextClipRect (&r);
+
+ // Hack the states so that we can use standard SIS display funcs
+ GlobData.SIS_state = pSD->SS;
+ InitQueue (&GLOBAL (built_ship_q),
+ MAX_BUILT_SHIPS, sizeof (SHIP_FRAGMENT));
+ for (i = 0; i < pSD->NumShips; ++i)
+ CloneShipFragment (pSD->ShipList[i], &GLOBAL (built_ship_q), 0);
+ DateToString (buf, sizeof buf,
+ pSD->month_index, pSD->day_index, pSD->year_index),
+ ClearSISRect (DRAW_SIS_DISPLAY);
+ DrawStatusMessage (buf);
+ UninitQueue (&GLOBAL (built_ship_q));
+
+ SetContextClipRect (&OldRect);
+
+ SetContext (SpaceContext);
+ // draw devices
+ s.origin.y = 13;
+ for (i = 0; i < 4; ++i)
+ {
+ COUNT j;
+
+ s.origin.x = 140 + SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
+ for (j = 0; j < 4; ++j)
+ {
+ COUNT devIndex = (i * 4) + j;
+ if (devIndex < pSD->NumDevices)
+ {
+ s.frame = SetAbsFrameIndex (MiscDataFrame, 77
+ + pSD->DeviceList[devIndex]);
+ DrawStamp (&s);
+ }
+ s.origin.x += 18;
+ }
+ s.origin.y += 18;
+ }
+
+ SetContextFont (StarConFont);
+ t.baseline.x = 173 + SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
+ t.align = ALIGN_CENTER;
+ t.CharCount = (COUNT)~0;
+ t.pStr = buf;
+ if (pSD->Flags & AFTER_BOMB_INSTALLED)
+ {
+ // draw the bomb and the escape pod
+ s.origin.x = SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 6;
+ s.origin.y = 0;
+ s.frame = SetRelFrameIndex (pickState->SummaryFrame, 0);
+ DrawStamp (&s);
+ // draw RU "NO LIMIT"
+ s.origin.x = SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
+ s.frame = IncFrameIndex (s.frame);
+ DrawStamp (&s);
+ }
+ else
+ {
+ DrawSavegameCargo (&pSD->SS);
+
+ SetContext (RadarContext);
+ // Hack RadarContext so we can use standard Lander display funcs
+ GetContextClipRect (&OldRect);
+ r.corner.x = SIS_ORG_X + 10 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS;
+ r.corner.y = SIS_ORG_Y + 84;
+ r.extent = OldRect.extent;
+ SetContextClipRect (&r);
+ // draw the lander with upgrades
+ InitLander (pSD->Flags | OVERRIDE_LANDER_FLAGS);
+ SetContextClipRect (&OldRect);
+ SetContext (SpaceContext);
+
+ snprintf (buf, sizeof buf, "%u", pSD->SS.ResUnits);
+ t.baseline.y = 102;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x00, 0x10), 0x01));
+ font_DrawText (&t);
+ t.CharCount = (COUNT)~0;
+ }
+ t.baseline.y = 126;
+ snprintf (buf, sizeof buf, "%u",
+ MAKE_WORD (pSD->MCreditLo, pSD->MCreditHi));
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x00, 0x10), 0x01));
+ font_DrawText (&t);
+
+ // print the location
+ t.baseline.x = 6;
+ t.baseline.y = 139 + 6;
+ t.align = ALIGN_LEFT;
+ t.pStr = buf;
+ starPt.x = LOGX_TO_UNIVERSE (pSD->SS.log_x);
+ starPt.y = LOGY_TO_UNIVERSE (pSD->SS.log_y);
+ switch (pSD->Activity)
+ {
+ case IN_LAST_BATTLE:
+ case IN_INTERPLANETARY:
+ case IN_PLANET_ORBIT:
+ case IN_STARBASE:
+ {
+ BYTE QuasiState;
+ STAR_DESC *SDPtr;
+
+ QuasiState = GET_GAME_STATE (ARILOU_SPACE_SIDE);
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, 0);
+ SDPtr = FindStar (NULL, &starPt, 1, 1);
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, QuasiState);
+ if (SDPtr)
+ {
+ GetClusterName (SDPtr, buf);
+ starPt = SDPtr->star_pt;
+ break;
+ }
+ }
+ default:
+ buf[0] = '\0';
+ break;
+ case IN_HYPERSPACE:
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (NAVIGATION_STRING_BASE + 0));
+ break;
+ case IN_QUASISPACE:
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (NAVIGATION_STRING_BASE + 1));
+ break;
+ }
+
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33));
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.align = ALIGN_CENTER;
+ t.baseline.x = SIS_SCREEN_WIDTH - SIS_TITLE_BOX_WIDTH - 4
+ + (SIS_TITLE_WIDTH >> 1);
+ switch (pSD->Activity)
+ {
+ case IN_STARBASE:
+ utf8StringCopy (buf, sizeof (buf), // Starbase
+ GAME_STRING (STARBASE_STRING_BASE));
+ break;
+ case IN_LAST_BATTLE:
+ utf8StringCopy (buf, sizeof (buf), // Sa-Matra
+ GAME_STRING (PLANET_NUMBER_BASE + 32));
+ break;
+ case IN_PLANET_ORBIT:
+ utf8StringCopy (buf, sizeof (buf), pSD->SS.PlanetName);
+ break;
+ default:
+ snprintf (buf, sizeof buf, "%03u.%01u : %03u.%01u",
+ starPt.x / 10, starPt.x % 10,
+ starPt.y / 10, starPt.y % 10);
+ }
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ SetContext (OldContext);
+
+ // Restore the states because we hacked them
+ GLOBAL (built_ship_q) = player_q;
+ GlobData.SIS_state = SaveSS;
+ }
+
+ UnbatchGraphics ();
+}
+
+static void
+DrawGameSelection (PICK_GAME_STATE *pickState, COUNT selSlot)
+{
+ RECT r;
+ TEXT t;
+ COUNT i;
+ COUNT curSlot;
+ UNICODE buf[256];
+ UNICODE buf2[80];
+
+ BatchGraphics ();
+
+ SetContextFont (TinyFont);
+
+ // Erase the selection menu
+ r.extent.width = 240;
+ r.extent.height = 65;
+ r.corner.x = 1;
+ r.corner.y = 160;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ t.CharCount = (COUNT)~0;
+ t.pStr = buf;
+ t.align = ALIGN_LEFT;
+
+ // Draw savegame slots info
+ curSlot = selSlot - (selSlot % SAVES_PER_PAGE);
+ for (i = 0; i < SAVES_PER_PAGE && curSlot < MAX_SAVED_GAMES;
+ ++i, ++curSlot)
+ {
+ SUMMARY_DESC *desc = &pickState->summary[curSlot];
+
+ SetContextForeGroundColor ((curSlot == selSlot) ?
+ (BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33)):
+ (BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)));
+ r.extent.width = 15;
+ if (MAX_SAVED_GAMES > 99)
+ r.extent.width += 5;
+ r.extent.height = 11;
+ r.corner.x = 8;
+ r.corner.y = 160 + (i * 13);
+ DrawRectangle (&r);
+
+ t.baseline.x = r.corner.x + 3;
+ t.baseline.y = r.corner.y + 8;
+ snprintf (buf, sizeof buf, (MAX_SAVED_GAMES > 99) ? "%03u" : "%02u",
+ curSlot);
+ font_DrawText (&t);
+
+ r.extent.width = 204 - SAFE_X;
+ r.corner.x = 30 + SAFE_X;
+ DrawRectangle (&r);
+
+ t.baseline.x = r.corner.x + 3;
+ if (desc->year_index == 0)
+ {
+ utf8StringCopy (buf, sizeof buf,
+ GAME_STRING (SAVEGAME_STRING_BASE + 3)); // "Empty Slot"
+ }
+ else
+ {
+ DateToString (buf2, sizeof buf2, desc->month_index,
+ desc->day_index, desc->year_index);
+ snprintf (buf, sizeof buf, "%s: %s", buf2, desc->SaveName[0] ? desc->SaveName : GAME_STRING (SAVEGAME_STRING_BASE + 4));
+ }
+ font_DrawText (&t);
+ }
+
+ UnbatchGraphics ();
+}
+
+static void
+RedrawPickDisplay (PICK_GAME_STATE *pickState, COUNT selSlot)
+{
+ BatchGraphics ();
+ DrawBlankSavegameDisplay (pickState);
+ DrawSavegameSummary (pickState, selSlot);
+ DrawGameSelection (pickState, selSlot);
+ UnbatchGraphics ();
+}
+
+static void
+LoadGameDescriptions (SUMMARY_DESC *pSD)
+{
+ COUNT i;
+
+ for (i = 0; i < MAX_SAVED_GAMES; ++i, ++pSD)
+ {
+ if (!LoadGame (i, pSD))
+ pSD->year_index = 0;
+ }
+}
+
+static BOOLEAN
+DoPickGame (MENU_STATE *pMS)
+{
+ PICK_GAME_STATE *pickState = pMS->privData;
+ BYTE NewState;
+ SUMMARY_DESC *pSD;
+ DWORD TimeIn = GetTimeCounter ();
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ pickState->success = FALSE;
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ pSD = &pickState->summary[pMS->CurState];
+ if (pickState->saving || pSD->year_index)
+ { // valid slot
+ PlayMenuSound (MENU_SOUND_SUCCESS);
+ pickState->success = TRUE;
+ return FALSE;
+ }
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ else
+ {
+ NewState = pMS->CurState;
+ if (PulsedInputState.menu[KEY_MENU_LEFT]
+ || PulsedInputState.menu[KEY_MENU_PAGE_UP])
+ {
+ if (NewState == 0)
+ NewState = MAX_SAVED_GAMES - 1;
+ else if ((NewState - SAVES_PER_PAGE) > 0)
+ NewState -= SAVES_PER_PAGE;
+ else
+ NewState = 0;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT]
+ || PulsedInputState.menu[KEY_MENU_PAGE_DOWN])
+ {
+ if (NewState == MAX_SAVED_GAMES - 1)
+ NewState = 0;
+ else if ((NewState + SAVES_PER_PAGE) < MAX_SAVED_GAMES - 1)
+ NewState += SAVES_PER_PAGE;
+ else
+ NewState = MAX_SAVED_GAMES - 1;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (NewState == 0)
+ NewState = MAX_SAVED_GAMES - 1;
+ else
+ NewState--;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (NewState == MAX_SAVED_GAMES - 1)
+ NewState = 0;
+ else
+ NewState++;
+ }
+
+ if (NewState != pMS->CurState)
+ {
+ pMS->CurState = NewState;
+ SetContext (SpaceContext);
+ RedrawPickDisplay (pickState, pMS->CurState);
+ }
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+ }
+
+ return TRUE;
+}
+
+static BOOLEAN
+SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex, BOOLEAN *canceled_by_user)
+{
+ SUMMARY_DESC *desc = pickState->summary + gameIndex;
+ UNICODE nameBuf[256];
+ STAMP saveStamp;
+ BOOLEAN success;
+
+ saveStamp.frame = NULL;
+
+ if (pickState->saving)
+ {
+ // Initialize the save name with whatever name is there already
+ // SAVE_NAME_SIZE is less than 256, so this is safe.
+ strncpy(nameBuf, desc->SaveName, SAVE_NAME_SIZE);
+ nameBuf[SAVE_NAME_SIZE] = 0;
+ if (NameSaveGame (gameIndex, nameBuf))
+ {
+ PlayMenuSound (MENU_SOUND_SUCCESS);
+ ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL);
+ success = SaveGame (gameIndex, desc, nameBuf);
+ }
+ else
+ {
+ success = FALSE;
+ *canceled_by_user = TRUE;
+ }
+ }
+ else
+ {
+ ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL);
+ success = LoadGame (gameIndex, NULL);
+ }
+
+ // TODO: the same should be done for both save and load if we also
+ // display a load problem message
+ if (pickState->saving)
+ { // restore the screen under "SAVING..." message
+ DrawStamp (&saveStamp);
+ }
+
+ DestroyDrawable (ReleaseDrawable (saveStamp.frame));
+
+ return success;
+}
+
+static BOOLEAN
+PickGame (BOOLEAN saving, BOOLEAN fromMainMenu)
+{
+ CONTEXT OldContext;
+ MENU_STATE MenuState;
+ PICK_GAME_STATE pickState;
+ RECT DlgRect;
+ STAMP DlgStamp;
+ TimeCount TimeOut;
+ InputFrameCallback *oldCallback;
+
+ memset (&pickState, 0, sizeof pickState);
+ pickState.saving = saving;
+ pickState.SummaryFrame = SetAbsFrameIndex (PlayFrame, 39);
+
+ memset (&MenuState, 0, sizeof MenuState);
+ MenuState.privData = &pickState;
+ // select the last used slot
+ MenuState.CurState = lastUsedSlot;
+
+ TimeOut = FadeMusic (0, ONE_SECOND / 2);
+
+ // Deactivate any background drawing, like planet rotation
+ oldCallback = SetInputCallback (NULL);
+
+ LoadGameDescriptions (pickState.summary);
+
+ OldContext = SetContext (SpaceContext);
+ // Save the current state of the screen for later restoration
+ DlgStamp = SaveContextFrame (NULL);
+ GetContextClipRect (&DlgRect);
+
+ SleepThreadUntil (TimeOut);
+ PauseMusic ();
+ StopSound ();
+ FadeMusic (NORMAL_VOLUME, 0);
+
+ // draw the current savegame and fade in
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+ RedrawPickDisplay (&pickState, MenuState.CurState);
+ DrawSaveLoad (&pickState);
+
+ if (fromMainMenu)
+ {
+ UnbatchGraphics ();
+ FadeScreen (FadeAllToColor, ONE_SECOND / 2);
+ }
+ else
+ {
+ RECT ctxRect;
+
+ GetContextClipRect (&ctxRect);
+ ScreenTransition (3, &ctxRect);
+ UnbatchGraphics ();
+ }
+
+ SetMenuSounds (MENU_SOUND_ARROWS | MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN,
+ 0);
+ MenuState.InputFunc = DoPickGame;
+
+ // Save/load retry loop
+ while (1)
+ {
+ BOOLEAN canceled_by_user = FALSE;
+
+ pickState.success = FALSE;
+ DoInput (&MenuState, TRUE);
+ if (!pickState.success)
+ break; // canceled
+
+ lastUsedSlot = MenuState.CurState;
+
+ if (SaveLoadGame (&pickState, MenuState.CurState, &canceled_by_user))
+ break; // all good
+
+ // something broke
+ if (saving && !canceled_by_user)
+ SaveProblem ();
+ // TODO: Shouldn't we have a Problem() equivalent for Load too?
+
+ // reload and redraw everything
+ LoadGameDescriptions (pickState.summary);
+ RedrawPickDisplay (&pickState, MenuState.CurState);
+ }
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ if (pickState.success && !saving)
+ { // Load succeeded, signal up the chain
+ GLOBAL (CurrentActivity) |= CHECK_LOAD;
+ }
+
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT) &&
+ (saving || (!pickState.success && !fromMainMenu)))
+ { // Restore previous screen
+ SetTransitionSource (&DlgRect);
+ BatchGraphics ();
+ DrawStamp (&DlgStamp);
+ ScreenTransition (3, &DlgRect);
+ UnbatchGraphics ();
+ }
+
+ DestroyDrawable (ReleaseDrawable (DlgStamp.frame));
+
+ SetContext (OldContext);
+
+ ResumeMusic ();
+
+ // Reactivate any background drawing, like planet rotation
+ SetInputCallback (oldCallback);
+
+ return pickState.success;
+}
+
+static BOOLEAN
+DoGameOptions (MENU_STATE *pMS)
+{
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL]
+ || (PulsedInputState.menu[KEY_MENU_SELECT]
+ && pMS->CurState == EXIT_GAME_MENU))
+ {
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ switch (pMS->CurState)
+ {
+ case SAVE_GAME:
+ case LOAD_GAME:
+ SetFlashRect (NULL);
+ if (PickGame (pMS->CurState == SAVE_GAME, FALSE))
+ return FALSE;
+ SetFlashRect (SFR_MENU_3DO);
+ break;
+ case QUIT_GAME:
+ if (ConfirmExit ())
+ return FALSE;
+ break;
+ case SETTINGS:
+ SettingsMenu ();
+ DrawMenuStateStrings (PM_SAVE_GAME, pMS->CurState);
+ break;
+ }
+ }
+ else
+ DoMenuChooser (pMS, PM_SAVE_GAME);
+
+ return TRUE;
+}
+
+// Returns TRUE when the owner menu should continue
+BOOLEAN
+GameOptions (void)
+{
+ MENU_STATE MenuState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+
+ if (LastActivity == CHECK_LOAD)
+ { // Selected LOAD from main menu
+ BOOLEAN success;
+
+ DrawMenuStateStrings (PM_SAVE_GAME, LOAD_GAME);
+ success = PickGame (FALSE, TRUE);
+ if (!success)
+ { // Selected LOAD from main menu, and canceled
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ }
+ return FALSE;
+ }
+
+ MenuState.CurState = SAVE_GAME;
+ DrawMenuStateStrings (PM_SAVE_GAME, MenuState.CurState);
+
+ SetFlashRect (SFR_MENU_3DO);
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ MenuState.InputFunc = DoGameOptions;
+ DoInput (&MenuState, TRUE);
+
+ SetFlashRect (NULL);
+
+ return !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD));
+}
diff --git a/src/uqm/gameopt.h b/src/uqm/gameopt.h
new file mode 100644
index 0000000..1e14906
--- /dev/null
+++ b/src/uqm/gameopt.h
@@ -0,0 +1,36 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef UQM_GAMEOPT_H_
+#define UQM_GAMEOPT_H_
+
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern void ConfirmSaveLoad (STAMP *MsgStamp);
+extern BOOLEAN GameOptions (void);
+
+typedef void (NamingCallback) (void);
+extern void SetNamingCallback (NamingCallback *);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_GAMEOPT_H_ */
diff --git a/src/uqm/gamestr.h b/src/uqm/gamestr.h
new file mode 100644
index 0000000..60f7843
--- /dev/null
+++ b/src/uqm/gamestr.h
@@ -0,0 +1,93 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file contains definitions relating to using strings specific to
+ * the game. libs/strlib.h is for the string library.
+ */
+
+#ifndef UQM_GAMESTR_H_
+#define UQM_GAMESTR_H_
+
+
+#include "libs/strlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define STAR_STRING_COUNT 133
+#define DEVICE_STRING_COUNT 29
+#define CARGO_STRING_COUNT 10
+#define ELEMENTS_STRING_COUNT 133
+#define SCAN_STRING_COUNT 56
+#define STAR_NUMBER_COUNT 14
+#define PLANET_NUMBER_COUNT 33
+#define MONTHS_STRING_COUNT 12
+#define FEEDBACK_STRING_COUNT 2
+#define STARBASE_STRING_COUNT 5
+#define ENCOUNTER_STRING_COUNT 8
+#define NAVIGATION_STRING_COUNT 6
+#define NAMING_STRING_COUNT 4
+#define MELEE_STRING_COUNT 9
+#define SAVEGAME_STRING_COUNT 5
+#define OPTION_STRING_COUNT 5
+#define QUITMENU_STRING_COUNT 3
+#define STATUS_STRING_COUNT 6
+#define FLAGSHIP_STRING_COUNT 13
+#define ORBITSCAN_STRING_COUNT 19
+#define MAINMENU_STRING_COUNT 55
+#define NETMELEE_STRING_COUNT 19
+
+enum {
+ STAR_STRING_BASE = 0,
+ DEVICE_STRING_BASE = STAR_STRING_BASE + STAR_STRING_COUNT,
+ CARGO_STRING_BASE = DEVICE_STRING_BASE + DEVICE_STRING_COUNT,
+ ELEMENTS_STRING_BASE = CARGO_STRING_BASE + CARGO_STRING_COUNT,
+ SCAN_STRING_BASE = ELEMENTS_STRING_BASE + ELEMENTS_STRING_COUNT,
+ STAR_NUMBER_BASE = SCAN_STRING_BASE + SCAN_STRING_COUNT,
+ PLANET_NUMBER_BASE = STAR_NUMBER_BASE + STAR_NUMBER_COUNT,
+ MONTHS_STRING_BASE = PLANET_NUMBER_BASE + PLANET_NUMBER_COUNT,
+ FEEDBACK_STRING_BASE = MONTHS_STRING_BASE + MONTHS_STRING_COUNT,
+ STARBASE_STRING_BASE = FEEDBACK_STRING_BASE + FEEDBACK_STRING_COUNT,
+ ENCOUNTER_STRING_BASE = STARBASE_STRING_BASE + STARBASE_STRING_COUNT,
+ NAVIGATION_STRING_BASE = ENCOUNTER_STRING_BASE + ENCOUNTER_STRING_COUNT,
+ NAMING_STRING_BASE = NAVIGATION_STRING_BASE + NAVIGATION_STRING_COUNT,
+ MELEE_STRING_BASE = NAMING_STRING_BASE + NAMING_STRING_COUNT,
+ SAVEGAME_STRING_BASE = MELEE_STRING_BASE + MELEE_STRING_COUNT,
+ OPTION_STRING_BASE = SAVEGAME_STRING_BASE + SAVEGAME_STRING_COUNT,
+ QUITMENU_STRING_BASE = OPTION_STRING_BASE + OPTION_STRING_COUNT,
+ STATUS_STRING_BASE = QUITMENU_STRING_BASE + QUITMENU_STRING_COUNT,
+ FLAGSHIP_STRING_BASE = STATUS_STRING_BASE + STATUS_STRING_COUNT,
+ ORBITSCAN_STRING_BASE = FLAGSHIP_STRING_BASE + FLAGSHIP_STRING_COUNT,
+ MAINMENU_STRING_BASE = ORBITSCAN_STRING_BASE + ORBITSCAN_STRING_COUNT,
+ NETMELEE_STRING_BASE = MAINMENU_STRING_BASE + MAINMENU_STRING_COUNT,
+
+ GAMESTR_COUNT = NETMELEE_STRING_BASE + NETMELEE_STRING_COUNT
+};
+
+
+#define GAME_STRING(i) ((UNICODE *)GetStringAddress (SetAbsStringTableIndex (GameStrings, (i))))
+
+extern STRING GameStrings;
+
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_GAMESTR_H_ */
diff --git a/src/uqm/gendef.c b/src/uqm/gendef.c
new file mode 100644
index 0000000..6a01377
--- /dev/null
+++ b/src/uqm/gendef.c
@@ -0,0 +1,137 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gendef.h"
+#include "planets/generate.h"
+
+
+extern GenerateFunctions generateDefaultFunctions;
+
+extern GenerateFunctions generateAndrosynthFunctions;
+extern GenerateFunctions generateBurvixeseFunctions;
+extern GenerateFunctions generateChmmrFunctions;
+extern GenerateFunctions generateColonyFunctions;
+extern GenerateFunctions generateDruugeFunctions;
+extern GenerateFunctions generateIlwrathFunctions;
+extern GenerateFunctions generateMelnormeFunctions;
+extern GenerateFunctions generateMyconFunctions;
+extern GenerateFunctions generateOrzFunctions;
+extern GenerateFunctions generatePkunkFunctions;
+extern GenerateFunctions generateRainbowWorldFunctions;
+extern GenerateFunctions generateSaMatraFunctions;
+extern GenerateFunctions generateShofixtiFunctions;
+extern GenerateFunctions generateSlylandroFunctions;
+extern GenerateFunctions generateSolFunctions;
+extern GenerateFunctions generateSpathiFunctions;
+extern GenerateFunctions generateSupoxFunctions;
+extern GenerateFunctions generateSyreenFunctions;
+extern GenerateFunctions generateTalkingPetFunctions;
+extern GenerateFunctions generateThraddashFunctions;
+extern GenerateFunctions generateTrapFunctions;
+extern GenerateFunctions generateUtwigFunctions;
+extern GenerateFunctions generateVaultFunctions;
+extern GenerateFunctions generateVuxFunctions;
+extern GenerateFunctions generateWreckFunctions;
+extern GenerateFunctions generateYehatFunctions;
+extern GenerateFunctions generateZoqFotPikFunctions;
+extern GenerateFunctions generateZoqFotPikScoutFunctions;
+
+
+const GenerateFunctions *
+getGenerateFunctions (BYTE Index)
+{
+ switch (Index)
+ {
+ case SOL_DEFINED:
+ return &generateSolFunctions;
+ case SHOFIXTI_DEFINED:
+ return &generateShofixtiFunctions;
+ case START_COLONY_DEFINED:
+ return &generateColonyFunctions;
+ case SPATHI_DEFINED:
+ return &generateSpathiFunctions;
+ case MELNORME0_DEFINED:
+ case MELNORME1_DEFINED:
+ case MELNORME2_DEFINED:
+ case MELNORME3_DEFINED:
+ case MELNORME4_DEFINED:
+ case MELNORME5_DEFINED:
+ case MELNORME6_DEFINED:
+ case MELNORME7_DEFINED:
+ case MELNORME8_DEFINED:
+ return &generateMelnormeFunctions;
+ case TALKING_PET_DEFINED:
+ return &generateTalkingPetFunctions;
+ case CHMMR_DEFINED:
+ return &generateChmmrFunctions;
+ case SYREEN_DEFINED:
+ return &generateSyreenFunctions;
+ case MYCON_TRAP_DEFINED:
+ return &generateTrapFunctions;
+ case BURVIXESE_DEFINED:
+ return &generateBurvixeseFunctions;
+ case SLYLANDRO_DEFINED:
+ return &generateSlylandroFunctions;
+ case DRUUGE_DEFINED:
+ return &generateDruugeFunctions;
+ case BOMB_DEFINED:
+ case UTWIG_DEFINED:
+ return &generateUtwigFunctions;
+ case AQUA_HELIX_DEFINED:
+ case THRADD_DEFINED:
+ return &generateThraddashFunctions;
+ case SUN_DEVICE_DEFINED:
+ case MYCON_DEFINED:
+ case EGG_CASE0_DEFINED:
+ case EGG_CASE1_DEFINED:
+ case EGG_CASE2_DEFINED:
+ return &generateMyconFunctions;
+ case ANDROSYNTH_DEFINED:
+ return &generateAndrosynthFunctions;
+ case TAALO_PROTECTOR_DEFINED:
+ case ORZ_DEFINED:
+ return &generateOrzFunctions;
+ case SHIP_VAULT_DEFINED:
+ return &generateVaultFunctions;
+ case URQUAN_WRECK_DEFINED:
+ return &generateWreckFunctions;
+ case MAIDENS_DEFINED:
+ case VUX_BEAST_DEFINED:
+ case VUX_DEFINED:
+ return &generateVuxFunctions;
+ case SAMATRA_DEFINED:
+ return &generateSaMatraFunctions;
+ case ZOQFOT_DEFINED:
+ return &generateZoqFotPikFunctions;
+ case ZOQ_SCOUT_DEFINED:
+ return &generateZoqFotPikScoutFunctions;
+ case YEHAT_DEFINED:
+ return &generateYehatFunctions;
+ case PKUNK_DEFINED:
+ return &generatePkunkFunctions;
+ case SUPOX_DEFINED:
+ return &generateSupoxFunctions;
+ case RAINBOW_DEFINED:
+ return &generateRainbowWorldFunctions;
+ case ILWRATH_DEFINED:
+ return &generateIlwrathFunctions;
+ default:
+ return &generateDefaultFunctions;
+ }
+}
+
diff --git a/src/uqm/gendef.h b/src/uqm/gendef.h
new file mode 100644
index 0000000..b5a292a
--- /dev/null
+++ b/src/uqm/gendef.h
@@ -0,0 +1,71 @@
+#ifndef GENDEF_H
+#define GENDEF_H
+
+#include "planets/generate.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+const GenerateFunctions *getGenerateFunctions (BYTE Index);
+
+enum
+{
+ SOL_DEFINED = 1,
+ SHOFIXTI_DEFINED,
+ MAIDENS_DEFINED,
+ START_COLONY_DEFINED,
+ SPATHI_DEFINED,
+ ZOQFOT_DEFINED,
+
+ MELNORME0_DEFINED,
+ MELNORME1_DEFINED,
+ MELNORME2_DEFINED,
+ MELNORME3_DEFINED,
+ MELNORME4_DEFINED,
+ MELNORME5_DEFINED,
+ MELNORME6_DEFINED,
+ MELNORME7_DEFINED,
+ MELNORME8_DEFINED,
+
+ TALKING_PET_DEFINED,
+ CHMMR_DEFINED,
+ SYREEN_DEFINED,
+ BURVIXESE_DEFINED,
+ SLYLANDRO_DEFINED,
+ DRUUGE_DEFINED,
+ BOMB_DEFINED,
+ AQUA_HELIX_DEFINED,
+ SUN_DEVICE_DEFINED,
+ TAALO_PROTECTOR_DEFINED,
+ SHIP_VAULT_DEFINED,
+ URQUAN_WRECK_DEFINED,
+ VUX_BEAST_DEFINED,
+ SAMATRA_DEFINED,
+ ZOQ_SCOUT_DEFINED,
+ MYCON_DEFINED,
+ EGG_CASE0_DEFINED,
+ EGG_CASE1_DEFINED,
+ EGG_CASE2_DEFINED,
+ PKUNK_DEFINED,
+ UTWIG_DEFINED,
+ SUPOX_DEFINED,
+ YEHAT_DEFINED,
+ VUX_DEFINED,
+ ORZ_DEFINED,
+ THRADD_DEFINED,
+ RAINBOW_DEFINED,
+ ILWRATH_DEFINED,
+ ANDROSYNTH_DEFINED,
+ MYCON_TRAP_DEFINED
+};
+
+#define UMGAH_DEFINED TALKING_PET_DEFINED
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* GENDEF_H */
+
diff --git a/src/uqm/getchar.c b/src/uqm/getchar.c
new file mode 100644
index 0000000..3f1f6ed
--- /dev/null
+++ b/src/uqm/getchar.c
@@ -0,0 +1,442 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "port.h"
+#include "controls.h"
+#include "libs/inplib.h"
+#include "libs/memlib.h"
+#include "libs/log.h"
+#include "globdata.h"
+#include "sounds.h"
+#include "settings.h"
+#include "resinst.h"
+#include "nameref.h"
+
+
+// TODO: This may be better done with UniChar at the cost of a tiny bit
+// of overhead to convert UniChar back to UTF8 string. This overhead
+// will probably be offset by removal of looped string-compare overhead ;)
+struct joy_char
+{
+ unsigned char len;
+ char enc[7];
+ // 1+7 is a nice round number
+};
+
+static int
+ReadOneChar (joy_char_t *ch, const UNICODE *str)
+{
+ UNICODE *next = skipUTF8Chars (str, 1);
+ int len = next - str;
+ ch->len = len;
+ memcpy (ch->enc, str, len);
+ ch->enc[len] = '\0'; // string term
+
+ return len;
+}
+
+static joy_char_t *
+LoadJoystickAlpha (STRING String, int *count)
+{
+ UNICODE *str;
+ int c;
+ int i;
+ joy_char_t *chars;
+ UNICODE *cur;
+
+ *count = 0;
+ str = GetStringAddress (String);
+ if (!str)
+ return 0;
+
+ c = utf8StringCount (str);
+ chars = HMalloc (c * sizeof (*chars));
+ if (!chars)
+ return 0;
+
+ for (i = 0, cur = str; i < c; ++i)
+ {
+ int len = ReadOneChar (chars + i, cur);
+ cur += len;
+ }
+
+ *count = c;
+ return chars;
+}
+
+static int
+JoyCharFindIn (const joy_char_t *ch, const joy_char_t *set,
+ int setsize)
+{
+ int i;
+
+ for (i = 0; i < setsize && strcmp (set[i].enc, ch->enc) != 0; ++i)
+ ;
+
+ return (i < setsize) ? i : -1;
+}
+
+static int
+JoyCharIsLower (const joy_char_t *ch, TEXTENTRY_STATE *pTES)
+{
+ return 0 <= JoyCharFindIn (ch, pTES->JoyLower, pTES->JoyRegLength);
+}
+
+static void
+JoyCharSwitchReg (joy_char_t *ch, const joy_char_t *from,
+ const joy_char_t *to, int regsize)
+{
+ int i = JoyCharFindIn (ch, from, regsize);
+ if (i >= 0)
+ *ch = to[i];
+}
+
+static void
+JoyCharToUpper (joy_char_t *outch, const joy_char_t *ch,
+ TEXTENTRY_STATE *pTES)
+{
+ *outch = *ch;
+ JoyCharSwitchReg (outch, pTES->JoyLower, pTES->JoyUpper,
+ pTES->JoyRegLength);
+}
+
+static void
+JoyCharToLower (joy_char_t *outch, const joy_char_t *ch,
+ TEXTENTRY_STATE *pTES)
+{
+ *outch = *ch;
+ JoyCharSwitchReg (outch, pTES->JoyUpper, pTES->JoyLower,
+ pTES->JoyRegLength);
+}
+
+BOOLEAN
+DoTextEntry (TEXTENTRY_STATE *pTES)
+{
+ UniChar ch;
+ UNICODE *pStr;
+ UNICODE *CacheInsPt;
+ int CacheCursorPos;
+ int len;
+ BOOLEAN changed = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return (FALSE);
+
+ if (!pTES->Initialized)
+ { // init basic vars
+ int lwlen;
+
+ pTES->InputFunc = DoTextEntry;
+ pTES->Success = FALSE;
+ pTES->Initialized = TRUE;
+ pTES->JoystickMode = FALSE;
+ pTES->UpperRegister = TRUE;
+
+ // init insertion point
+ if ((size_t)pTES->CursorPos > utf8StringCount (pTES->BaseStr))
+ pTES->CursorPos = utf8StringCount (pTES->BaseStr);
+ pTES->InsPt = skipUTF8Chars (pTES->BaseStr, pTES->CursorPos);
+
+ // load joystick alphabet
+ pTES->JoyAlphaString = CaptureStringTable (
+ LoadStringTable (JOYSTICK_ALPHA_STRTAB));
+ pTES->JoyAlpha = LoadJoystickAlpha (
+ SetAbsStringTableIndex (pTES->JoyAlphaString, 0),
+ &pTES->JoyAlphaLength);
+ pTES->JoyUpper = LoadJoystickAlpha (
+ SetAbsStringTableIndex (pTES->JoyAlphaString, 1),
+ &pTES->JoyRegLength);
+ pTES->JoyLower = LoadJoystickAlpha (
+ SetAbsStringTableIndex (pTES->JoyAlphaString, 2),
+ &lwlen);
+ if (lwlen != pTES->JoyRegLength)
+ {
+ if (lwlen < pTES->JoyRegLength)
+ pTES->JoyRegLength = lwlen;
+ log_add (log_Warning, "Warning: Joystick upper-lower registers"
+ " size mismatch; using the smallest subset (%d)",
+ pTES->JoyRegLength);
+ }
+
+ pTES->CacheStr = HMalloc (pTES->MaxSize * sizeof (*pTES->CacheStr));
+
+ EnterCharacterMode ();
+ DoInput (pTES, TRUE);
+ ExitCharacterMode ();
+
+ if (pTES->CacheStr)
+ HFree (pTES->CacheStr);
+ if (pTES->JoyLower)
+ HFree (pTES->JoyLower);
+ if (pTES->JoyUpper)
+ HFree (pTES->JoyUpper);
+ if (pTES->JoyAlpha)
+ HFree (pTES->JoyAlpha);
+ DestroyStringTable ( ReleaseStringTable (pTES->JoyAlphaString));
+
+ return pTES->Success;
+ }
+
+ pStr = pTES->InsPt;
+ len = strlen (pStr);
+ // save a copy of string
+ CacheInsPt = pTES->InsPt;
+ CacheCursorPos = pTES->CursorPos;
+ memcpy (pTES->CacheStr, pTES->BaseStr, pTES->MaxSize);
+
+ // process the pending character buffer
+ ch = GetNextCharacter ();
+ if (!ch && PulsedInputState.menu[KEY_MENU_ANY])
+ { // keyboard repeat, but only when buffer empty
+ ch = GetLastCharacter ();
+ }
+ while (ch)
+ {
+ UNICODE chbuf[8];
+ int chsize;
+
+ pTES->JoystickMode = FALSE;
+
+ chsize = getStringFromChar (chbuf, sizeof (chbuf), ch);
+ if (UniChar_isPrint (ch) && chsize > 0)
+ {
+ if (pStr + len - pTES->BaseStr + chsize < pTES->MaxSize)
+ { // insert character, when fits
+ memmove (pStr + chsize, pStr, len + 1);
+ memcpy (pStr, chbuf, chsize);
+ pStr += chsize;
+ ++pTES->CursorPos;
+ changed = TRUE;
+ }
+ else
+ { // does not fit
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ }
+ ch = GetNextCharacter ();
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_DELETE])
+ {
+ if (len)
+ {
+ joy_char_t ch;
+
+ ReadOneChar (&ch, pStr);
+ memmove (pStr, pStr + ch.len, len - ch.len + 1);
+ len -= ch.len;
+ changed = TRUE;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_BACKSPACE])
+ {
+ if (pStr > pTES->BaseStr)
+ {
+ UNICODE *prev = skipUTF8Chars (pTES->BaseStr,
+ pTES->CursorPos - 1);
+
+ memmove (prev, pStr, len + 1);
+ pStr = prev;
+ --pTES->CursorPos;
+ changed = TRUE;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ if (pStr > pTES->BaseStr)
+ {
+ UNICODE *prev = skipUTF8Chars (pTES->BaseStr,
+ pTES->CursorPos - 1);
+
+ pStr = prev;
+ len += (prev - pStr);
+ --pTES->CursorPos;
+ changed = TRUE;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ if (len > 0)
+ {
+ joy_char_t ch;
+
+ ReadOneChar (&ch, pStr);
+ pStr += ch.len;
+ len -= ch.len;
+ ++pTES->CursorPos;
+ changed = TRUE;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_HOME])
+ {
+ if (pStr > pTES->BaseStr)
+ {
+ pStr = pTES->BaseStr;
+ len = strlen (pStr);
+ pTES->CursorPos = 0;
+ changed = TRUE;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_END])
+ {
+ if (len > 0)
+ {
+ pTES->CursorPos += utf8StringCount (pStr);
+ pStr += len;
+ len = 0;
+ changed = TRUE;
+ }
+ }
+
+ if (pTES->JoyAlpha && (
+ PulsedInputState.menu[KEY_MENU_UP] ||
+ PulsedInputState.menu[KEY_MENU_DOWN] ||
+ PulsedInputState.menu[KEY_MENU_PAGE_UP] ||
+ PulsedInputState.menu[KEY_MENU_PAGE_DOWN]) )
+ { // do joystick text
+ joy_char_t ch;
+ joy_char_t newch;
+ joy_char_t cmpch;
+ int i;
+ BOOLEAN curCharUpper;
+
+ pTES->JoystickMode = TRUE;
+
+ if (len)
+ { // changing an existing character
+ ReadOneChar (&ch, pStr);
+ curCharUpper = !JoyCharIsLower (&ch, pTES);
+ }
+ else
+ { // adding a new character
+ ch = pTES->JoyAlpha[0];
+ // new characters will have case determined by the
+ // currently selected register
+ curCharUpper = pTES->UpperRegister;
+ }
+
+ newch = ch;
+ JoyCharToUpper (&cmpch, &ch, pTES);
+
+ // find current char in the alphabet
+ i = JoyCharFindIn (&cmpch, pTES->JoyAlpha, pTES->JoyAlphaLength);
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ --i;
+ if (i < 0)
+ i = pTES->JoyAlphaLength - 1;
+ newch = pTES->JoyAlpha[i];
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ ++i;
+ if (i >= pTES->JoyAlphaLength)
+ i = 0;
+ newch = pTES->JoyAlpha[i];
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_PAGE_UP] ||
+ PulsedInputState.menu[KEY_MENU_PAGE_DOWN])
+ {
+ if (len)
+ { // single char change
+ if (!curCharUpper)
+ JoyCharToUpper (&newch, &newch, pTES);
+ else
+ JoyCharToLower (&newch, &newch, pTES);
+ }
+ else
+ { // register change
+ pTES->UpperRegister = !pTES->UpperRegister;
+ }
+ }
+ else
+ { // check register
+ if (curCharUpper)
+ JoyCharToUpper (&newch, &newch, pTES);
+ else
+ JoyCharToLower (&newch, &newch, pTES);
+ }
+
+ if (strcmp (newch.enc, ch.enc) != 0)
+ { // new char is different, put it in
+ if (len)
+ { // change current -- this is messy with utf8
+ int l = len - ch.len;
+ if (pStr + l - pTES->BaseStr + newch.len < pTES->MaxSize)
+ {
+ // adjust other chars if necessary
+ if (newch.len != ch.len)
+ memmove (pStr + newch.len, pStr + ch.len, l + 1);
+
+ memcpy (pStr, newch.enc, newch.len);
+ len = l + newch.len;
+ changed = TRUE;
+ }
+ }
+ else
+ { // append
+ if (pStr + len - pTES->BaseStr + newch.len < pTES->MaxSize)
+ {
+ memcpy (pStr, newch.enc, newch.len);
+ pStr[newch.len] = '\0';
+ len += newch.len;
+ changed = TRUE;
+ }
+ else
+ { // does not fit
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ }
+ }
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT])
+ { // done entering
+ pTES->Success = TRUE;
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_EDIT_CANCEL])
+ { // canceled entering
+ pTES->Success = FALSE;
+ return FALSE;
+ }
+
+ pTES->InsPt = pStr;
+
+ if (changed && pTES->ChangeCallback)
+ {
+ if (!pTES->ChangeCallback (pTES))
+ { // changes not accepted - revert
+ memcpy (pTES->BaseStr, pTES->CacheStr, pTES->MaxSize);
+ pTES->InsPt = CacheInsPt;
+ pTES->CursorPos = CacheCursorPos;
+
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ }
+
+ if (pTES->FrameCallback)
+ return pTES->FrameCallback (pTES);
+ else
+ SleepThread (ONE_SECOND / 30);
+
+ return TRUE;
+}
+
diff --git a/src/uqm/globdata.c b/src/uqm/globdata.c
new file mode 100644
index 0000000..ff9edc2
--- /dev/null
+++ b/src/uqm/globdata.c
@@ -0,0 +1,511 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "globdata.h"
+
+#include "coderes.h"
+#include "encount.h"
+#include "starmap.h"
+#include "master.h"
+#include "setup.h"
+#include "units.h"
+#include "hyper.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "build.h"
+#include "state.h"
+#include "grpinfo.h"
+#include "gamestr.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#ifdef STATE_DEBUG
+# include "libs/log.h"
+#endif
+
+
+static void CreateRadar (void);
+
+CONTEXT RadarContext;
+FRAME PlayFrame;
+
+GLOBDATA GlobData;
+
+
+BYTE
+getGameState (BYTE *state, int startBit, int endBit)
+{
+ return (BYTE) (((startBit >> 3) == (endBit >> 3)
+ ? (state[startBit >> 3] >> (startBit & 7))
+ : ((state[startBit >> 3] >> (startBit & 7))
+ | (state[endBit >> 3]
+ << (endBit - startBit - (endBit & 7)))))
+ & ((1 << (endBit - startBit + 1)) - 1));
+}
+
+void
+setGameState (BYTE *state, int startBit, int endBit, BYTE val
+#ifdef STATE_DEBUG
+ , const char *name
+#endif
+)
+{
+ state[startBit >> 3] =
+ (state[startBit >> 3]
+ & (BYTE) ~(((1 << (endBit - startBit + 1)) - 1) << (startBit & 7)))
+ | (BYTE)((val) << (startBit & 7));
+
+ if ((startBit >> 3) < (endBit >> 3)) {
+ state[endBit >> 3] =
+ (state[endBit >> 3]
+ & (BYTE)~((1 << ((endBit & 7) + 1)) - 1))
+ | (BYTE)((val) >> (endBit - startBit - (endBit & 7)));
+ }
+#ifdef STATE_DEBUG
+ log_add (log_Debug, "State '%s' set to %d.", name, (int)val);
+#endif
+}
+
+DWORD
+getGameState32 (BYTE *state, int startBit)
+{
+ DWORD v;
+ int shift;
+
+ for (v = 0, shift = 0; shift < 32; shift += 8, startBit += 8)
+ {
+ v |= getGameState (state, startBit, startBit + 7) << shift;
+ }
+
+ return v;
+}
+
+void
+setGameState32 (BYTE *state, int startBit, DWORD val
+#ifdef STATE_DEBUG
+ , const char *name
+#endif
+)
+{
+ DWORD v = val;
+ int i;
+
+ for (i = 0; i < 4; ++i, v >>= 8, startBit += 8)
+ {
+ setGameState (state, startBit, startBit + 7, v & 0xff
+#ifdef STATE_DEBUG
+ , "(ignored)"
+#endif
+ );
+ }
+
+#ifdef STATE_DEBUG
+ log_add (log_Debug, "State '%s' set to %u.", name, (unsigned)val);
+#endif
+}
+
+void
+copyGameState (BYTE *dest, DWORD target, BYTE *src, DWORD begin, DWORD end)
+{
+ while (begin < end)
+ {
+ BYTE b;
+ DWORD delta = 7;
+ if (begin + delta > end)
+ delta = end - begin;
+ b = getGameState (src, begin, begin + delta);
+ setGameState (dest, target, target + delta, b);
+ begin += 8;
+ target += 8;
+ }
+}
+
+static void
+CreateRadar (void)
+{
+ if (RadarContext == 0)
+ {
+ RECT r;
+ CONTEXT OldContext;
+
+ RadarContext = CreateContext ("RadarContext");
+ OldContext = SetContext (RadarContext);
+ SetContextFGFrame (Screen);
+ r.corner.x = RADAR_X;
+ r.corner.y = RADAR_Y;
+ r.extent.width = RADAR_WIDTH;
+ r.extent.height = RADAR_HEIGHT;
+ SetContextClipRect (&r);
+ SetContext (OldContext);
+ }
+}
+
+BOOLEAN
+LoadSC2Data (void)
+{
+ if (FlagStatFrame == 0)
+ {
+ FlagStatFrame = CaptureDrawable (
+ LoadGraphic (FLAGSTAT_MASK_PMAP_ANIM));
+ if (FlagStatFrame == NULL)
+ return FALSE;
+
+ MiscDataFrame = CaptureDrawable (
+ LoadGraphic (MISCDATA_MASK_PMAP_ANIM));
+ if (MiscDataFrame == NULL)
+ return FALSE;
+
+ FontGradFrame = CaptureDrawable (
+ LoadGraphic (FONTGRAD_PMAP_ANIM));
+ }
+
+ CreateRadar ();
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_HYPERSPACE)
+ {
+ GLOBAL (ShipStamp.origin.x) =
+ GLOBAL (ShipStamp.origin.y) = -1;
+ }
+
+ return TRUE;
+}
+
+static void
+copyFleetInfo (FLEET_INFO *dst, SHIP_INFO *src, FLEET_STUFF *fleet)
+{
+ // other leading fields are irrelevant
+ dst->crew_level = src->crew_level;
+ dst->max_crew = src->max_crew;
+ dst->max_energy = src->max_energy;
+
+ dst->race_strings = src->race_strings;
+ dst->icons = src->icons;
+ dst->melee_icon = src->melee_icon;
+
+ dst->actual_strength = fleet->strength;
+ dst->known_loc = fleet->known_loc;
+}
+
+BOOLEAN
+InitGameStructures (void)
+{
+ COUNT i;
+
+ InitGlobData ();
+
+ PlayFrame = CaptureDrawable (LoadGraphic (PLAYMENU_ANIM));
+
+ {
+ COUNT num_ships;
+ SPECIES_ID s_id = ARILOU_ID;
+
+ num_ships = KOHR_AH_ID - s_id + 1
+ + 2; /* Yehat Rebels and Ur-Quan probe */
+
+ InitQueue (&GLOBAL (avail_race_q), num_ships, sizeof (FLEET_INFO));
+ for (i = 0; i < num_ships; ++i)
+ {
+ SPECIES_ID ship_ref;
+ HFLEETINFO hFleet;
+ FLEET_INFO *FleetPtr;
+
+ if (i < num_ships - 2)
+ ship_ref = s_id++;
+ else if (i == num_ships - 2)
+ ship_ref = YEHAT_ID;
+ else /* (i == num_ships - 1) */
+ ship_ref = UR_QUAN_PROBE_ID;
+
+ hFleet = AllocLink (&GLOBAL (avail_race_q));
+ if (!hFleet)
+ continue;
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ FleetPtr->SpeciesID = ship_ref;
+
+ if (i < num_ships - 1)
+ {
+ HMASTERSHIP hMasterShip;
+ MASTER_SHIP_INFO *MasterPtr;
+
+ hMasterShip = FindMasterShip (ship_ref);
+ MasterPtr = LockMasterShip (&master_q, hMasterShip);
+ // Grab a copy of loaded icons and strings (not owned)
+ copyFleetInfo (FleetPtr, &MasterPtr->ShipInfo,
+ &MasterPtr->Fleet);
+ UnlockMasterShip (&master_q, hMasterShip);
+ }
+ else
+ {
+ // Ur-Quan probe.
+ RACE_DESC *RDPtr = load_ship (FleetPtr->SpeciesID,
+ FALSE);
+ if (RDPtr)
+ { // Grab a copy of loaded icons and strings
+ copyFleetInfo (FleetPtr, &RDPtr->ship_info,
+ &RDPtr->fleet);
+ // avail_race_q owns these resources now
+ free_ship (RDPtr, FALSE, FALSE);
+ }
+ }
+
+ FleetPtr->allied_state = BAD_GUY;
+ FleetPtr->known_strength = 0;
+ FleetPtr->loc = FleetPtr->known_loc;
+ // XXX: Hack: Rebel special case
+ if (i == YEHAT_REBEL_SHIP)
+ FleetPtr->actual_strength = 0;
+ FleetPtr->growth = 0;
+ FleetPtr->growth_fract = 0;
+ FleetPtr->growth_err_term = 255 >> 1;
+ FleetPtr->days_left = 0;
+ FleetPtr->func_index = ~0;
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ PutQueue (&GLOBAL (avail_race_q), hFleet);
+ }
+ }
+
+ InitSISContexts ();
+ LoadSC2Data ();
+
+ InitPlanetInfo ();
+ InitGroupInfo (TRUE);
+
+ GLOBAL (glob_flags) = 0;
+
+ GLOBAL (ElementWorth[COMMON]) = 1;
+ GLOBAL_SIS (ElementAmounts[COMMON]) = 0;
+ GLOBAL (ElementWorth[CORROSIVE]) = 2;
+ GLOBAL_SIS (ElementAmounts[CORROSIVE]) = 0;
+ GLOBAL (ElementWorth[BASE_METAL]) = 3;
+ GLOBAL_SIS (ElementAmounts[BASE_METAL]) = 0;
+ GLOBAL (ElementWorth[NOBLE]) = 4;
+ GLOBAL_SIS (ElementAmounts[NOBLE]) = 0;
+ GLOBAL (ElementWorth[RARE_EARTH]) = 5;
+ GLOBAL_SIS (ElementAmounts[RARE_EARTH]) = 0;
+ GLOBAL (ElementWorth[PRECIOUS]) = 6;
+ GLOBAL_SIS (ElementAmounts[PRECIOUS]) = 0;
+ GLOBAL (ElementWorth[RADIOACTIVE]) = 8;
+ GLOBAL_SIS (ElementAmounts[RADIOACTIVE]) = 0;
+ GLOBAL (ElementWorth[EXOTIC]) = 25;
+ GLOBAL_SIS (ElementAmounts[EXOTIC]) = 0;
+
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ GLOBAL_SIS (DriveSlots[i]) = EMPTY_SLOT + 0;
+ GLOBAL_SIS (DriveSlots[5]) =
+ GLOBAL_SIS (DriveSlots[6]) = FUSION_THRUSTER;
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ GLOBAL_SIS (JetSlots[i]) = EMPTY_SLOT + 1;
+ GLOBAL_SIS (JetSlots[0]) =
+ GLOBAL_SIS (JetSlots[6]) = TURNING_JETS;
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ GLOBAL_SIS (ModuleSlots[i]) = EMPTY_SLOT + 2;
+ GLOBAL_SIS (ModuleSlots[15]) = GUN_WEAPON;
+ GLOBAL_SIS (ModuleSlots[2]) = CREW_POD;
+ GLOBAL_SIS (CrewEnlisted) = CREW_POD_CAPACITY;
+ GLOBAL_SIS (ModuleSlots[8]) = STORAGE_BAY;
+ GLOBAL_SIS (ModuleSlots[1]) = FUEL_TANK;
+ GLOBAL_SIS (FuelOnBoard) = 10 * FUEL_TANK_SCALE;
+
+ InitQueue (&GLOBAL (built_ship_q),
+ MAX_BUILT_SHIPS, sizeof (SHIP_FRAGMENT));
+ InitQueue (&GLOBAL (npc_built_ship_q), MAX_SHIPS_PER_SIDE,
+ sizeof (SHIP_FRAGMENT));
+ InitQueue (&GLOBAL (ip_group_q), MAX_BATTLE_GROUPS,
+ sizeof (IP_GROUP));
+ InitQueue (&GLOBAL (encounter_q), MAX_ENCOUNTERS, sizeof (ENCOUNTER));
+
+ GLOBAL (CurrentActivity) = IN_INTERPLANETARY | START_INTERPLANETARY;
+
+ GLOBAL_SIS (ResUnits) = 0;
+ GLOBAL (CrewCost) = 3;
+ GLOBAL (FuelCost) = 20;
+ GLOBAL (ModuleCost[PLANET_LANDER]) = 500 / MODULE_COST_SCALE;
+ GLOBAL (ModuleCost[FUSION_THRUSTER]) = 500 / MODULE_COST_SCALE;
+ GLOBAL (ModuleCost[TURNING_JETS]) = 500 / MODULE_COST_SCALE;
+ GLOBAL (ModuleCost[CREW_POD]) = 2000 / MODULE_COST_SCALE;
+ GLOBAL (ModuleCost[STORAGE_BAY]) = 750 / MODULE_COST_SCALE;
+ GLOBAL (ModuleCost[FUEL_TANK]) = 500 / MODULE_COST_SCALE;
+ GLOBAL (ModuleCost[DYNAMO_UNIT]) = 2000 / MODULE_COST_SCALE;
+ GLOBAL (ModuleCost[GUN_WEAPON]) = 2000 / MODULE_COST_SCALE;
+
+ GLOBAL_SIS (NumLanders) = 1;
+
+ utf8StringCopy (GLOBAL_SIS (ShipName), sizeof (GLOBAL_SIS (ShipName)),
+ GAME_STRING (NAMING_STRING_BASE + 2));
+ utf8StringCopy (GLOBAL_SIS (CommanderName),
+ sizeof (GLOBAL_SIS (CommanderName)),
+ GAME_STRING (NAMING_STRING_BASE + 3));
+
+ SetRaceAllied (HUMAN_SHIP, TRUE);
+ CloneShipFragment (HUMAN_SHIP, &GLOBAL (built_ship_q), 0);
+
+ GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX (SOL_X);
+ GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY (SOL_Y);
+ CurStarDescPtr = 0;
+ GLOBAL (autopilot.x) = ~0;
+ GLOBAL (autopilot.y) = ~0;
+
+ return (TRUE);
+}
+
+void
+FreeSC2Data (void)
+{
+ DestroyContext (RadarContext);
+ RadarContext = 0;
+ DestroyDrawable (ReleaseDrawable (FontGradFrame));
+ FontGradFrame = 0;
+ DestroyDrawable (ReleaseDrawable (MiscDataFrame));
+ MiscDataFrame = 0;
+ DestroyDrawable (ReleaseDrawable (FlagStatFrame));
+ FlagStatFrame = 0;
+}
+
+void
+UninitGameStructures (void)
+{
+ HFLEETINFO hStarShip;
+
+ UninitQueue (&GLOBAL (encounter_q));
+ UninitQueue (&GLOBAL (ip_group_q));
+ UninitQueue (&GLOBAL (npc_built_ship_q));
+ UninitQueue (&GLOBAL (built_ship_q));
+ UninitGroupInfo ();
+ UninitPlanetInfo ();
+
+// FreeSC2Data ();
+
+ // The only resources avail_race_q owns are the Ur-Quan probe's
+ // so free them now
+ hStarShip = GetTailLink (&GLOBAL (avail_race_q));
+ if (hStarShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ DestroyDrawable (ReleaseDrawable (FleetPtr->melee_icon));
+ DestroyDrawable (ReleaseDrawable (FleetPtr->icons));
+ DestroyStringTable (ReleaseStringTable (FleetPtr->race_strings));
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+
+ UninitQueue (&GLOBAL (avail_race_q));
+
+ DestroyDrawable (ReleaseDrawable (PlayFrame));
+ PlayFrame = 0;
+}
+
+void
+InitGlobData (void)
+{
+ COUNT i;
+
+ i = GLOBAL (glob_flags);
+ memset (&GlobData, 0, sizeof (GlobData));
+ GLOBAL (glob_flags) = (BYTE)i;
+
+ GLOBAL (DisplayArray) = DisplayArray;
+}
+
+
+BOOLEAN
+inFullGame (void)
+{
+ ACTIVITY act = LOBYTE (GLOBAL (CurrentActivity));
+ return (act == IN_LAST_BATTLE || act == IN_ENCOUNTER ||
+ act == IN_HYPERSPACE || act == IN_INTERPLANETARY ||
+ act == WON_LAST_BATTLE);
+}
+
+BOOLEAN
+inSuperMelee (void)
+{
+ return (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE);
+ // TODO: && !inMainMenu ()
+}
+
+#if 0
+BOOLEAN
+inBattle (void)
+{
+ // TODO: IN_BATTLE is also set while in HyperSpace/QuasiSpace.
+ return ((GLOBAL (CurrentActivity) & IN_BATTLE) != 0);
+}
+#endif
+
+#if 0
+// Disabled for now as there are similar functions in uqm/planets/planets.h
+// Pre: inFullGame()
+BOOLEAN
+inInterPlanetary (void)
+{
+ assert (inFullGame ());
+ return (pSolarSysState != NULL);
+}
+
+// Pre: inFullGame()
+BOOLEAN
+inSolarSystem (void)
+{
+ assert (inFullGame ());
+ return (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY);
+}
+
+// Pre: inFullGame()
+BOOLEAN
+inOrbit (void)
+{
+ assert (inFullGame ());
+ return (pSolarSysState != NULL) &&
+ (pSolarSysState->pOrbitalDesc != NULL);
+}
+#endif
+
+// In HyperSpace or QuasiSpace
+// Pre: inFullGame()
+BOOLEAN
+inHQSpace (void)
+{
+ //assert (inFullGame ());
+ return (LOBYTE (GLOBAL (CurrentActivity)) == IN_HYPERSPACE);
+ // IN_HYPERSPACE is also set for QuasiSpace
+}
+
+// In HyperSpace
+// Pre: inFullGame()
+BOOLEAN
+inHyperSpace (void)
+{
+ //assert (inFullGame ());
+ return (LOBYTE (GLOBAL (CurrentActivity)) == IN_HYPERSPACE) &&
+ (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1);
+ // IN_HYPERSPACE is also set for QuasiSpace
+}
+
+// In QuasiSpace
+// Pre: inFullGame()
+BOOLEAN
+inQuasiSpace (void)
+{
+ //assert (inFullGame ());
+ return (LOBYTE (GLOBAL (CurrentActivity)) == IN_HYPERSPACE) &&
+ (GET_GAME_STATE (ARILOU_SPACE_SIDE) > 1);
+ // IN_HYPERSPACE is also set for QuasiSpace
+}
+
diff --git a/src/uqm/globdata.h b/src/uqm/globdata.h
new file mode 100644
index 0000000..216cadf
--- /dev/null
+++ b/src/uqm/globdata.h
@@ -0,0 +1,1059 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_GLOBDATA_H_
+#define UQM_GLOBDATA_H_
+
+#include "clock.h"
+#include "libs/gfxlib.h"
+#include "libs/reslib.h"
+#include "libs/sndlib.h"
+#include "sis.h"
+#include "velocity.h"
+#include "commanim.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+// general numbers-speech generator info
+// should accomodate most common base-10 languages
+// many languages require various plural forms
+// for digit names like "hundred"
+// possibly needs reworking for others
+typedef struct
+{
+ // an array of these structs must be in ascending remainder order
+ // terminate the array with Divider == 0
+
+ // digit divider, i.e. 1, 10, 100, etc.
+ int Divider;
+ // maximum remainder for this name
+ // name will be used if Number % Divider <= MaxRemainder
+ int MaxRemainder;
+ // string table index for this name
+ // i.e. "hundred" in English
+ COUNT StrIndex;
+} SPEECH_DIGITNAME;
+
+typedef struct
+{
+ // digit divider, i.e. 1, 10, 100, etc.
+ int Divider;
+ // digit sub, i.e. 10 for teens
+ // subtracted from the value to get an index into StrDigits
+ int Subtrahend;
+ // ptr to 10 indices for this digit
+ // index is string table ptr when > 0
+ // is invalid (should not happen) or
+ // is a a 'skip digit' indicator when == 0
+ // StrDigits can be NULL, in which case
+ // the value is interpreted recursively
+ COUNT *StrDigits;
+ // digit Names, can be NULL, in which case
+ // CommonNameIndex is used
+ SPEECH_DIGITNAME *Names;
+ // common digit name string table index
+ // i.e. "hundred" in English
+ COUNT CommonNameIndex;
+} SPEECH_DIGIT;
+
+// this accomodates up to "billions" in english
+#define MAX_SPEECH_DIGITS 7
+
+typedef struct
+{
+ // slots used in Digits array
+ COUNT NumDigits;
+ // slots for each digit in numbers
+ // teens is exception
+ // 0-9, 10-19, ..20-90, ..100-900, etc.
+ SPEECH_DIGIT Digits[MAX_SPEECH_DIGITS];
+} NUMBER_SPEECH_DESC;
+typedef const NUMBER_SPEECH_DESC *NUMBER_SPEECH;
+
+typedef DWORD LDAS_FLAGS;
+#define LDASF_NONE ((LDAS_FLAGS) 0 )
+#define LDASF_USE_ALTERNATE ((LDAS_FLAGS)(1 << 0))
+
+typedef struct
+{
+ void (*init_encounter_func) (void);
+ /* Called when entering communications */
+ void (*post_encounter_func) (void);
+ /* Called when leaving communications or combat normally */
+ COUNT (*uninit_encounter_func) (void);
+ /* Called when encounter is done for cleanup */
+
+ RESOURCE AlienFrameRes;
+ RESOURCE AlienFontRes;
+ Color AlienTextFColor, AlienTextBColor;
+ POINT AlienTextBaseline;
+ COUNT AlienTextWidth;
+ TEXT_ALIGN AlienTextAlign;
+ TEXT_VALIGN AlienTextValign;
+ RESOURCE AlienColorMapRes;
+ RESOURCE AlienSongRes;
+ RESOURCE AlienAltSongRes;
+ LDAS_FLAGS AlienSongFlags;
+
+ RESOURCE ConversationPhrasesRes;
+
+ COUNT NumAnimations;
+ ANIMATION_DESC AlienAmbientArray[MAX_ANIMATIONS];
+
+ // Transition animation to/from talking state;
+ // the first frame is neutral (sort of like YOYO_ANIM)
+ ANIMATION_DESC AlienTransitionDesc;
+ // Talking animation, like RANDOM_ANIM, except random frames
+ // always alternate with a neutral frame;
+ // the first frame is neutral
+ ANIMATION_DESC AlienTalkDesc;
+
+ NUMBER_SPEECH AlienNumberSpeech;
+
+ FRAME AlienFrame;
+ FONT AlienFont;
+ COLORMAP AlienColorMap;
+ MUSIC_REF AlienSong;
+ STRING ConversationPhrases;
+
+} LOCDATA;
+
+enum
+{
+ PORTAL_SPAWNER_DEVICE = 0,
+ TALKING_PET_DEVICE,
+ UTWIG_BOMB_DEVICE,
+ SUN_EFFICIENCY_DEVICE,
+ ROSY_SPHERE_DEVICE,
+ AQUA_HELIX_DEVICE,
+ CLEAR_SPINDLE_DEVICE,
+ ULTRON_0_DEVICE,
+ ULTRON_1_DEVICE,
+ ULTRON_2_DEVICE,
+ ULTRON_3_DEVICE,
+ MAIDENS_DEVICE,
+ UMGAH_HYPERWAVE_DEVICE,
+ BURVIX_HYPERWAVE_DEVICE,
+ DATA_PLATE_1_DEVICE,
+ DATA_PLATE_2_DEVICE,
+ DATA_PLATE_3_DEVICE,
+ TAALO_PROTECTOR_DEVICE,
+ EGG_CASING0_DEVICE,
+ EGG_CASING1_DEVICE,
+ EGG_CASING2_DEVICE,
+ SYREEN_SHUTTLE_DEVICE,
+ VUX_BEAST_DEVICE,
+ DESTRUCT_CODE_DEVICE,
+ URQUAN_WARP_DEVICE,
+ ARTIFACT_2_DEVICE,
+ ARTIFACT_3_DEVICE,
+ LUNAR_BASE_DEVICE,
+
+ NUM_DEVICES
+};
+
+#define YEARS_TO_KOHRAH_VICTORY 4
+
+#define START_GAME_STATE enum {
+#define ADD_GAME_STATE(SName,NumBits) SName, END_##SName = SName + NumBits - 1,
+#define END_GAME_STATE NUM_GAME_STATE_BITS };
+
+START_GAME_STATE
+ /* Shofixti states */
+ ADD_GAME_STATE (SHOFIXTI_VISITS, 3)
+ ADD_GAME_STATE (SHOFIXTI_STACK1, 2)
+ ADD_GAME_STATE (SHOFIXTI_STACK2, 3)
+ ADD_GAME_STATE (SHOFIXTI_STACK3, 2)
+ ADD_GAME_STATE (SHOFIXTI_KIA, 1)
+ ADD_GAME_STATE (SHOFIXTI_BRO_KIA, 1)
+ ADD_GAME_STATE (SHOFIXTI_RECRUITED, 1)
+
+ ADD_GAME_STATE (SHOFIXTI_MAIDENS, 1) /* Did you find the babes yet? */
+ ADD_GAME_STATE (MAIDENS_ON_SHIP, 1)
+ ADD_GAME_STATE (BATTLE_SEGUE, 1)
+ /* Set to 0 in init_xxx_comm() if communications directly
+ * follows an encounter.
+ * Set to 1 in init_xxx_comm() if the player gets to decide
+ * whether to attack or talk.
+ * Set to 1 in communication when battle follows the
+ * communication. It is still valid when uninit_xxx_comm() gets
+ * called after combat or communication.
+ */
+ ADD_GAME_STATE (PLANETARY_LANDING, 1)
+ ADD_GAME_STATE (PLANETARY_CHANGE, 1)
+ /* Flag set to 1 when the planet information for the current
+ * world is changed since it was last saved to the starinfo.dat
+ * file. Set when picking up bio, mineral, or energy nodes.
+ * When there's no current world, it should be 0.
+ */
+
+ /* Spathi states */
+ ADD_GAME_STATE (SPATHI_VISITS, 3)
+ ADD_GAME_STATE (SPATHI_HOME_VISITS, 3)
+ ADD_GAME_STATE (FOUND_PLUTO_SPATHI, 2)
+ /* 0 - Haven't met Fwiffo.
+ * 1 - Met Fwiffo on Pluto, now talking to him.
+ * 2 - Met Fwiffo on Pluto, after dialog.
+ * 3 - Met Fwiffo, and have reported to the Safe Ones on
+ * the Spathi moon that he was either killed, or that
+ * you have him on board.
+ */
+ ADD_GAME_STATE (SPATHI_SHIELDED_SELVES, 1)
+ ADD_GAME_STATE (SPATHI_CREATURES_EXAMINED, 1)
+ ADD_GAME_STATE (SPATHI_CREATURES_ELIMINATED, 1)
+ ADD_GAME_STATE (UMGAH_BROADCASTERS, 1)
+ ADD_GAME_STATE (SPATHI_MANNER, 2)
+ ADD_GAME_STATE (SPATHI_QUEST, 1)
+ ADD_GAME_STATE (LIED_ABOUT_CREATURES, 2)
+ ADD_GAME_STATE (SPATHI_PARTY, 1)
+ ADD_GAME_STATE (KNOW_SPATHI_PASSWORD, 1)
+
+ ADD_GAME_STATE (ILWRATH_HOME_VISITS, 3)
+ ADD_GAME_STATE (ILWRATH_CHMMR_VISITS, 1)
+
+ ADD_GAME_STATE (ARILOU_SPACE, 1)
+ /* 0 if the periodically opening QuasiSpace portal is
+ * closed or closing.
+ * 1 if the periodically opening QuasiSpace portal is
+ * open or opening.
+ */
+ ADD_GAME_STATE (ARILOU_SPACE_SIDE, 2)
+ /* 0 if in HyperSpace and not just emerged from the periodically
+ * opening QuasiSpace portal.
+ * 1 if in HyperSpace and just emerged from the periodically
+ * QuasiSpace portal (still on the portal).
+ * 2 if in QuasiSpace and just emerged from the periodically
+ * opening portal (still on the portal).
+ * 3 if in QuasiSpace and not just emerged from the
+ * periodically opening portal.
+ */
+ ADD_GAME_STATE (ARILOU_SPACE_COUNTER, 4)
+ /* Keeps track of how far the periodically opening QuasiSpace
+ * portal is open. (This determines the image)
+ * 0 <= ARILOU_SPACE_COUNTER <= 9
+ * 0 means totally closed.
+ * 9 means completely open.
+ */
+
+ ADD_GAME_STATE (LANDER_SHIELDS, 4)
+
+ ADD_GAME_STATE (MET_MELNORME, 1)
+ ADD_GAME_STATE (MELNORME_RESCUE_REFUSED, 1)
+ ADD_GAME_STATE (MELNORME_RESCUE_COUNT, 3)
+ ADD_GAME_STATE (TRADED_WITH_MELNORME, 1)
+ ADD_GAME_STATE (WHY_MELNORME_PURPLE, 1)
+ ADD_GAME_STATE (MELNORME_CREDIT0, 8)
+ ADD_GAME_STATE (MELNORME_CREDIT1, 8)
+ ADD_GAME_STATE (MELNORME_BUSINESS_COUNT, 2)
+ ADD_GAME_STATE (MELNORME_YACK_STACK0, 2)
+ ADD_GAME_STATE (MELNORME_YACK_STACK1, 2)
+ ADD_GAME_STATE (MELNORME_YACK_STACK2, 4)
+ ADD_GAME_STATE (MELNORME_YACK_STACK3, 3)
+ ADD_GAME_STATE (MELNORME_YACK_STACK4, 2)
+ ADD_GAME_STATE (WHY_MELNORME_BLUE, 1)
+ ADD_GAME_STATE (MELNORME_ANGER, 2)
+ ADD_GAME_STATE (MELNORME_MIFFED_COUNT, 2)
+ ADD_GAME_STATE (MELNORME_PISSED_COUNT, 2)
+ ADD_GAME_STATE (MELNORME_HATE_COUNT, 2)
+
+ ADD_GAME_STATE (PROBE_MESSAGE_DELIVERED, 1)
+ ADD_GAME_STATE (PROBE_ILWRATH_ENCOUNTER, 1)
+
+ ADD_GAME_STATE (STARBASE_AVAILABLE, 1)
+ ADD_GAME_STATE (STARBASE_VISITED, 1)
+ ADD_GAME_STATE (RADIOACTIVES_PROVIDED, 1)
+ ADD_GAME_STATE (LANDERS_LOST, 1)
+ ADD_GAME_STATE (GIVEN_FUEL_BEFORE, 1)
+
+ ADD_GAME_STATE (AWARE_OF_SAMATRA, 1)
+ ADD_GAME_STATE (YEHAT_CAVALRY_ARRIVED, 1)
+ ADD_GAME_STATE (URQUAN_MESSED_UP, 1)
+
+ ADD_GAME_STATE (MOONBASE_DESTROYED, 1)
+ ADD_GAME_STATE (WILL_DESTROY_BASE, 1)
+
+ ADD_GAME_STATE (ARTIFACT_2_ON_SHIP, 1)
+ ADD_GAME_STATE (ARTIFACT_3_ON_SHIP, 1)
+
+ ADD_GAME_STATE (KOHR_AH_KILLED_ALL, 1)
+
+ ADD_GAME_STATE (STARBASE_YACK_STACK1, 1)
+
+ ADD_GAME_STATE (DISCUSSED_PORTAL_SPAWNER, 1)
+ ADD_GAME_STATE (DISCUSSED_TALKING_PET, 1)
+ ADD_GAME_STATE (DISCUSSED_UTWIG_BOMB, 1)
+ ADD_GAME_STATE (DISCUSSED_SUN_EFFICIENCY, 1)
+ ADD_GAME_STATE (DISCUSSED_ROSY_SPHERE, 1)
+ ADD_GAME_STATE (DISCUSSED_AQUA_HELIX, 1)
+ ADD_GAME_STATE (DISCUSSED_CLEAR_SPINDLE, 1)
+ ADD_GAME_STATE (DISCUSSED_ULTRON, 1)
+ ADD_GAME_STATE (DISCUSSED_MAIDENS, 1)
+ ADD_GAME_STATE (DISCUSSED_UMGAH_HYPERWAVE, 1)
+ ADD_GAME_STATE (DISCUSSED_BURVIX_HYPERWAVE, 1)
+ ADD_GAME_STATE (SYREEN_WANT_PROOF, 1)
+ ADD_GAME_STATE (PLAYER_HAVING_SEX, 1)
+ ADD_GAME_STATE (MET_ARILOU, 1)
+ ADD_GAME_STATE (DISCUSSED_TAALO_PROTECTOR, 1)
+ ADD_GAME_STATE (DISCUSSED_EGG_CASING0, 1)
+ ADD_GAME_STATE (DISCUSSED_EGG_CASING1, 1)
+ ADD_GAME_STATE (DISCUSSED_EGG_CASING2, 1)
+ ADD_GAME_STATE (DISCUSSED_SYREEN_SHUTTLE, 1)
+ ADD_GAME_STATE (DISCUSSED_VUX_BEAST, 1)
+ ADD_GAME_STATE (DISCUSSED_DESTRUCT_CODE, 1)
+ ADD_GAME_STATE (DISCUSSED_URQUAN_WARP, 1)
+ ADD_GAME_STATE (DISCUSSED_ARTIFACT_2, 1)
+ ADD_GAME_STATE (DISCUSSED_ARTIFACT_3, 1)
+
+ ADD_GAME_STATE (ATTACKED_DRUUGE, 1)
+
+ ADD_GAME_STATE (NEW_ALLIANCE_NAME, 2)
+
+ ADD_GAME_STATE (PORTAL_COUNTER, 4)
+ /* Set to 1 when the player opens a QuasiSpace portal.
+ * It will then be increased to 10, at which time
+ * the portal is completely open. (This determines the image).
+ */
+
+ ADD_GAME_STATE (BURVIXESE_BROADCASTERS, 1)
+ ADD_GAME_STATE (BURV_BROADCASTERS_ON_SHIP, 1)
+
+ ADD_GAME_STATE (UTWIG_BOMB, 1)
+ ADD_GAME_STATE (UTWIG_BOMB_ON_SHIP, 1)
+
+ ADD_GAME_STATE (AQUA_HELIX, 1)
+ ADD_GAME_STATE (AQUA_HELIX_ON_SHIP, 1)
+
+ ADD_GAME_STATE (SUN_DEVICE, 1)
+ ADD_GAME_STATE (SUN_DEVICE_ON_SHIP, 1)
+
+ ADD_GAME_STATE (TAALO_PROTECTOR, 1)
+ ADD_GAME_STATE (TAALO_PROTECTOR_ON_SHIP, 1)
+
+ ADD_GAME_STATE (SHIP_VAULT_UNLOCKED, 1)
+ ADD_GAME_STATE (SYREEN_SHUTTLE, 1)
+
+ ADD_GAME_STATE (PORTAL_KEY, 1)
+ ADD_GAME_STATE (PORTAL_KEY_ON_SHIP, 1)
+
+ ADD_GAME_STATE (VUX_BEAST, 1)
+ ADD_GAME_STATE (VUX_BEAST_ON_SHIP, 1)
+
+ ADD_GAME_STATE (TALKING_PET, 1)
+ ADD_GAME_STATE (TALKING_PET_ON_SHIP, 1)
+
+ ADD_GAME_STATE (MOONBASE_ON_SHIP, 1)
+
+ ADD_GAME_STATE (KOHR_AH_FRENZY, 1)
+ ADD_GAME_STATE (KOHR_AH_VISITS, 2)
+ ADD_GAME_STATE (KOHR_AH_BYES, 1)
+
+ ADD_GAME_STATE (SLYLANDRO_HOME_VISITS, 3)
+ ADD_GAME_STATE (DESTRUCT_CODE_ON_SHIP, 1)
+
+ ADD_GAME_STATE (ILWRATH_VISITS, 3)
+ ADD_GAME_STATE (ILWRATH_DECEIVED, 1)
+ ADD_GAME_STATE (FLAGSHIP_CLOAKED, 1)
+
+ ADD_GAME_STATE (MYCON_VISITS, 3)
+ ADD_GAME_STATE (MYCON_HOME_VISITS, 3)
+ ADD_GAME_STATE (MYCON_AMBUSH, 1)
+ ADD_GAME_STATE (MYCON_FELL_FOR_AMBUSH, 1)
+ /* Set to 1 when the Mycon have been told about Organon
+ * and are moving towards it.
+ */
+
+ ADD_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 8)
+ /* This state seems to be used to distinguish between different
+ * places where one may have an conversation with an alien.
+ * Like home world, other world, space.
+ * Why this needs 8 bits I don't know. Only specific
+ * combinations of bits seem to be used (0, 1, or all bits).
+ * A closer investigation is desirable. - SvdB
+ * Bit 4 is set when initiating communication with the Ilwrath
+ * homeworld by means of a HyperWave Broadcaster.
+ * Bit 5 is set when initiating communication with an Ilwrath
+ * ship by means of a HyperWave Broadcaster.
+ * All bits are cleared when communication is over.
+ */
+
+ ADD_GAME_STATE (ORZ_VISITS, 3)
+ ADD_GAME_STATE (TAALO_VISITS, 3)
+ ADD_GAME_STATE (ORZ_MANNER, 2)
+
+ ADD_GAME_STATE (PROBE_EXHIBITED_BUG, 1)
+ ADD_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 1)
+
+ ADD_GAME_STATE (URQUAN_VISITS, 3)
+ ADD_GAME_STATE (PLAYER_HYPNOTIZED, 1)
+
+ ADD_GAME_STATE (VUX_VISITS, 3)
+ ADD_GAME_STATE (VUX_HOME_VISITS, 3)
+ ADD_GAME_STATE (ZEX_VISITS, 3)
+ ADD_GAME_STATE (ZEX_IS_DEAD, 1)
+ ADD_GAME_STATE (KNOW_ZEX_WANTS_MONSTER, 1)
+
+ ADD_GAME_STATE (UTWIG_VISITS, 3)
+ ADD_GAME_STATE (UTWIG_HOME_VISITS, 3)
+ ADD_GAME_STATE (BOMB_VISITS, 3)
+ ADD_GAME_STATE (ULTRON_CONDITION, 3)
+ /* 0 if the Supox still have the Ultron
+ * 1 if the Captain has the Ultron, completely broken
+ * 2 if the Captain has the Ultron, with 1 fix
+ * 3 if the Captain has the Ultron, with 2 fixes
+ * 4 if the Captain has the Ultron, completely restored
+ * 5 if the Ultron has been returned to the Utwig
+ */
+ ADD_GAME_STATE (UTWIG_HAVE_ULTRON, 1)
+ ADD_GAME_STATE (BOMB_UNPROTECTED, 1)
+
+ ADD_GAME_STATE (TAALO_UNPROTECTED, 1)
+
+ ADD_GAME_STATE (TALKING_PET_VISITS, 3)
+ ADD_GAME_STATE (TALKING_PET_HOME_VISITS, 3)
+ ADD_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES, 1)
+ /* The Umgah have come under the influence of the Talking Pet */
+ ADD_GAME_STATE (KNOW_UMGAH_ZOMBIES, 1)
+ /* The Captain is aware that something is up with the Umgah */
+
+ ADD_GAME_STATE (ARILOU_VISITS, 3)
+ ADD_GAME_STATE (ARILOU_HOME_VISITS, 3)
+ ADD_GAME_STATE (KNOW_ARILOU_WANT_WRECK, 1)
+ ADD_GAME_STATE (ARILOU_CHECKED_UMGAH, 2)
+ ADD_GAME_STATE (PORTAL_SPAWNER, 1)
+ ADD_GAME_STATE (PORTAL_SPAWNER_ON_SHIP, 1)
+
+ ADD_GAME_STATE (UMGAH_VISITS, 3)
+ ADD_GAME_STATE (UMGAH_HOME_VISITS, 3)
+ ADD_GAME_STATE (MET_NORMAL_UMGAH, 1)
+
+ ADD_GAME_STATE (SYREEN_HOME_VISITS, 3)
+ ADD_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP, 1)
+ ADD_GAME_STATE (KNOW_SYREEN_VAULT, 1)
+
+ ADD_GAME_STATE (EGG_CASE0_ON_SHIP, 1)
+ ADD_GAME_STATE (SUN_DEVICE_UNGUARDED, 1)
+
+ ADD_GAME_STATE (ROSY_SPHERE_ON_SHIP, 1)
+ /* The Rosy Sphere is aboard the flagship, i.e. It has been
+ * acquired from the Druuge, but not yet inserted in the broken
+ * Ultron. cf. ROSY_SPHERE */
+
+ ADD_GAME_STATE (CHMMR_HOME_VISITS, 3)
+ ADD_GAME_STATE (CHMMR_EMERGING, 1)
+ ADD_GAME_STATE (CHMMR_UNLEASHED, 1)
+ ADD_GAME_STATE (CHMMR_BOMB_STATE, 2)
+ /* 0 - Nothing is known about the Precursor Bomb.
+ * 1 - The captain knows from the Chmmr that some extremely
+ * powerful weapon is needed to destroy the Sa-Matra.
+ * 2 - Installation of the precursor bomb has started.
+ * 3 - Left the starbase after installation of the Precursor bomb.
+ */
+
+ ADD_GAME_STATE (DRUUGE_DISCLAIMER, 1)
+
+ ADD_GAME_STATE (YEHAT_VISITS, 3)
+ ADD_GAME_STATE (YEHAT_REBEL_VISITS, 3)
+ ADD_GAME_STATE (YEHAT_HOME_VISITS, 3)
+ ADD_GAME_STATE (YEHAT_CIVIL_WAR, 1)
+ ADD_GAME_STATE (YEHAT_ABSORBED_PKUNK, 1)
+ ADD_GAME_STATE (YEHAT_SHIP_MONTH, 4)
+ ADD_GAME_STATE (YEHAT_SHIP_DAY, 5)
+ ADD_GAME_STATE (YEHAT_SHIP_YEAR, 5)
+
+ ADD_GAME_STATE (CLEAR_SPINDLE, 1)
+ ADD_GAME_STATE (PKUNK_VISITS, 3)
+ ADD_GAME_STATE (PKUNK_HOME_VISITS, 3)
+ ADD_GAME_STATE (PKUNK_SHIP_MONTH, 4)
+ /* The month in PKUNK_SHIP_YEAR that new ships are available
+ * from the Pkunk. */
+ ADD_GAME_STATE (PKUNK_SHIP_DAY, 5)
+ /* The day of the month in PKUNK_SHIP_MONTH in PKUNK_SHIP_YEAR
+ * that new ships are available. */
+ ADD_GAME_STATE (PKUNK_SHIP_YEAR, 5)
+ /* The year that new ships are available from the Pkunk
+ * (stored as an offset from the year the game starts). */
+ ADD_GAME_STATE (PKUNK_MISSION, 3)
+
+ ADD_GAME_STATE (SUPOX_VISITS, 3)
+ ADD_GAME_STATE (SUPOX_HOME_VISITS, 3)
+
+ ADD_GAME_STATE (THRADD_VISITS, 3)
+ ADD_GAME_STATE (THRADD_HOME_VISITS, 3)
+ ADD_GAME_STATE (HELIX_VISITS, 3)
+ ADD_GAME_STATE (HELIX_UNPROTECTED, 1)
+ ADD_GAME_STATE (THRADD_CULTURE, 2)
+ ADD_GAME_STATE (THRADD_MISSION, 3)
+ /* 0 if the Thraddash fleet hasn't left the Thraddash home world.
+ * 1 if the Thraddash are heading towards Kohr-Ah territory.
+ * 2 if the Thraddash are fighting the Kohr-Ah.
+ * 3 if the Thraddash are returning from Kohr-Ah territory.
+ * 4 if the Thraddash fleet is back at the Thraddash home world.
+ */
+
+ ADD_GAME_STATE (DRUUGE_VISITS, 3)
+ ADD_GAME_STATE (DRUUGE_HOME_VISITS, 3)
+ ADD_GAME_STATE (ROSY_SPHERE, 1)
+ /* The play has or has had the Rosy Sphere.
+ * cf. ROSY_SHERE_ON_SHIP */
+ ADD_GAME_STATE (SCANNED_MAIDENS, 1)
+ ADD_GAME_STATE (SCANNED_FRAGMENTS, 1)
+ ADD_GAME_STATE (SCANNED_CASTER, 1)
+ ADD_GAME_STATE (SCANNED_SPAWNER, 1)
+ ADD_GAME_STATE (SCANNED_ULTRON, 1)
+
+ ADD_GAME_STATE (ZOQFOT_INFO, 2)
+ ADD_GAME_STATE (ZOQFOT_HOSTILE, 1)
+ ADD_GAME_STATE (ZOQFOT_HOME_VISITS, 3)
+ ADD_GAME_STATE (MET_ZOQFOT, 1)
+ ADD_GAME_STATE (ZOQFOT_DISTRESS, 2)
+ /* 0 if the Zoq-Fot-Pik aren't in distress
+ * 1 if the Zoq-Fot-Pik are under attack by the Kohr-Ah
+ * 2 if the Zoq-Fot-Pik have been destroyed because of this
+ * attack (not by the Kohr-Ah final victory cleansing)
+ */
+
+ ADD_GAME_STATE (EGG_CASE1_ON_SHIP, 1)
+ ADD_GAME_STATE (EGG_CASE2_ON_SHIP, 1)
+ ADD_GAME_STATE (MYCON_SUN_VISITS, 3)
+ ADD_GAME_STATE (ORZ_HOME_VISITS, 3)
+
+ ADD_GAME_STATE (MELNORME_FUEL_PROCEDURE, 1)
+ ADD_GAME_STATE (MELNORME_TECH_PROCEDURE, 1)
+ ADD_GAME_STATE (MELNORME_INFO_PROCEDURE, 1)
+
+ ADD_GAME_STATE (MELNORME_TECH_STACK, 4)
+ /* MELNORME_TECH_STACK is now unused */
+ ADD_GAME_STATE (MELNORME_EVENTS_INFO_STACK, 5)
+ ADD_GAME_STATE (MELNORME_ALIEN_INFO_STACK, 5)
+ ADD_GAME_STATE (MELNORME_HISTORY_INFO_STACK, 5)
+
+ ADD_GAME_STATE (RAINBOW_WORLD0, 8)
+ /* Low byte of a bit array, one bit per rainbow world.
+ * Each bit is set if the rainbow world has been visited.
+ * The lowest bit is for the first star in the star_array
+ * with RAINBOW_DEFINED, and so on.
+ */
+ ADD_GAME_STATE (RAINBOW_WORLD1, 2)
+ /* High 2 bits of the bit array of which RAINBOW_WORLD0
+ * is the low byte.
+ */
+ ADD_GAME_STATE (MELNORME_RAINBOW_COUNT, 4)
+ /* The number of rainbow world locations sold to the Melnorme. */
+
+ ADD_GAME_STATE (USED_BROADCASTER, 1)
+ ADD_GAME_STATE (BROADCASTER_RESPONSE, 1)
+
+ ADD_GAME_STATE (IMPROVED_LANDER_SPEED, 1)
+ ADD_GAME_STATE (IMPROVED_LANDER_CARGO, 1)
+ ADD_GAME_STATE (IMPROVED_LANDER_SHOT, 1)
+
+ ADD_GAME_STATE (MET_ORZ_BEFORE, 1)
+ ADD_GAME_STATE (YEHAT_REBEL_TOLD_PKUNK, 1)
+ ADD_GAME_STATE (PLAYER_HAD_SEX, 1)
+ ADD_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP, 1)
+
+ ADD_GAME_STATE (LIGHT_MINERAL_LOAD, 3)
+ ADD_GAME_STATE (MEDIUM_MINERAL_LOAD, 3)
+ ADD_GAME_STATE (HEAVY_MINERAL_LOAD, 3)
+
+ ADD_GAME_STATE (STARBASE_BULLETS0, 8)
+ ADD_GAME_STATE (STARBASE_BULLETS1, 8)
+ ADD_GAME_STATE (STARBASE_BULLETS2, 8)
+ ADD_GAME_STATE (STARBASE_BULLETS3, 8)
+
+ ADD_GAME_STATE (STARBASE_MONTH, 4)
+ ADD_GAME_STATE (STARBASE_DAY, 5)
+
+ ADD_GAME_STATE (CREW_SOLD_TO_DRUUGE0, 8)
+ ADD_GAME_STATE (CREW_PURCHASED0, 8)
+ ADD_GAME_STATE (CREW_PURCHASED1, 8)
+
+ ADD_GAME_STATE (URQUAN_PROTECTING_SAMATRA, 1)
+
+#define THRADDASH_BODY_THRESHOLD 25
+ ADD_GAME_STATE (THRADDASH_BODY_COUNT, 5)
+
+ ADD_GAME_STATE (UTWIG_SUPOX_MISSION, 3)
+ /* 0 if the Utwig and Supox fleet haven't left their home world.
+ * 1 if the U&S are on their way towards the Kohr-Ah
+ * 2 if the U&S are fighting the Kohr-Ah (first 80 days)
+ * 3 does not occur
+ * 4 if the U&S are fighting the Kohr-Ah (second 80 days)
+ * 5 if the U&S are returning home.
+ * 6 if the U&S are back at their home world.
+ */
+ ADD_GAME_STATE (SPATHI_INFO, 3)
+
+ ADD_GAME_STATE (ILWRATH_INFO, 2)
+ ADD_GAME_STATE (ILWRATH_GODS_SPOKEN, 4)
+ ADD_GAME_STATE (ILWRATH_WORSHIP, 2)
+ ADD_GAME_STATE (ILWRATH_FIGHT_THRADDASH, 1)
+
+ ADD_GAME_STATE (READY_TO_CONFUSE_URQUAN, 1)
+ ADD_GAME_STATE (URQUAN_HYPNO_VISITS, 1)
+ ADD_GAME_STATE (MENTIONED_PET_COMPULSION, 1)
+ ADD_GAME_STATE (URQUAN_INFO, 2)
+ ADD_GAME_STATE (KNOW_URQUAN_STORY, 2)
+
+ ADD_GAME_STATE (MYCON_INFO, 4)
+ ADD_GAME_STATE (MYCON_RAMBLE, 5)
+ ADD_GAME_STATE (KNOW_ABOUT_SHATTERED, 2)
+ /* 0 if the player doesn't known about shattered worlds
+ * 1 if the player has encountered a shattered world
+ * 2 if the player knows that shatterred worlds are caused
+ * by Mycon deep children.
+ * 3 if the player has told the Syreen that Mycon Deep Children
+ * cause shattered worlds. Proof doesn't have to be presented
+ * yet at this time.
+ */
+ ADD_GAME_STATE (MYCON_INSULTS, 3)
+ ADD_GAME_STATE (MYCON_KNOW_AMBUSH, 1)
+ /* Set to 1 when the Mycon have been butchered at Organon,
+ * just before the remaining Mycon head back home.
+ */
+
+ ADD_GAME_STATE (SYREEN_INFO, 2)
+ ADD_GAME_STATE (KNOW_SYREEN_WORLD_SHATTERED, 1)
+ ADD_GAME_STATE (SYREEN_KNOW_ABOUT_MYCON, 1)
+
+ ADD_GAME_STATE (TALKING_PET_INFO, 3)
+ ADD_GAME_STATE (TALKING_PET_SUGGESTIONS, 3)
+ ADD_GAME_STATE (LEARNED_TALKING_PET, 1)
+ ADD_GAME_STATE (DNYARRI_LIED, 1)
+ /* Set when the Talking Pet tells you his version of their
+ * race's history with the Ur-Quan.
+ * Cleared once you confront him about this lie.
+ */
+ ADD_GAME_STATE (SHIP_TO_COMPEL, 1)
+
+ ADD_GAME_STATE (ORZ_GENERAL_INFO, 2)
+ ADD_GAME_STATE (ORZ_PERSONAL_INFO, 3)
+ ADD_GAME_STATE (ORZ_ANDRO_STATE, 2)
+ ADD_GAME_STATE (REFUSED_ORZ_ALLIANCE, 1)
+
+ ADD_GAME_STATE (PKUNK_MANNER, 2)
+ /* 0 not met the Pkunk
+ * 1 fought the Pkunk, but relations are still salvagable.
+ * 2 hostile relations with the Pkunk, no way back.
+ * 3 friendly relations with the Pkunk
+ */
+ ADD_GAME_STATE (PKUNK_ON_THE_MOVE, 1)
+ ADD_GAME_STATE (PKUNK_FLEET, 2)
+ ADD_GAME_STATE (PKUNK_MIGRATE, 2)
+ ADD_GAME_STATE (PKUNK_RETURN, 1)
+ ADD_GAME_STATE (PKUNK_WORRY, 2)
+ ADD_GAME_STATE (PKUNK_INFO, 3)
+ ADD_GAME_STATE (PKUNK_WAR, 2)
+ ADD_GAME_STATE (PKUNK_FORTUNE, 3)
+ ADD_GAME_STATE (PKUNK_MIGRATE_VISITS, 3)
+ ADD_GAME_STATE (PKUNK_REASONS, 4)
+ ADD_GAME_STATE (PKUNK_SWITCH, 1)
+ ADD_GAME_STATE (PKUNK_SENSE_VICTOR, 1)
+
+ ADD_GAME_STATE (KOHR_AH_REASONS, 2)
+ ADD_GAME_STATE (KOHR_AH_PLEAD, 2)
+ ADD_GAME_STATE (KOHR_AH_INFO, 2)
+ ADD_GAME_STATE (KNOW_KOHR_AH_STORY, 2)
+ ADD_GAME_STATE (KOHR_AH_SENSES_EVIL, 1)
+ ADD_GAME_STATE (URQUAN_SENSES_EVIL, 1)
+
+ ADD_GAME_STATE (SLYLANDRO_PROBE_VISITS, 3)
+ ADD_GAME_STATE (SLYLANDRO_PROBE_THREAT, 2)
+ ADD_GAME_STATE (SLYLANDRO_PROBE_WRONG, 2)
+ ADD_GAME_STATE (SLYLANDRO_PROBE_ID, 2)
+ ADD_GAME_STATE (SLYLANDRO_PROBE_INFO, 2)
+ ADD_GAME_STATE (SLYLANDRO_PROBE_EXIT, 2)
+
+ ADD_GAME_STATE (UMGAH_HOSTILE, 1)
+ ADD_GAME_STATE (UMGAH_EVIL_BLOBBIES, 1)
+ ADD_GAME_STATE (UMGAH_MENTIONED_TRICKS, 2)
+
+ ADD_GAME_STATE (BOMB_CARRIER, 1)
+ /* 0 when the flagship is not in battle, or it doesn't have the
+ * enhanced precursor bomb installed.
+ * 1 when the flagship is in battle and the bomb is installed.
+ * This determines whether you can flee (if the warp escape unit
+ * is installed at all), and whether taking the ship into the
+ * Sa-Matra defense structure will trigger the end of the game.
+ */
+
+ ADD_GAME_STATE (THRADD_MANNER, 1)
+ ADD_GAME_STATE (THRADD_INTRO, 2)
+ ADD_GAME_STATE (THRADD_DEMEANOR, 3)
+ ADD_GAME_STATE (THRADD_INFO, 2)
+ ADD_GAME_STATE (THRADD_BODY_LEVEL, 2)
+ ADD_GAME_STATE (THRADD_MISSION_VISITS, 1)
+ ADD_GAME_STATE (THRADD_STACK_1, 3)
+ ADD_GAME_STATE (THRADD_HOSTILE_STACK_2, 1)
+ ADD_GAME_STATE (THRADD_HOSTILE_STACK_3, 1)
+ ADD_GAME_STATE (THRADD_HOSTILE_STACK_4, 1)
+ ADD_GAME_STATE (THRADD_HOSTILE_STACK_5, 1)
+
+ ADD_GAME_STATE (CHMMR_STACK, 2)
+
+ ADD_GAME_STATE (ARILOU_MANNER, 2)
+ ADD_GAME_STATE (NO_PORTAL_VISITS, 1)
+ ADD_GAME_STATE (ARILOU_STACK_1, 2)
+ ADD_GAME_STATE (ARILOU_STACK_2, 1)
+ ADD_GAME_STATE (ARILOU_STACK_3, 2)
+ ADD_GAME_STATE (ARILOU_STACK_4, 1)
+ ADD_GAME_STATE (ARILOU_STACK_5, 2)
+ ADD_GAME_STATE (ARILOU_INFO, 2)
+ ADD_GAME_STATE (ARILOU_HINTS, 2)
+
+ ADD_GAME_STATE (DRUUGE_MANNER, 1)
+ ADD_GAME_STATE (DRUUGE_SPACE_INFO, 2)
+ ADD_GAME_STATE (DRUUGE_HOME_INFO, 2)
+ ADD_GAME_STATE (DRUUGE_SALVAGE, 1)
+ ADD_GAME_STATE (KNOW_DRUUGE_SLAVERS, 2)
+ ADD_GAME_STATE (FRAGMENTS_BOUGHT, 2)
+
+ ADD_GAME_STATE (ZEX_STACK_1, 2)
+ ADD_GAME_STATE (ZEX_STACK_2, 2)
+ ADD_GAME_STATE (ZEX_STACK_3, 2)
+
+ ADD_GAME_STATE (VUX_INFO, 2)
+ ADD_GAME_STATE (VUX_STACK_1, 4)
+ ADD_GAME_STATE (VUX_STACK_2, 2)
+ ADD_GAME_STATE (VUX_STACK_3, 2)
+ ADD_GAME_STATE (VUX_STACK_4, 2)
+
+ ADD_GAME_STATE (SHOFIXTI_STACK4, 2)
+
+ ADD_GAME_STATE (YEHAT_REBEL_INFO, 3)
+ ADD_GAME_STATE (YEHAT_ROYALIST_INFO, 1)
+ ADD_GAME_STATE (YEHAT_ROYALIST_TOLD_PKUNK, 1)
+ ADD_GAME_STATE (NO_YEHAT_ALLY_HOME, 1)
+ ADD_GAME_STATE (NO_YEHAT_HELP_HOME, 1)
+ ADD_GAME_STATE (NO_YEHAT_INFO, 1)
+ ADD_GAME_STATE (NO_YEHAT_ALLY_SPACE, 2)
+ ADD_GAME_STATE (NO_YEHAT_HELP_SPACE, 2)
+
+ ADD_GAME_STATE (ZOQFOT_KNOW_MASK, 4)
+
+ ADD_GAME_STATE (SUPOX_HOSTILE, 1)
+ ADD_GAME_STATE (SUPOX_INFO, 1)
+ ADD_GAME_STATE (SUPOX_WAR_NEWS, 2)
+ ADD_GAME_STATE (SUPOX_ULTRON_HELP, 1)
+ ADD_GAME_STATE (SUPOX_STACK1, 3)
+ ADD_GAME_STATE (SUPOX_STACK2, 2)
+
+ ADD_GAME_STATE (UTWIG_HOSTILE, 1)
+ ADD_GAME_STATE (UTWIG_INFO, 1)
+ ADD_GAME_STATE (UTWIG_WAR_NEWS, 2)
+ ADD_GAME_STATE (UTWIG_STACK1, 3)
+ ADD_GAME_STATE (UTWIG_STACK2, 2)
+ ADD_GAME_STATE (BOMB_INFO, 1)
+ ADD_GAME_STATE (BOMB_STACK1, 2)
+ ADD_GAME_STATE (BOMB_STACK2, 2)
+
+ ADD_GAME_STATE (SLYLANDRO_KNOW_BROKEN, 1)
+ ADD_GAME_STATE (PLAYER_KNOWS_PROBE, 1)
+ ADD_GAME_STATE (PLAYER_KNOWS_PROGRAM, 1)
+ ADD_GAME_STATE (PLAYER_KNOWS_EFFECTS, 1)
+ ADD_GAME_STATE (PLAYER_KNOWS_PRIORITY, 1)
+ ADD_GAME_STATE (SLYLANDRO_STACK1, 3)
+ ADD_GAME_STATE (SLYLANDRO_STACK2, 1)
+ ADD_GAME_STATE (SLYLANDRO_STACK3, 2)
+ ADD_GAME_STATE (SLYLANDRO_STACK4, 2)
+ ADD_GAME_STATE (SLYLANDRO_STACK5, 1)
+ ADD_GAME_STATE (SLYLANDRO_STACK6, 1)
+ ADD_GAME_STATE (SLYLANDRO_STACK7, 2)
+ ADD_GAME_STATE (SLYLANDRO_STACK8, 2)
+ ADD_GAME_STATE (SLYLANDRO_STACK9, 2)
+ ADD_GAME_STATE (SLYLANDRO_KNOW_EARTH, 1)
+ ADD_GAME_STATE (SLYLANDRO_KNOW_EXPLORE, 1)
+ ADD_GAME_STATE (SLYLANDRO_KNOW_GATHER, 1)
+ ADD_GAME_STATE (SLYLANDRO_KNOW_URQUAN, 2)
+ ADD_GAME_STATE (RECALL_VISITS, 2)
+
+ ADD_GAME_STATE (SLYLANDRO_MULTIPLIER, 3)
+ ADD_GAME_STATE (KNOW_SPATHI_QUEST, 1)
+ ADD_GAME_STATE (KNOW_SPATHI_EVIL, 1)
+
+ ADD_GAME_STATE (BATTLE_PLANET, 8)
+ ADD_GAME_STATE (ESCAPE_COUNTER, 8)
+
+ ADD_GAME_STATE (CREW_SOLD_TO_DRUUGE1, 8)
+ ADD_GAME_STATE (PKUNK_DONE_WAR, 1)
+
+ ADD_GAME_STATE (SYREEN_STACK0, 2)
+ ADD_GAME_STATE (SYREEN_STACK1, 2)
+ ADD_GAME_STATE (SYREEN_STACK2, 2)
+
+ ADD_GAME_STATE (REFUSED_ULTRON_AT_BOMB, 1)
+ ADD_GAME_STATE (NO_TRICK_AT_SUN, 1)
+
+ ADD_GAME_STATE (SPATHI_STACK0, 2)
+ ADD_GAME_STATE (SPATHI_STACK1, 1)
+ ADD_GAME_STATE (SPATHI_STACK2, 1)
+
+ ADD_GAME_STATE (ORZ_STACK0, 1)
+ ADD_GAME_STATE (ORZ_STACK1, 1)
+
+/* These state bits are actually offsets into defgrp.dat. They really
+ * shouldn't be part of the serialized Game State array! --MCM */
+ ADD_GAME_STATE (SHOFIXTI_GRPOFFS0, 8)
+ ADD_GAME_STATE (SHOFIXTI_GRPOFFS1, 8)
+ ADD_GAME_STATE (SHOFIXTI_GRPOFFS2, 8)
+ ADD_GAME_STATE (SHOFIXTI_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (ZOQFOT_GRPOFFS0, 8)
+ ADD_GAME_STATE (ZOQFOT_GRPOFFS1, 8)
+ ADD_GAME_STATE (ZOQFOT_GRPOFFS2, 8)
+ ADD_GAME_STATE (ZOQFOT_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME0_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME0_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME0_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME0_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME1_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME1_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME1_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME1_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME2_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME2_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME2_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME2_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME3_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME3_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME3_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME3_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME4_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME4_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME4_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME4_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME5_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME5_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME5_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME5_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME6_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME6_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME6_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME6_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME7_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME7_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME7_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME7_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (MELNORME8_GRPOFFS0, 8)
+ ADD_GAME_STATE (MELNORME8_GRPOFFS1, 8)
+ ADD_GAME_STATE (MELNORME8_GRPOFFS2, 8)
+ ADD_GAME_STATE (MELNORME8_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS0, 8)
+ ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS1, 8)
+ ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS2, 8)
+ ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (COLONY_GRPOFFS0, 8)
+ ADD_GAME_STATE (COLONY_GRPOFFS1, 8)
+ ADD_GAME_STATE (COLONY_GRPOFFS2, 8)
+ ADD_GAME_STATE (COLONY_GRPOFFS3, 8)
+
+ ADD_GAME_STATE (SAMATRA_GRPOFFS0, 8)
+ ADD_GAME_STATE (SAMATRA_GRPOFFS1, 8)
+ ADD_GAME_STATE (SAMATRA_GRPOFFS2, 8)
+ ADD_GAME_STATE (SAMATRA_GRPOFFS3, 8)
+
+END_GAME_STATE
+
+// Values for GAME_STATE.glob_flags:
+#define COMBAT_SPEED_SHIFT 6
+#define COMBAT_SPEED_MASK (((1 << 2) - 1) << COMBAT_SPEED_SHIFT)
+#define NUM_COMBAT_SPEEDS 4
+#define MUSIC_DISABLED (1 << 3)
+#define SOUND_DISABLED (1 << 4)
+#define CYBORG_ENABLED (1 << 5)
+
+enum
+{
+ SUPER_MELEE = 0, /* Is also used while in the main menu */
+ IN_LAST_BATTLE,
+ IN_ENCOUNTER,
+ IN_HYPERSPACE /* in HyperSpace or QuasiSpace */,
+ IN_INTERPLANETARY,
+ WON_LAST_BATTLE,
+
+ /* The following three are only used when displaying save game
+ * summaries */
+ IN_QUASISPACE,
+ IN_PLANET_ORBIT,
+ IN_STARBASE,
+
+ CHECK_PAUSE = MAKE_WORD (0, (1 << 0)),
+ IN_BATTLE = MAKE_WORD (0, (1 << 1)),
+ /* Is also set while in HyperSpace/QuasiSpace */
+ START_ENCOUNTER = MAKE_WORD (0, (1 << 2)),
+ START_INTERPLANETARY = MAKE_WORD (0, (1 << 3)),
+ CHECK_LOAD = MAKE_WORD (0, (1 << 4)),
+ CHECK_RESTART = MAKE_WORD (0, (1 << 5)),
+ CHECK_ABORT = MAKE_WORD (0, (1 << 6)),
+};
+typedef UWORD ACTIVITY;
+
+typedef struct
+{
+ BYTE glob_flags;
+ // See above for the meaning of the bits.
+
+ BYTE CrewCost, FuelCost;
+ BYTE ModuleCost[NUM_MODULES];
+ BYTE ElementWorth[NUM_ELEMENT_CATEGORIES];
+
+ PRIMITIVE *DisplayArray;
+ ACTIVITY CurrentActivity;
+
+ CLOCK_STATE GameClock;
+
+ POINT autopilot;
+ POINT ip_location;
+ STAMP ShipStamp;
+ UWORD ShipFacing;
+ BYTE ip_planet;
+ BYTE in_orbit;
+ VELOCITY_DESC velocity;
+
+ DWORD BattleGroupRef;
+ QUEUE avail_race_q;
+ /* List of all the races in the game with information
+ * about their ships, and what player knows about their
+ * fleet, center of SoI, status, etc.
+ * queue element is FLEET_INFO */
+ QUEUE npc_built_ship_q;
+ /* Non-player-character list of ships (during encounter)
+ * queue element is SHIP_FRAGMENT */
+ QUEUE ip_group_q;
+ /* List of groups present in solarsys (during IP);
+ * queue element is IP_GROUP */
+ QUEUE encounter_q;
+ /* List of HyperSpace encounters (black globes);
+ * queue element is ENCOUNTER */
+ QUEUE built_ship_q;
+ /* List of SIS escort ships;
+ * queue element is SHIP_FRAGMENT */
+
+ BYTE GameState[(NUM_GAME_STATE_BITS + 7) >> 3];
+} GAME_STATE;
+
+typedef struct
+{
+ SIS_STATE SIS_state;
+ GAME_STATE Game_state;
+} GLOBDATA;
+
+extern GLOBDATA GlobData;
+#define GLOBAL(f) GlobData.Game_state.f
+#define GLOBAL_SIS(f) GlobData.SIS_state.f
+
+#define MAX_ENCOUNTERS 16
+#define MAX_BATTLE_GROUPS 32
+
+/* DEFGRP enumeration. These identify scripted TrueSpace encounters
+ * more consistently than offsets into the DEFGRPINFO_FILE state
+ * file. */
+enum {
+ DEFGRP_NONE,
+ DEFGRP_SHOFIXTI,
+ DEFGRP_ZOQFOT,
+ DEFGRP_MELNORME0,
+ DEFGRP_MELNORME1,
+ DEFGRP_MELNORME2,
+ DEFGRP_MELNORME3,
+ DEFGRP_MELNORME4,
+ DEFGRP_MELNORME5,
+ DEFGRP_MELNORME6,
+ DEFGRP_MELNORME7,
+ DEFGRP_MELNORME8,
+ DEFGRP_URQUAN_PROBE,
+ DEFGRP_COLONY,
+ DEFGRP_SAMATRA,
+ NUM_DEFGRPS
+};
+
+//#define STATE_DEBUG
+
+extern BYTE getGameState (BYTE *state, int startBit, int endBit);
+extern void setGameState (BYTE *state, int startBit, int endBit, BYTE val
+#ifdef STATE_DEBUG
+ , const char *name
+#endif
+ );
+extern void copyGameState (BYTE *dest, DWORD target, BYTE *src, DWORD begin, DWORD end);
+
+#define GET_GAME_STATE(SName) getGameState (GLOBAL(GameState), (SName), (END_##SName))
+#ifdef STATE_DEBUG
+# define SET_GAME_STATE(SName, val) \
+ setGameState (GLOBAL(GameState), (SName), (END_##SName), (val), #SName)
+#else
+# define SET_GAME_STATE(SName, val) \
+ setGameState (GLOBAL(GameState), (SName), (END_##SName), (val))
+#endif
+
+extern DWORD getGameState32 (BYTE *state, int startBit);
+extern void setGameState32 (BYTE *state, int startBit, DWORD val
+#ifdef STATE_DEBUG
+ , const char *name
+#endif
+ );
+
+#define GET_GAME_STATE_32(SName) getGameState32 (GLOBAL(GameState), (SName))
+#ifdef STATE_DEBUG
+# define SET_GAME_STATE_32(SName, val) \
+ setGameState32 (GLOBAL(GameState), (SName), (val), #SName)
+#else
+# define SET_GAME_STATE_32(SName, val) \
+ setGameState32 (GLOBAL(GameState), (SName), (val))
+#endif
+
+
+extern CONTEXT RadarContext;
+
+extern void FreeSC2Data (void);
+extern BOOLEAN LoadSC2Data (void);
+
+extern void InitGlobData (void);
+
+BOOLEAN inFullGame (void);
+BOOLEAN inSuperMelee (void);
+//BOOLEAN inBattle (void);
+//BOOLEAN inInterPlanetary (void);
+//BOOLEAN inSolarSystem (void);
+//BOOLEAN inOrbit (void);
+BOOLEAN inHQSpace (void);
+BOOLEAN inHyperSpace (void);
+BOOLEAN inQuasiSpace (void);
+
+extern BOOLEAN InitGameStructures (void);
+extern void UninitGameStructures (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_GLOBDATA_H_ */
diff --git a/src/uqm/gravity.c b/src/uqm/gravity.c
new file mode 100644
index 0000000..5a7899c
--- /dev/null
+++ b/src/uqm/gravity.c
@@ -0,0 +1,200 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "collide.h"
+#include "races.h"
+#include "units.h"
+#include "libs/log.h"
+
+//#define DEBUG_GRAVITY
+
+BOOLEAN
+CalculateGravity (ELEMENT *ElementPtr)
+{
+ BOOLEAN retval, HasGravity;
+ HELEMENT hTestElement, hSuccElement;
+
+ retval = FALSE;
+ HasGravity = (BOOLEAN)(CollidingElement (ElementPtr)
+ && GRAVITY_MASS (ElementPtr->mass_points + 1));
+ for (hTestElement = GetHeadElement ();
+ hTestElement != 0; hTestElement = hSuccElement)
+ {
+ BOOLEAN TestHasGravity;
+ ELEMENT *TestElementPtr;
+
+ LockElement (hTestElement, &TestElementPtr);
+ if (TestElementPtr != ElementPtr
+ && CollidingElement (TestElementPtr)
+ && (TestHasGravity =
+ GRAVITY_MASS (TestElementPtr->mass_points + 1)) != HasGravity)
+ {
+ COUNT abs_dx, abs_dy;
+ SIZE dx, dy;
+
+ if (!(ElementPtr->state_flags & PRE_PROCESS))
+ {
+ dx = ElementPtr->current.location.x
+ - TestElementPtr->current.location.x;
+ dy = ElementPtr->current.location.y
+ - TestElementPtr->current.location.y;
+ }
+ else
+ {
+ dx = ElementPtr->next.location.x
+ - TestElementPtr->next.location.x;
+ dy = ElementPtr->next.location.y
+ - TestElementPtr->next.location.y;
+ }
+#ifdef DEBUG_GRAVITY
+ if (TestElementPtr->state_flags & PLAYER_SHIP)
+ {
+ log_add (log_Debug, "CalculateGravity:");
+ log_add (log_Debug, "\tdx = %d, dy = %d", dx, dy);
+ }
+#endif /* DEBUG_GRAVITY */
+ dx = WRAP_DELTA_X (dx);
+ dy = WRAP_DELTA_Y (dy);
+#ifdef DEBUG_GRAVITY
+ if (TestElementPtr->state_flags & PLAYER_SHIP)
+ log_add (log_Debug, "\twrap_dx = %d, wrap_dy = %d", dx, dy);
+#endif /* DEBUG_GRAVITY */
+ abs_dx = dx >= 0 ? dx : -dx;
+ abs_dy = dy >= 0 ? dy : -dy;
+ abs_dx = WORLD_TO_DISPLAY (abs_dx);
+ abs_dy = WORLD_TO_DISPLAY (abs_dy);
+#ifdef DEBUG_GRAVITY
+ if (TestElementPtr->state_flags & PLAYER_SHIP)
+ log_add (log_Debug, "\tdisplay_dx = %d, display_dy = %d",
+ abs_dx, abs_dy);
+#endif /* DEBUG_GRAVITY */
+ if (abs_dx <= GRAVITY_THRESHOLD
+ && abs_dy <= GRAVITY_THRESHOLD)
+ {
+ DWORD dist_squared;
+
+ dist_squared = (DWORD)(abs_dx * abs_dx)
+ + (DWORD)(abs_dy * abs_dy);
+ if (dist_squared <= (DWORD)(GRAVITY_THRESHOLD
+ * GRAVITY_THRESHOLD))
+ {
+#ifdef NEVER
+ COUNT magnitude;
+
+#define DIFUSE_GRAVITY 175
+ dist_squared += (DWORD)abs_dx * (DIFUSE_GRAVITY << 1)
+ + (DWORD)abs_dy * (DIFUSE_GRAVITY << 1)
+ + ((DWORD)(DIFUSE_GRAVITY * DIFUSE_GRAVITY) << 1);
+ if ((magnitude = (COUNT)((DWORD)(GRAVITY_THRESHOLD
+ * GRAVITY_THRESHOLD) / dist_squared)) == 0)
+ magnitude = 1;
+
+#define MAX_MAGNITUDE 6
+ else if (magnitude > MAX_MAGNITUDE)
+ magnitude = MAX_MAGNITUDE;
+ log_add (log_Debug, "magnitude = %u", magnitude);
+#endif /* NEVER */
+
+#ifdef DEBUG_GRAVITY
+ if (TestElementPtr->state_flags & PLAYER_SHIP)
+ log_add (log_Debug, "dist_squared = %lu", dist_squared);
+#endif /* DEBUG_GRAVITY */
+ if (TestHasGravity)
+ {
+ retval = TRUE;
+ UnlockElement (hTestElement);
+ break;
+ }
+ else
+ {
+ COUNT angle;
+
+ angle = ARCTAN (dx, dy);
+ DeltaVelocityComponents (&TestElementPtr->velocity,
+ COSINE (angle, WORLD_TO_VELOCITY (1)),
+ SINE (angle, WORLD_TO_VELOCITY (1)));
+ if (TestElementPtr->state_flags & PLAYER_SHIP)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (TestElementPtr, &StarShipPtr);
+ StarShipPtr->cur_status_flags &= ~SHIP_AT_MAX_SPEED;
+ StarShipPtr->cur_status_flags |= SHIP_IN_GRAVITY_WELL;
+ }
+ }
+ }
+ }
+ }
+
+ hSuccElement = GetSuccElement (TestElementPtr);
+ UnlockElement (hTestElement);
+ }
+
+ return (retval);
+}
+
+BOOLEAN
+TimeSpaceMatterConflict (ELEMENT *ElementPtr)
+{
+ HELEMENT hTestElement, hSuccElement;
+ INTERSECT_CONTROL ElementControl;
+
+ ElementControl.IntersectStamp.origin.x =
+ WORLD_TO_DISPLAY (ElementPtr->current.location.x);
+ ElementControl.IntersectStamp.origin.y =
+ WORLD_TO_DISPLAY (ElementPtr->current.location.y);
+ ElementControl.IntersectStamp.frame =
+ SetEquFrameIndex (ElementPtr->current.image.farray[0],
+ ElementPtr->current.image.frame);
+ ElementControl.EndPoint = ElementControl.IntersectStamp.origin;
+ for (hTestElement = GetHeadElement ();
+ hTestElement != 0; hTestElement = hSuccElement)
+ {
+ ELEMENT *TestElementPtr;
+
+ LockElement (hTestElement, &TestElementPtr);
+ hSuccElement = GetSuccElement (TestElementPtr);
+ if (TestElementPtr != ElementPtr
+ && (CollidingElement (TestElementPtr)
+ /* ship in transition */
+ || (TestElementPtr->state_flags & PLAYER_SHIP)))
+ {
+ INTERSECT_CONTROL TestElementControl;
+
+ TestElementControl.IntersectStamp.origin.x =
+ WORLD_TO_DISPLAY (TestElementPtr->current.location.x);
+ TestElementControl.IntersectStamp.origin.y =
+ WORLD_TO_DISPLAY (TestElementPtr->current.location.y);
+ TestElementControl.IntersectStamp.frame =
+ SetEquFrameIndex (TestElementPtr->current.image.farray[0],
+ TestElementPtr->current.image.frame);
+ TestElementControl.EndPoint = TestElementControl.IntersectStamp.origin;
+ if (DrawablesIntersect (&ElementControl,
+ &TestElementControl, MAX_TIME_VALUE))
+ {
+ UnlockElement (hTestElement);
+
+ break;
+ }
+ }
+ UnlockElement (hTestElement);
+ }
+
+ return (hTestElement != 0 ? TRUE : FALSE);
+}
+
diff --git a/src/uqm/grpinfo.c b/src/uqm/grpinfo.c
new file mode 100644
index 0000000..43f1b22
--- /dev/null
+++ b/src/uqm/grpinfo.c
@@ -0,0 +1,865 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "build.h"
+#include "starmap.h"
+#include "gendef.h"
+#include "libs/file.h"
+#include "globdata.h"
+#include "intel.h"
+#include "state.h"
+#include "grpintrn.h"
+
+#include "libs/mathlib.h"
+#include "libs/log.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+static BYTE LastEncGroup;
+ // Last encountered group, saved into state files
+
+void
+ReadGroupHeader (GAME_STATE_FILE *fp, GROUP_HEADER *pGH)
+{
+ sread_8 (fp, &pGH->NumGroups);
+ sread_8 (fp, &pGH->day_index);
+ sread_8 (fp, &pGH->month_index);
+ sread_8 (fp, NULL); /* padding */
+ sread_16 (fp, &pGH->star_index);
+ sread_16 (fp, &pGH->year_index);
+ sread_a32 (fp, pGH->GroupOffset, NUM_SAVED_BATTLE_GROUPS + 1);
+}
+
+void
+WriteGroupHeader (GAME_STATE_FILE *fp, const GROUP_HEADER *pGH)
+{
+ swrite_8 (fp, pGH->NumGroups);
+ swrite_8 (fp, pGH->day_index);
+ swrite_8 (fp, pGH->month_index);
+ swrite_8 (fp, 0); /* padding */
+ swrite_16 (fp, pGH->star_index);
+ swrite_16 (fp, pGH->year_index);
+ swrite_a32 (fp, pGH->GroupOffset, NUM_SAVED_BATTLE_GROUPS + 1);
+}
+
+void
+ReadShipFragment (GAME_STATE_FILE *fp, SHIP_FRAGMENT *FragPtr)
+{
+ BYTE tmpb;
+
+ sread_16 (fp, NULL); /* unused: was which_side */
+ sread_8 (fp, &FragPtr->captains_name_index);
+ sread_8 (fp, NULL); /* padding; for savegame compat */
+ sread_16 (fp, NULL); /* unused: was ship_flags */
+ sread_8 (fp, &FragPtr->race_id);
+ sread_8 (fp, &FragPtr->index);
+ // XXX: reading crew as BYTE to maintain savegame compatibility
+ sread_8 (fp, &tmpb);
+ FragPtr->crew_level = tmpb;
+ sread_8 (fp, &tmpb);
+ FragPtr->max_crew = tmpb;
+ sread_8 (fp, &FragPtr->energy_level);
+ sread_8 (fp, &FragPtr->max_energy);
+ sread_16 (fp, NULL); /* unused; was loc.x */
+ sread_16 (fp, NULL); /* unused; was loc.y */
+}
+
+void
+WriteShipFragment (GAME_STATE_FILE *fp, const SHIP_FRAGMENT *FragPtr)
+{
+ swrite_16 (fp, 0); /* unused: was which_side */
+ swrite_8 (fp, FragPtr->captains_name_index);
+ swrite_8 (fp, 0); /* padding; for savegame compat */
+ swrite_16 (fp, 0); /* unused: was ship_flags */
+ swrite_8 (fp, FragPtr->race_id);
+ swrite_8 (fp, FragPtr->index);
+ // XXX: writing crew as BYTE to maintain savegame compatibility
+ swrite_8 (fp, FragPtr->crew_level);
+ swrite_8 (fp, FragPtr->max_crew);
+ swrite_8 (fp, FragPtr->energy_level);
+ swrite_8 (fp, FragPtr->max_energy);
+ swrite_16 (fp, 0); /* unused; was loc.x */
+ swrite_16 (fp, 0); /* unused; was loc.y */
+}
+
+void
+ReadIpGroup (GAME_STATE_FILE *fp, IP_GROUP *GroupPtr)
+{
+ BYTE tmpb;
+
+ sread_16 (fp, NULL); /* unused; was which_side */
+ sread_8 (fp, NULL); /* unused; was captains_name_index */
+ sread_8 (fp, NULL); /* padding; for savegame compat */
+ sread_16 (fp, &GroupPtr->group_counter);
+ sread_8 (fp, &GroupPtr->race_id);
+ sread_8 (fp, &tmpb); /* was var2 */
+ GroupPtr->sys_loc = LONIBBLE (tmpb);
+ GroupPtr->task = HINIBBLE (tmpb);
+ sread_8 (fp, &GroupPtr->in_system); /* was crew_level */
+ sread_8 (fp, NULL); /* unused; was max_crew */
+ sread_8 (fp, &tmpb); /* was energy_level */
+ GroupPtr->dest_loc = LONIBBLE (tmpb);
+ GroupPtr->orbit_pos = HINIBBLE (tmpb);
+ sread_8 (fp, &GroupPtr->group_id); /* was max_energy */
+ sread_16s(fp, &GroupPtr->loc.x);
+ sread_16s(fp, &GroupPtr->loc.y);
+}
+
+void
+WriteIpGroup (GAME_STATE_FILE *fp, const IP_GROUP *GroupPtr)
+{
+ swrite_16 (fp, 0); /* unused; was which_side */
+ swrite_8 (fp, 0); /* unused; was captains_name_index */
+ swrite_8 (fp, 0); /* padding; for savegame compat */
+ swrite_16 (fp, GroupPtr->group_counter);
+ swrite_8 (fp, GroupPtr->race_id);
+ assert (GroupPtr->sys_loc < 0x10 && GroupPtr->task < 0x10);
+ swrite_8 (fp, MAKE_BYTE (GroupPtr->sys_loc, GroupPtr->task));
+ /* was var2 */
+ swrite_8 (fp, GroupPtr->in_system); /* was crew_level */
+ swrite_8 (fp, 0); /* unused; was max_crew */
+ assert (GroupPtr->dest_loc < 0x10 && GroupPtr->orbit_pos < 0x10);
+ swrite_8 (fp, MAKE_BYTE (GroupPtr->dest_loc, GroupPtr->orbit_pos));
+ /* was energy_level */
+ swrite_8 (fp, GroupPtr->group_id); /* was max_energy */
+ swrite_16 (fp, GroupPtr->loc.x);
+ swrite_16 (fp, GroupPtr->loc.y);
+}
+
+void
+InitGroupInfo (BOOLEAN FirstTime)
+{
+ GAME_STATE_FILE *fp;
+
+ assert (NUM_SAVED_BATTLE_GROUPS >= MAX_BATTLE_GROUPS);
+
+ fp = OpenStateFile (RANDGRPINFO_FILE, "wb");
+ if (fp)
+ {
+ GROUP_HEADER GH;
+
+ memset (&GH, 0, sizeof (GH));
+ GH.star_index = (COUNT)~0;
+ WriteGroupHeader (fp, &GH);
+ CloseStateFile (fp);
+ }
+
+ if (FirstTime && (fp = OpenStateFile (DEFGRPINFO_FILE, "wb")))
+ {
+ // Group headers cannot start with offset 0 in 'defined' group
+ // info file, so bump it (because offset 0 is reserved to
+ // indicate the 'random' group info file).
+ swrite_8 (fp, 0);
+ CloseStateFile (fp);
+ }
+}
+
+void
+UninitGroupInfo (void)
+{
+ DeleteStateFile (DEFGRPINFO_FILE);
+ DeleteStateFile (RANDGRPINFO_FILE);
+}
+
+HIPGROUP
+BuildGroup (QUEUE *pDstQueue, BYTE race_id)
+{
+ HFLEETINFO hFleet;
+ FLEET_INFO *TemplatePtr;
+ HLINK hGroup;
+ IP_GROUP *GroupPtr;
+
+ assert (GetLinkSize (pDstQueue) == sizeof (IP_GROUP));
+
+ hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race_id);
+ if (!hFleet)
+ return 0;
+
+ hGroup = AllocLink (pDstQueue);
+ if (!hGroup)
+ return 0;
+
+ TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ GroupPtr = LockIpGroup (pDstQueue, hGroup);
+ memset (GroupPtr, 0, GetLinkSize (pDstQueue));
+ GroupPtr->race_id = race_id;
+ GroupPtr->melee_icon = TemplatePtr->melee_icon;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ UnlockIpGroup (pDstQueue, hGroup);
+ PutQueue (pDstQueue, hGroup);
+
+ return hGroup;
+}
+
+void
+BuildGroups (void)
+{
+ BYTE Index;
+ BYTE BestIndex = 0;
+ COUNT BestPercent = 0;
+ POINT universe;
+ HFLEETINFO hFleet, hNextFleet;
+ BYTE HomeWorld[] =
+ {
+ 0, /* ARILOU_SHIP */
+ 0, /* CHMMR_SHIP */
+ 0, /* HUMAN_SHIP */
+ ORZ_DEFINED, /* ORZ_SHIP */
+ PKUNK_DEFINED, /* PKUNK_SHIP */
+ 0, /* SHOFIXTI_SHIP */
+ SPATHI_DEFINED, /* SPATHI_SHIP */
+ SUPOX_DEFINED, /* SUPOX_SHIP */
+ THRADD_DEFINED, /* THRADDASH_SHIP */
+ UTWIG_DEFINED, /* UTWIG_SHIP */
+ VUX_DEFINED, /* VUX_SHIP */
+ YEHAT_DEFINED, /* YEHAT_SHIP */
+ 0, /* MELNORME_SHIP */
+ DRUUGE_DEFINED, /* DRUUGE_SHIP */
+ ILWRATH_DEFINED, /* ILWRATH_SHIP */
+ MYCON_DEFINED, /* MYCON_SHIP */
+ 0, /* SLYLANDRO_SHIP */
+ UMGAH_DEFINED, /* UMGAH_SHIP */
+ 0, /* URQUAN_SHIP */
+ ZOQFOT_DEFINED, /* ZOQFOTPIK_SHIP */
+
+ 0, /* SYREEN_SHIP */
+ 0, /* BLACK_URQUAN_SHIP */
+ 0, /* YEHAT_REBEL_SHIP */
+ };
+ BYTE EncounterPercent[] =
+ {
+ RACE_INTERPLANETARY_PERCENT
+ };
+
+ EncounterPercent[SLYLANDRO_SHIP] *= GET_GAME_STATE (SLYLANDRO_MULTIPLIER);
+ Index = GET_GAME_STATE (UTWIG_SUPOX_MISSION);
+ if (Index > 1 && Index < 5)
+ {
+ // When the Utwig and Supox are on their mission, there won't be
+ // new battle groups generated for the system.
+ // Note that old groups may still exist (in which case this function
+ // would not even be called), but those expire after spending a week
+ // outside of the star system, or when a different star system is
+ // entered.
+ HomeWorld[UTWIG_SHIP] = 0;
+ HomeWorld[SUPOX_SHIP] = 0;
+ }
+
+ universe = CurStarDescPtr->star_pt;
+ for (hFleet = GetHeadLink (&GLOBAL (avail_race_q)), Index = 0;
+ hFleet; hFleet = hNextFleet, ++Index)
+ {
+ COUNT i, encounter_radius;
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ hNextFleet = _GetSuccLink (FleetPtr);
+
+ if ((encounter_radius = FleetPtr->actual_strength)
+ && (i = EncounterPercent[Index]))
+ {
+ SIZE dx, dy;
+ DWORD d_squared;
+ BYTE race_enc;
+
+ race_enc = HomeWorld[Index];
+ if (race_enc && CurStarDescPtr->Index == race_enc)
+ { // In general, there are always ships at the Homeworld for
+ // the races specified in HomeWorld[] array.
+ BestIndex = Index;
+ BestPercent = 70;
+ if (race_enc == SPATHI_DEFINED || race_enc == SUPOX_DEFINED)
+ BestPercent = 2;
+ // Terminate the loop!
+ hNextFleet = 0;
+
+ goto FoundHome;
+ }
+
+ if (encounter_radius == INFINITE_RADIUS)
+ encounter_radius = (MAX_X_UNIVERSE + 1) << 1;
+ else
+ encounter_radius =
+ (encounter_radius * SPHERE_RADIUS_INCREMENT) >> 1;
+ dx = universe.x - FleetPtr->loc.x;
+ if (dx < 0)
+ dx = -dx;
+ dy = universe.y - FleetPtr->loc.y;
+ if (dy < 0)
+ dy = -dy;
+ if ((COUNT)dx < encounter_radius
+ && (COUNT)dy < encounter_radius
+ && (d_squared = (DWORD)dx * dx + (DWORD)dy * dy) <
+ (DWORD)encounter_radius * encounter_radius)
+ {
+ DWORD rand_val;
+
+ // EncounterPercent is only used in practice for the Slylandro
+ // Probes, for the rest of races the chance of encounter is
+ // calced directly below from the distance to the Homeworld
+ if (FleetPtr->actual_strength != INFINITE_RADIUS)
+ {
+ i = 70 - (COUNT)((DWORD)square_root (d_squared)
+ * 60L / encounter_radius);
+ }
+
+ rand_val = TFB_Random ();
+ if ((int)(LOWORD (rand_val) % 100) < (int)i
+ && (BestPercent == 0
+ || (HIWORD (rand_val) % (i + BestPercent)) < i))
+ {
+ if (FleetPtr->actual_strength == INFINITE_RADIUS)
+ { // The prevailing encounter chance is hereby limitted
+ // to 4% for races with infinite SoI (currently, it
+ // is only the Slylandro Probes)
+ i = 4;
+ }
+
+ BestPercent = i;
+ BestIndex = Index;
+ }
+ }
+ }
+
+FoundHome:
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
+ }
+
+ if (BestPercent)
+ {
+ BYTE which_group, num_groups;
+ BYTE EncounterMakeup[] =
+ {
+ RACE_ENCOUNTER_MAKEUP
+ };
+
+ which_group = 0;
+ num_groups = ((COUNT)TFB_Random () % (BestPercent >> 1)) + 1;
+ if (num_groups > MAX_BATTLE_GROUPS)
+ num_groups = MAX_BATTLE_GROUPS;
+ else if (num_groups < 5
+ && (Index = HomeWorld[BestIndex])
+ && CurStarDescPtr->Index == Index)
+ num_groups = 5;
+ do
+ {
+ for (Index = HINIBBLE (EncounterMakeup[BestIndex]); Index;
+ --Index)
+ {
+ if (Index <= LONIBBLE (EncounterMakeup[BestIndex])
+ || (COUNT)TFB_Random () % 100 < 50)
+ CloneShipFragment (BestIndex,
+ &GLOBAL (npc_built_ship_q), 0);
+ }
+
+ PutGroupInfo (GROUPS_RANDOM, ++which_group);
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ } while (--num_groups);
+ }
+
+ GetGroupInfo (GROUPS_RANDOM, GROUP_INIT_IP);
+}
+
+static void
+FlushGroupInfo (GROUP_HEADER* pGH, DWORD offset, BYTE which_group, GAME_STATE_FILE *fp)
+{
+ if (which_group == GROUP_LIST)
+ {
+ HIPGROUP hGroup, hNextGroup;
+
+ /* If the group list was never written before, add it */
+ if (pGH->GroupOffset[0] == 0)
+ pGH->GroupOffset[0] = LengthStateFile (fp);
+
+ // XXX: npc_built_ship_q must be empty because the wipe-out
+ // procedure is actually the writing of the npc_built_ship_q
+ // out as the group in question
+ assert (!GetHeadLink (&GLOBAL (npc_built_ship_q)));
+
+ /* Weed out the groups that left the system first */
+ for (hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ hGroup; hGroup = hNextGroup)
+ {
+ BYTE in_system;
+ BYTE group_id;
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+ in_system = GroupPtr->in_system;
+ group_id = GroupPtr->group_id;
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+
+ if (!in_system)
+ {
+ // The following 'if' is needed because GROUP_LIST is only
+ // ever flushed to RANDGRPINFO_FILE, but the current group
+ // may need to be updated in the DEFGRPINFO_FILE as well.
+ // In that case, PutGroupInfo() will update the correct file.
+ if (GLOBAL (BattleGroupRef))
+ PutGroupInfo (GLOBAL (BattleGroupRef), group_id);
+ else
+ FlushGroupInfo (pGH, GROUPS_RANDOM, group_id, fp);
+ // This will also wipe the group out in the RANDGRPINFO_FILE
+ pGH->GroupOffset[group_id] = 0;
+ RemoveQueue (&GLOBAL (ip_group_q), hGroup);
+ FreeIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+ }
+ }
+ else if (which_group > pGH->NumGroups)
+ { /* Group not present yet -- add it */
+ pGH->NumGroups = which_group;
+ pGH->GroupOffset[which_group] = LengthStateFile (fp);
+ }
+
+ SeekStateFile (fp, offset, SEEK_SET);
+ WriteGroupHeader (fp, pGH);
+
+#ifdef DEBUG_GROUPS
+ log_add (log_Debug, "1)FlushGroupInfo(%lu): WG = %u(%lu), NG = %u, "
+ "SI = %u", offset, which_group, pGH->GroupOffset[which_group],
+ pGH->NumGroups, pGH->star_index);
+#endif /* DEBUG_GROUPS */
+
+ if (which_group == GROUP_LIST)
+ {
+ /* Write out ip_group_q as group 0 */
+ HIPGROUP hGroup, hNextGroup;
+ BYTE NumGroups = CountLinks (&GLOBAL (ip_group_q));
+
+ SeekStateFile (fp, pGH->GroupOffset[0], SEEK_SET);
+ swrite_8 (fp, LastEncGroup);
+ swrite_8 (fp, NumGroups);
+
+ hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ for ( ; NumGroups; --NumGroups, hGroup = hNextGroup)
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+
+ swrite_8 (fp, GroupPtr->race_id);
+
+#ifdef DEBUG_GROUPS
+ log_add (log_Debug, "F) type %u, loc %u<%d, %d>, task 0x%02x:%u",
+ GroupPtr->race_id,
+ GET_GROUP_LOC (GroupPtr),
+ GroupPtr->loc.x,
+ GroupPtr->loc.y,
+ GET_GROUP_MISSION (GroupPtr),
+ GET_GROUP_DEST (GroupPtr));
+#endif /* DEBUG_GROUPS */
+
+ WriteIpGroup (fp, GroupPtr);
+
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+ }
+ else
+ {
+ /* Write out npc_built_ship_q as 'which_group' group */
+ HSHIPFRAG hStarShip, hNextShip;
+ BYTE NumShips = CountLinks (&GLOBAL (npc_built_ship_q));
+ BYTE RaceType = 0;
+
+ hStarShip = GetHeadLink (&GLOBAL (npc_built_ship_q));
+ if (NumShips > 0)
+ {
+ SHIP_FRAGMENT *FragPtr;
+
+ /* The first ship in a group defines the alien race */
+ FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ RaceType = FragPtr->race_id;
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ }
+
+ SeekStateFile (fp, pGH->GroupOffset[which_group], SEEK_SET);
+ swrite_8 (fp, RaceType);
+ swrite_8 (fp, NumShips);
+
+ for ( ; NumShips; --NumShips, hStarShip = hNextShip)
+ {
+ SHIP_FRAGMENT *FragPtr;
+
+ FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (FragPtr);
+
+ swrite_8 (fp, FragPtr->race_id);
+ WriteShipFragment (fp, FragPtr);
+
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ }
+ }
+}
+
+BOOLEAN
+GetGroupInfo (DWORD offset, BYTE which_group)
+{
+ GAME_STATE_FILE *fp;
+ GROUP_HEADER GH;
+
+ if (offset != GROUPS_RANDOM && which_group != GROUP_LIST)
+ fp = OpenStateFile (DEFGRPINFO_FILE, "r+b");
+ else
+ fp = OpenStateFile (RANDGRPINFO_FILE, "r+b");
+
+ if (!fp)
+ return FALSE;
+
+ SeekStateFile (fp, offset, SEEK_SET);
+ ReadGroupHeader (fp, &GH);
+#ifdef DEBUG_GROUPS
+ log_add (log_Debug, "GetGroupInfo(%lu): %u(%lu) out of %u", offset,
+ which_group, GH.GroupOffset[which_group], GH.NumGroups);
+#endif /* DEBUG_GROUPS */
+
+ if (which_group == GROUP_INIT_IP)
+ {
+ COUNT month_index, day_index, year_index;
+
+ ReinitQueue (&GLOBAL (ip_group_q));
+#ifdef DEBUG_GROUPS
+ log_add (log_Debug, "%u == %u", GH.star_index,
+ (COUNT)(CurStarDescPtr - star_array));
+#endif /* DEBUG_GROUPS */
+
+ /* Check if the requested groups are valid for this star system
+ * and if they are still current (not expired) */
+ day_index = GH.day_index;
+ month_index = GH.month_index;
+ year_index = GH.year_index;
+ if (offset == GROUPS_RANDOM
+ && (GH.star_index != (COUNT)(CurStarDescPtr - star_array)
+ || !ValidateEvent (ABSOLUTE_EVENT, &month_index, &day_index,
+ &year_index)))
+ {
+#ifdef DEBUG_GROUPS
+ if (GH.star_index == CurStarDescPtr - star_array)
+ log_add (log_Debug, "GetGroupInfo: battle groups out of "
+ "date %u/%u/%u!", month_index, day_index,
+ year_index);
+#endif /* DEBUG_GROUPS */
+
+ CloseStateFile (fp);
+ /* Erase random groups (out of date) */
+ fp = OpenStateFile (RANDGRPINFO_FILE, "wb");
+ memset (&GH, 0, sizeof (GH));
+ GH.star_index = (COUNT)~0;
+ WriteGroupHeader (fp, &GH);
+ CloseStateFile (fp);
+
+ return FALSE;
+ }
+
+ /* Read IP groups into ip_group_q and send them on their missions */
+ for (which_group = 1; which_group <= GH.NumGroups; ++which_group)
+ {
+ BYTE task, group_loc;
+ DWORD rand_val;
+ BYTE RaceType;
+ BYTE NumShips;
+ HIPGROUP hGroup;
+ IP_GROUP *GroupPtr;
+
+ if (GH.GroupOffset[which_group] == 0)
+ continue;
+
+ SeekStateFile (fp, GH.GroupOffset[which_group], SEEK_SET);
+ sread_8 (fp, &RaceType);
+ sread_8 (fp, &NumShips);
+ if (!NumShips)
+ continue; /* group is dead */
+
+ hGroup = BuildGroup (&GLOBAL (ip_group_q), RaceType);
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ GroupPtr->group_id = which_group;
+ GroupPtr->in_system = 1;
+
+ rand_val = TFB_Random ();
+ task = (BYTE)(LOBYTE (LOWORD (rand_val)) % ON_STATION);
+ if (task == FLEE)
+ task = ON_STATION;
+ GroupPtr->orbit_pos = NORMALIZE_FACING (
+ LOBYTE (HIWORD (rand_val)));
+
+ group_loc = pSolarSysState->SunDesc[0].NumPlanets;
+ if (group_loc == 1 && task == EXPLORE)
+ task = IN_ORBIT;
+ else
+ group_loc = (BYTE)((HIBYTE (LOWORD (rand_val)) % group_loc) + 1);
+ GroupPtr->dest_loc = group_loc;
+ rand_val = TFB_Random ();
+ GroupPtr->loc.x = (LOWORD (rand_val) % 10000) - 5000;
+ GroupPtr->loc.y = (HIWORD (rand_val) % 10000) - 5000;
+ GroupPtr->group_counter = 0;
+ if (task == EXPLORE)
+ {
+ GroupPtr->group_counter = ((COUNT)TFB_Random () %
+ MAX_REVOLUTIONS) << FACING_SHIFT;
+ }
+ else if (task == ON_STATION)
+ {
+ COUNT angle;
+ POINT org;
+
+ org = planetOuterLocation (group_loc - 1);
+ angle = FACING_TO_ANGLE (GroupPtr->orbit_pos + 1);
+ GroupPtr->loc.x = org.x + COSINE (angle, STATION_RADIUS);
+ GroupPtr->loc.y = org.y + SINE (angle, STATION_RADIUS);
+ group_loc = 0;
+ }
+
+ GroupPtr->task = task;
+ GroupPtr->sys_loc = group_loc;
+
+#ifdef DEBUG_GROUPS
+ log_add (log_Debug, "battle group %u(0x%04x) strength "
+ "%u, type %u, loc %u<%d, %d>, task %u",
+ which_group,
+ hGroup,
+ NumShips,
+ RaceType,
+ group_loc,
+ GroupPtr->loc.x,
+ GroupPtr->loc.y,
+ task);
+#endif /* DEBUG_GROUPS */
+
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+
+ if (offset != GROUPS_RANDOM)
+ InitGroupInfo (FALSE); /* Wipe out random battle groups */
+ else if (ValidateEvent (ABSOLUTE_EVENT, /* still fresh */
+ &month_index, &day_index, &year_index))
+ {
+ CloseStateFile (fp);
+ return TRUE;
+ }
+
+ CloseStateFile (fp);
+ return (GetHeadLink (&GLOBAL (ip_group_q)) != 0);
+ }
+
+ if (!GH.GroupOffset[which_group])
+ {
+ /* Group not present */
+ CloseStateFile (fp);
+ return FALSE;
+ }
+
+
+ if (which_group == GROUP_LIST)
+ {
+ BYTE NumGroups;
+ COUNT ShipsLeftInLEG;
+
+ // XXX: Hack: First, save the state of last encountered group, if any.
+ // The assumption here is that we read the group list immediately
+ // after an IP encounter, and npc_built_ship_q contains whatever
+ // ships are left in the encountered group (can be none).
+ ShipsLeftInLEG = CountLinks (&GLOBAL (npc_built_ship_q));
+
+ SeekStateFile (fp, GH.GroupOffset[0], SEEK_SET);
+ sread_8 (fp, &LastEncGroup);
+
+ if (LastEncGroup)
+ {
+ // The following 'if' is needed because GROUP_LIST is only
+ // ever read from RANDGRPINFO_FILE, but the LastEncGroup
+ // may need to be updated in the DEFGRPINFO_FILE as well.
+ // In that case, PutGroupInfo() will update the correct file.
+ if (GLOBAL (BattleGroupRef))
+ PutGroupInfo (GLOBAL (BattleGroupRef), LastEncGroup);
+ else
+ FlushGroupInfo (&GH, offset, LastEncGroup, fp);
+ }
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+
+ /* Read group 0 into ip_group_q */
+ ReinitQueue (&GLOBAL (ip_group_q));
+ /* Need a seek because Put/Flush has moved the file ptr */
+ SeekStateFile (fp, GH.GroupOffset[0] + 1, SEEK_SET);
+ sread_8 (fp, &NumGroups);
+
+ while (NumGroups--)
+ {
+ BYTE group_id;
+ BYTE RaceType;
+ HSHIPFRAG hGroup;
+ IP_GROUP *GroupPtr;
+
+ sread_8 (fp, &RaceType);
+
+ hGroup = BuildGroup (&GLOBAL (ip_group_q), RaceType);
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ ReadIpGroup (fp, GroupPtr);
+ group_id = GroupPtr->group_id;
+
+#ifdef DEBUG_GROUPS
+ log_add (log_Debug, "G) type %u, loc %u<%d, %d>, task 0x%02x:%u",
+ RaceType,
+ GroupPtr->sys_loc,
+ GroupPtr->loc.x,
+ GroupPtr->loc.y,
+ GroupPtr->task,
+ GroupPtr->dest_loc);
+#endif /* DEBUG_GROUPS */
+
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+
+ if (group_id == LastEncGroup && !ShipsLeftInLEG)
+ {
+ /* No ships left in the last encountered group, remove it */
+#ifdef DEBUG_GROUPS
+ log_add (log_Debug, " -- REMOVING");
+#endif /* DEBUG_GROUPS */
+ RemoveQueue (&GLOBAL (ip_group_q), hGroup);
+ FreeIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+ }
+
+ CloseStateFile (fp);
+ return (GetHeadLink (&GLOBAL (ip_group_q)) != 0);
+ }
+ else
+ {
+ /* Read 'which_group' group into npc_built_ship_q */
+ BYTE NumShips;
+
+ // XXX: Hack: The assumption here is that we only read the makeup
+ // of a particular group when initializing an encounter, which
+ // makes this group 'last encountered'. Also the state of all
+ // groups is saved here. This may make working with savegames
+ // harder in the future, as special care will have to be taken
+ // when loading a game into an encounter.
+ LastEncGroup = which_group;
+ // The following 'if' is needed because GROUP_LIST is only
+ // ever written to RANDGRPINFO_FILE, but the group we are reading
+ // may be in the DEFGRPINFO_FILE as well.
+ // In that case, PutGroupInfo() will update the correct file.
+ // Always calling PutGroupInfo() here would also be acceptable now.
+ if (offset != GROUPS_RANDOM)
+ PutGroupInfo (GROUPS_RANDOM, GROUP_LIST);
+ else
+ FlushGroupInfo (&GH, GROUPS_RANDOM, GROUP_LIST, fp);
+ ReinitQueue (&GLOBAL (ip_group_q));
+
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ // skip RaceType
+ SeekStateFile (fp, GH.GroupOffset[which_group] + 1, SEEK_SET);
+ sread_8 (fp, &NumShips);
+
+ while (NumShips--)
+ {
+ BYTE RaceType;
+ HSHIPFRAG hStarShip;
+ SHIP_FRAGMENT *FragPtr;
+
+ sread_8 (fp, &RaceType);
+
+ hStarShip = CloneShipFragment (RaceType,
+ &GLOBAL (npc_built_ship_q), 0);
+
+ FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ ReadShipFragment (fp, FragPtr);
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ }
+
+ CloseStateFile (fp);
+ return (GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0);
+ }
+}
+
+DWORD
+PutGroupInfo (DWORD offset, BYTE which_group)
+{
+ GAME_STATE_FILE *fp;
+ GROUP_HEADER GH;
+
+ if (offset != GROUPS_RANDOM && which_group != GROUP_LIST)
+ fp = OpenStateFile (DEFGRPINFO_FILE, "r+b");
+ else
+ fp = OpenStateFile (RANDGRPINFO_FILE, "r+b");
+
+ if (!fp)
+ return offset;
+
+ if (offset == GROUPS_ADD_NEW)
+ {
+ offset = LengthStateFile (fp);
+ SeekStateFile (fp, offset, SEEK_SET);
+ memset (&GH, 0, sizeof (GH));
+ GH.star_index = (COUNT)~0;
+ WriteGroupHeader (fp, &GH);
+ }
+
+ // XXX: This is a bit dangerous. The assumption here is that we are
+ // only called to write GROUP_LIST in the GROUPS_RANDOM context,
+ // which is true right now and in which case we would seek to 0 anyway.
+ // The latter also makes guarding the seek with
+ // 'if (which_group != GROUP_LIST)' moot.
+ if (which_group != GROUP_LIST)
+ {
+ SeekStateFile (fp, offset, SEEK_SET);
+ if (which_group == GROUP_SAVE_IP)
+ {
+ LastEncGroup = 0;
+ which_group = GROUP_LIST;
+ }
+ }
+ ReadGroupHeader (fp, &GH);
+
+#ifdef NEVER
+ // XXX: this appears to be a remnant of a slightly different group info
+ // expiration mechanism. Nowadays, the 'defined' groups never expire,
+ // and the dead 'random' groups stay in the file with NumShips==0 until
+ // the entire 'random' group header expires.
+ if (GetHeadLink (&GLOBAL (npc_built_ship_q)) || GH.GroupOffset[0] == 0)
+#endif /* NEVER */
+ {
+ COUNT month_index, day_index, year_index;
+
+ /* The groups in this system are good for the next 7 days */
+ month_index = 0;
+ day_index = 7;
+ year_index = 0;
+ ValidateEvent (RELATIVE_EVENT, &month_index, &day_index, &year_index);
+ GH.day_index = (BYTE)day_index;
+ GH.month_index = (BYTE)month_index;
+ GH.year_index = year_index;
+ }
+ GH.star_index = CurStarDescPtr - star_array;
+
+#ifdef DEBUG_GROUPS
+ log_add (log_Debug, "PutGroupInfo(%lu): %u out of %u -- %u/%u/%u",
+ offset, which_group, GH.NumGroups,
+ GH.month_index, GH.day_index, GH.year_index);
+#endif /* DEBUG_GROUPS */
+
+ FlushGroupInfo (&GH, offset, which_group, fp);
+
+ CloseStateFile (fp);
+
+ return (offset);
+}
+
diff --git a/src/uqm/grpinfo.h b/src/uqm/grpinfo.h
new file mode 100644
index 0000000..65286aa
--- /dev/null
+++ b/src/uqm/grpinfo.h
@@ -0,0 +1,93 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_GRPINFO_H_
+#define UQM_GRPINFO_H_
+
+#include "port.h"
+#include "libs/compiler.h"
+#include "displist.h"
+#include "libs/gfxlib.h"
+ // for POINT
+#include <assert.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// XXX: Needed to maintain savegame compatibility
+#define NUM_SAVED_BATTLE_GROUPS 64
+
+typedef HLINK HIPGROUP;
+
+typedef struct
+{
+ // LINK elements; must be first
+ HIPGROUP pred;
+ HIPGROUP succ;
+
+ UWORD group_counter;
+ BYTE race_id;
+ BYTE sys_loc;
+ BYTE task; // AKA mission
+ BYTE in_system;
+ // a simple != 0 flag
+ // In older savegames this will be >1, because
+ // CloneShipFragment was used to spawn groups,
+ // and it set this to crew_level values
+
+ BYTE dest_loc;
+ BYTE orbit_pos;
+ /* Also: saved prev dest_loc before intercept call,
+ * restored to dest_loc on all-clear */
+ BYTE group_id;
+ POINT loc;
+
+ FRAME melee_icon;
+} IP_GROUP;
+
+enum
+{
+ IN_ORBIT = 0,
+ EXPLORE,
+ FLEE,
+ ON_STATION,
+
+ IGNORE_FLAGSHIP = 1 << 2,
+ REFORM_GROUP = 1 << 3
+};
+#define MAX_REVOLUTIONS 5
+
+#define STATION_RADIUS 1600
+#define ORBIT_RADIUS 2400
+
+static inline IP_GROUP *
+LockIpGroup (const QUEUE *pq, HIPGROUP h)
+{
+ assert (GetLinkSize (pq) == sizeof (IP_GROUP));
+ return (IP_GROUP *) LockLink (pq, h);
+}
+
+#define UnlockIpGroup(pq, h) UnlockLink (pq, h)
+#define FreeIpGroup(pq, h) FreeLink (pq, h)
+
+extern HIPGROUP BuildGroup (QUEUE *pDstQueue, BYTE race_id);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_GRPINFO_H_ */
diff --git a/src/uqm/grpintrn.h b/src/uqm/grpintrn.h
new file mode 100644
index 0000000..d2136a5
--- /dev/null
+++ b/src/uqm/grpintrn.h
@@ -0,0 +1,56 @@
+#ifndef _GRPINTRN_H
+#define _GRPINTRN_H
+
+// For IPGROUP
+#include "grpinfo.h"
+
+// For SHIP_FRAGMENT
+#include "races.h"
+
+// For GAME_STATE_FILE
+#include "state.h"
+
+//#define DEBUG_GROUPS
+
+// A group header describes battle groups present in a star system. There is
+// at most 1 group header per system.
+// 'Random' group info file (RANDGRPINFO_FILE) always contains only one
+// group header record, which describes the last-visited star system,
+// (which may be the current system). Thus the randomly generated groups
+// are valid for 7 days (set in PutGroupInfo) after the player leaves
+// the system, or until the player enters another star system.
+typedef struct
+{
+ BYTE NumGroups;
+ BYTE day_index, month_index;
+ COUNT star_index, year_index;
+ // day_index, month_index, year_index specify when
+ // random groups expire (if you were to leave the system
+ // by going to HSpace and stay there till such time)
+ // star_index is the index of a star this group header
+ // applies to; ~0 means uninited
+ DWORD GroupOffset[NUM_SAVED_BATTLE_GROUPS + 1];
+ // Absolute offsets of group definitions in a state file
+ // Group 0 is a list of groups present in solarsys
+ // (RANDGRPINFO_FILE only)
+ // Groups 1..max are definitions of actual battle groups
+ // containing ship makeup and status
+
+ // Each group has the following format:
+ // 1 byte, RaceType (LastEncGroup in Group 0)
+ // 1 byte, NumShips (NumGroups in Group 0)
+ // Ships follow:
+ // 1 byte, RaceType
+ // 16 bytes, part of SHIP_FRAGMENT struct
+ // (part of IP_GROUP struct in Group 0)
+
+} GROUP_HEADER;
+
+void ReadGroupHeader (GAME_STATE_FILE *fp, GROUP_HEADER *pGH);
+void WriteGroupHeader (GAME_STATE_FILE *fp, const GROUP_HEADER *pGH);
+void ReadShipFragment (GAME_STATE_FILE *fp, SHIP_FRAGMENT *FragPtr);
+void WriteShipFragment (GAME_STATE_FILE *fp, const SHIP_FRAGMENT *FragPtr);
+void ReadIpGroup (GAME_STATE_FILE *fp, IP_GROUP *GroupPtr);
+void WriteIpGroup (GAME_STATE_FILE *fp, const IP_GROUP *GroupPtr);
+
+#endif
diff --git a/src/uqm/hyper.c b/src/uqm/hyper.c
new file mode 100644
index 0000000..9a9b9f4
--- /dev/null
+++ b/src/uqm/hyper.c
@@ -0,0 +1,1747 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "hyper.h"
+
+#include "build.h"
+#include "collide.h"
+#include "colors.h"
+#include "controls.h"
+#include "gameopt.h"
+#include "menustat.h"
+ // for DrawMenuStateStrings()
+#include "encount.h"
+#include "starmap.h"
+#include "ship.h"
+#include "shipcont.h"
+#include "process.h"
+#include "globdata.h"
+#include "sis.h"
+#include "units.h"
+#include "init.h"
+#include "nameref.h"
+#include "resinst.h"
+#include "setup.h"
+#include "sounds.h"
+#include "options.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/mathlib.h"
+
+
+#define XOFFS ((RADAR_SCAN_WIDTH + (UNIT_SCREEN_WIDTH << 2)) >> 1)
+#define YOFFS ((RADAR_SCAN_HEIGHT + (UNIT_SCREEN_HEIGHT << 2)) >> 1)
+
+static FRAME hyperstars[3];
+static COLORMAP hypercmaps[2];
+static BYTE fuel_ticks;
+static COUNT hyper_dx, hyper_dy, hyper_extra;
+
+// HyperspaceMenu() items
+enum HyperMenuItems
+{
+ // XXX: Must match the enum in menustat.h
+ STARMAP = 1,
+ EQUIP_DEVICE,
+ CARGO,
+ ROSTER,
+ GAME_MENU,
+ NAVIGATION,
+};
+
+
+void
+MoveSIS (SIZE *pdx, SIZE *pdy)
+{
+ SIZE new_dx, new_dy;
+
+ new_dx = *pdx;
+ GLOBAL_SIS (log_x) -= new_dx;
+ if (GLOBAL_SIS (log_x) < 0)
+ {
+ new_dx += (SIZE)GLOBAL_SIS (log_x);
+ GLOBAL_SIS (log_x) = 0;
+ }
+ else if (GLOBAL_SIS (log_x) > MAX_X_LOGICAL)
+ {
+ new_dx += (SIZE)(GLOBAL_SIS (log_x) - MAX_X_LOGICAL);
+ GLOBAL_SIS (log_x) = MAX_X_LOGICAL;
+ }
+
+ new_dy = *pdy;
+ GLOBAL_SIS (log_y) -= new_dy;
+ if (GLOBAL_SIS (log_y) < 0)
+ {
+ new_dy += (SIZE)GLOBAL_SIS (log_y);
+ GLOBAL_SIS (log_y) = 0;
+ }
+ else if (GLOBAL_SIS (log_y) > MAX_Y_LOGICAL)
+ {
+ new_dy += (SIZE)(GLOBAL_SIS (log_y) - MAX_Y_LOGICAL);
+ GLOBAL_SIS (log_y) = MAX_Y_LOGICAL;
+ }
+
+ if (new_dx != *pdx || new_dy != *pdy)
+ {
+ HELEMENT hElement, hNextElement;
+
+ *pdx = new_dx;
+ *pdy = new_dy;
+
+ for (hElement = GetTailElement ();
+ hElement != 0; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+
+ if (!(ElementPtr->state_flags & PLAYER_SHIP))
+ hNextElement = GetPredElement (ElementPtr);
+ else
+ {
+ ElementPtr->next.location.x = (LOG_SPACE_WIDTH >> 1) - new_dx;
+ ElementPtr->next.location.y = (LOG_SPACE_HEIGHT >> 1) - new_dy;
+ hNextElement = 0;
+ }
+
+ UnlockElement (hElement);
+ }
+ }
+
+ if (GLOBAL_SIS (FuelOnBoard) && GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ COUNT cur_fuel_ticks;
+ COUNT hyper_dist;
+ DWORD adj_dx, adj_dy;
+
+ if (new_dx < 0)
+ new_dx = -new_dx;
+ hyper_dx += new_dx;
+ if (new_dy < 0)
+ new_dy = -new_dy;
+ hyper_dy += new_dy;
+
+ /* These macros are also used in the fuel estimate on the starmap. */
+ adj_dx = LOGX_TO_UNIVERSE(16 * hyper_dx);
+ adj_dy = MAX_Y_UNIVERSE - LOGY_TO_UNIVERSE(16 * hyper_dy);
+
+ hyper_dist = square_root (adj_dx * adj_dx + adj_dy * adj_dy)
+ + hyper_extra;
+ cur_fuel_ticks = hyper_dist >> 4;
+
+ if (cur_fuel_ticks > (COUNT)fuel_ticks)
+ {
+#ifndef TESTING
+ DeltaSISGauges (0, fuel_ticks - cur_fuel_ticks, 0);
+#endif /* TESTING */
+ if (cur_fuel_ticks > 0x00FF)
+ {
+ hyper_dx = 0;
+ hyper_extra = hyper_dist & ((1 << 4) - 1);
+ hyper_dy = 0;
+ cur_fuel_ticks = 0;
+ }
+
+ fuel_ticks = (BYTE)cur_fuel_ticks;
+ }
+ }
+}
+
+void
+check_hyperspace_encounter (void)
+{
+ BYTE Type;
+ POINT universe;
+ HFLEETINFO hStarShip, hNextShip;
+ COUNT EncounterPercent[] =
+ {
+ RACE_HYPERSPACE_PERCENT
+ };
+
+ universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+ for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q)), Type = 0;
+ hStarShip && (GLOBAL (CurrentActivity) & IN_BATTLE);
+ hStarShip = hNextShip, ++Type)
+ {
+ COUNT encounter_radius;
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ encounter_radius = FleetPtr->actual_strength;
+ if (encounter_radius)
+ {
+ BYTE encounter_flags;
+ SIZE dx, dy;
+ COUNT percent;
+ HENCOUNTER hEncounter;
+ ENCOUNTER *EncounterPtr;
+
+ encounter_flags = 0;
+ percent = EncounterPercent[Type];
+
+ if (encounter_radius != INFINITE_RADIUS)
+ {
+ encounter_radius =
+ (encounter_radius * SPHERE_RADIUS_INCREMENT) >> 1;
+ }
+ else /* encounter_radius == infinity */
+ {
+ HENCOUNTER hNextEncounter;
+
+ encounter_radius = (MAX_X_UNIVERSE + 1) << 1;
+ if (Type == SLYLANDRO_SHIP)
+ {
+ encounter_flags = ONE_SHOT_ENCOUNTER;
+ if (!GET_GAME_STATE (STARBASE_AVAILABLE))
+ percent = 100;
+ else
+ percent *= GET_GAME_STATE (SLYLANDRO_MULTIPLIER);
+ }
+ else if (Type == MELNORME_SHIP
+ && (GLOBAL_SIS (FuelOnBoard) == 0
+ || GET_GAME_STATE (USED_BROADCASTER))
+ && GET_GAME_STATE (MELNORME_ANGER) < 3)
+ {
+ if (!GET_GAME_STATE (USED_BROADCASTER))
+ percent = 30;
+ else
+ percent = 100;
+ encounter_flags = ONE_SHOT_ENCOUNTER;
+ }
+
+ // There can be only one! (of either Slylandro or Melnorme)
+ for (hEncounter = GetHeadEncounter ();
+ hEncounter; hEncounter = hNextEncounter)
+ {
+ LockEncounter (hEncounter, &EncounterPtr);
+ hNextEncounter = GetSuccEncounter (EncounterPtr);
+ if (EncounterPtr->race_id == Type)
+ {
+ percent = 0;
+ hNextEncounter = 0;
+ }
+ UnlockEncounter (hEncounter);
+ }
+
+
+ if (percent == 100 && Type == MELNORME_SHIP)
+ {
+ SET_GAME_STATE (BROADCASTER_RESPONSE, 1);
+ }
+ }
+
+ dx = universe.x - FleetPtr->loc.x;
+ if (dx < 0)
+ dx = -dx;
+ dy = universe.y - FleetPtr->loc.y;
+ if (dy < 0)
+ dy = -dy;
+ if ((COUNT)dx < encounter_radius
+ && (COUNT)dy < encounter_radius
+ && (DWORD)dx * dx + (DWORD)dy * dy <
+ (DWORD)encounter_radius * encounter_radius
+ && ((COUNT)TFB_Random () % 100) < percent)
+ {
+ // Ship spawned for encounter.
+ hEncounter = AllocEncounter ();
+ if (hEncounter)
+ {
+ LockEncounter (hEncounter, &EncounterPtr);
+ memset (EncounterPtr, 0, sizeof (*EncounterPtr));
+ EncounterPtr->origin = FleetPtr->loc;
+ EncounterPtr->radius = encounter_radius;
+ EncounterPtr->flags = encounter_flags;
+ EncounterPtr->race_id = Type;
+ UnlockEncounter (hEncounter);
+
+ PutEncounter (hEncounter);
+ }
+ }
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+
+ SET_GAME_STATE (USED_BROADCASTER, 0);
+}
+
+void
+FreeHyperData (void)
+{
+ DestroyDrawable (ReleaseDrawable (hyperstars[0]));
+ hyperstars[0] = 0;
+ DestroyDrawable (ReleaseDrawable (hyperstars[1]));
+ hyperstars[1] = 0;
+ DestroyDrawable (ReleaseDrawable (hyperstars[2]));
+ hyperstars[2] = 0;
+
+ DestroyColorMap (ReleaseColorMap (hypercmaps[0]));
+ hypercmaps[0] = 0;
+ DestroyColorMap (ReleaseColorMap (hypercmaps[1]));
+ hypercmaps[1] = 0;
+}
+
+static void
+LoadHyperData (void)
+{
+ if (hyperstars[0] == 0)
+ {
+ hyperstars[0] = CaptureDrawable (
+ LoadGraphic (AMBIENT_MASK_PMAP_ANIM));
+ hyperstars[1] = CaptureDrawable (
+ LoadGraphic (HYPERSTARS_MASK_PMAP_ANIM));
+ hypercmaps[0] = CaptureColorMap (LoadColorMap (HYPER_COLOR_TAB));
+
+ hyperstars[2] = CaptureDrawable (
+ LoadGraphic (ARISPACE_MASK_PMAP_ANIM));
+ hypercmaps[1] = CaptureColorMap (LoadColorMap (ARISPACE_COLOR_TAB));
+ }
+}
+
+BOOLEAN
+LoadHyperspace (void)
+{
+ hyper_dx = 0;
+ hyper_dy = 0;
+ hyper_extra = 0;
+ fuel_ticks = 1;
+
+ GLOBAL (ShipStamp.origin.x) = -MAX_X_UNIVERSE;
+ GLOBAL (ShipStamp.origin.y) = -MAX_Y_UNIVERSE;
+
+ LoadHyperData ();
+ {
+ FRAME F;
+
+ F = hyperstars[0];
+ hyperstars[0] = stars_in_space;
+ stars_in_space = F;
+ }
+
+ if (!(LastActivity & CHECK_LOAD))
+ RepairSISBorder ();
+ else
+ {
+ if (LOBYTE (LastActivity) == 0)
+ {
+ DrawSISFrame ();
+ }
+ else
+ {
+ ClearSISRect (DRAW_SIS_DISPLAY);
+ RepairSISBorder ();
+ }
+ }
+ if (!(GLOBAL (autopilot.x) != ~0 && GLOBAL (autopilot.y) != ~0))
+ {
+ DrawSISMessage (NULL);
+ }
+
+ SetContext (RadarContext);
+ SetContextBackGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x0E, 0x00), 0x6C));
+
+ SetContext (SpaceContext);
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ SetContextBackGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x07, 0x00, 0x00), 0x2F));
+ SetColorMap (GetColorMapAddress (hypercmaps[0]));
+ }
+ else
+ {
+ SetContextBackGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x1A, 0x00), 0x2F));
+ SetColorMap (GetColorMapAddress (hypercmaps[1]));
+ SET_GAME_STATE (USED_BROADCASTER, 0);
+ SET_GAME_STATE (BROADCASTER_RESPONSE, 0);
+ }
+// ClearDrawable ();
+
+ ClearSISRect (CLEAR_SIS_RADAR);
+
+ return TRUE;
+}
+
+BOOLEAN
+FreeHyperspace (void)
+{
+ {
+ FRAME F;
+
+ F = hyperstars[0];
+ hyperstars[0] = stars_in_space;
+ stars_in_space = F;
+ }
+// FreeHyperData ();
+
+ return TRUE;
+}
+
+static void
+ElementToUniverse (ELEMENT *ElementPtr, POINT *pPt)
+{
+ SDWORD log_x, log_y;
+
+ log_x = GLOBAL_SIS (log_x)
+ + (ElementPtr->next.location.x - (LOG_SPACE_WIDTH >> 1));
+ log_y = GLOBAL_SIS (log_y)
+ + (ElementPtr->next.location.y - (LOG_SPACE_HEIGHT >> 1));
+ pPt->x = LOGX_TO_UNIVERSE (log_x);
+ pPt->y = LOGY_TO_UNIVERSE (log_y);
+}
+
+static void
+cleanup_hyperspace (void)
+{
+ HENCOUNTER hEncounter, hNextEncounter;
+
+ for (hEncounter = GetHeadEncounter ();
+ hEncounter != 0; hEncounter = hNextEncounter)
+ {
+ ENCOUNTER *EncounterPtr;
+
+ LockEncounter (hEncounter, &EncounterPtr);
+ hNextEncounter = GetSuccEncounter (EncounterPtr);
+ if (EncounterPtr->hElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (EncounterPtr->hElement, &ElementPtr);
+
+ if (ElementPtr->hTarget)
+ { // This is the encounter that collided with flagship
+ // Move the encounter to the head of the queue so that
+ // comm.c:RaceCommunication() gets the right one.
+ RemoveEncounter (hEncounter);
+ InsertEncounter (hEncounter, GetHeadEncounter ());
+ }
+
+ UnlockElement (EncounterPtr->hElement);
+ }
+ EncounterPtr->hElement = 0;
+ UnlockEncounter (hEncounter);
+ }
+}
+
+typedef enum
+{
+ RANDOM_ENCOUNTER_TRANSITION,
+ INTERPLANETARY_TRANSITION,
+ ARILOU_SPACE_TRANSITION
+} TRANSITION_TYPE;
+
+static void
+InterplanetaryTransition (ELEMENT *ElementPtr)
+{
+ GLOBAL (ip_planet) = 0;
+ GLOBAL (in_orbit) = 0;
+ GLOBAL (ShipFacing) = 0; /* Not reentering the system */
+ SET_GAME_STATE (USED_BROADCASTER, 0);
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ // Enter a solar system from HyperSpace.
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (ESCAPE_COUNTER, 0);
+ }
+ else
+ {
+ POINT pt;
+
+ GLOBAL (autopilot.x) = ~0;
+ GLOBAL (autopilot.y) = ~0;
+
+ ElementToUniverse (ElementPtr, &pt);
+ CurStarDescPtr = FindStar (NULL, &pt, 5, 5);
+ if (CurStarDescPtr->star_pt.x == ARILOU_HOME_X
+ && CurStarDescPtr->star_pt.y == ARILOU_HOME_Y)
+ {
+ // Meet the Arilou.
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ }
+ else
+ {
+ // Transition from QuasiSpace to HyperSpace through
+ // one of the permanent portals.
+ COUNT index;
+ const POINT portal_pt[] = QUASISPACE_PORTALS_HYPERSPACE_ENDPOINTS;
+
+ index = CurStarDescPtr - &star_array[NUM_SOLAR_SYSTEMS + 1];
+ GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX (portal_pt[index].x);
+ GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY (portal_pt[index].y);
+
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, 0);
+ }
+ }
+}
+
+/* Enter QuasiSpace from HyperSpace by any portal, or HyperSpace from
+ * QuasiSpace through the periodically opening portal.
+ */
+static void
+ArilouSpaceTransition (void)
+{
+ GLOBAL (ShipFacing) = 0; /* Not reentering the system */
+ SET_GAME_STATE (USED_BROADCASTER, 0);
+ GLOBAL (autopilot.x) = ~0;
+ GLOBAL (autopilot.y) = ~0;
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ // From HyperSpace to QuasiSpace.
+ GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX (QUASI_SPACE_X);
+ GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY (QUASI_SPACE_Y);
+ if (GET_GAME_STATE (PORTAL_COUNTER) == 0)
+ {
+ // Periodically appearing portal.
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, 3);
+ }
+ else
+ {
+ // Player-induced portal.
+ SET_GAME_STATE (PORTAL_COUNTER, 0);
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, 3);
+ }
+ }
+ else
+ {
+ // From QuasiSpace to HyperSpace through the periodically appearing
+ // portal.
+ GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX (ARILOU_SPACE_X);
+ GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY (ARILOU_SPACE_Y);
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, 0);
+ }
+}
+
+static void
+unhyper_transition (ELEMENT *ElementPtr)
+{
+ COUNT frame_index;
+
+ ElementPtr->state_flags |= CHANGING;
+
+ frame_index = GetFrameIndex (ElementPtr->current.image.frame);
+ if (frame_index == 0)
+ frame_index += ANGLE_TO_FACING (FULL_CIRCLE);
+ else if (frame_index < ANGLE_TO_FACING (FULL_CIRCLE))
+ frame_index = NORMALIZE_FACING (frame_index + 1);
+ else if (++frame_index == GetFrameCount (ElementPtr->current.image.frame))
+ {
+ cleanup_hyperspace ();
+
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ switch ((TRANSITION_TYPE) ElementPtr->turn_wait)
+ {
+ case RANDOM_ENCOUNTER_TRANSITION:
+ SaveSisHyperState ();
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ break;
+ case INTERPLANETARY_TRANSITION:
+ InterplanetaryTransition (ElementPtr);
+ break;
+ case ARILOU_SPACE_TRANSITION:
+ ArilouSpaceTransition ();
+ break;
+ }
+
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ return;
+ }
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame, frame_index);
+}
+
+static void
+init_transition (ELEMENT *ElementPtr0, ELEMENT *ElementPtr1,
+ TRANSITION_TYPE which_transition)
+{
+ SIZE dx, dy;
+ SIZE num_turns;
+ STARSHIP *StarShipPtr;
+
+ dx = WORLD_TO_VELOCITY (ElementPtr0->next.location.x
+ - ElementPtr1->next.location.x);
+ dy = WORLD_TO_VELOCITY (ElementPtr0->next.location.y
+ - ElementPtr1->next.location.y);
+
+ ElementPtr1->state_flags |= NONSOLID;
+ ElementPtr1->preprocess_func = unhyper_transition;
+ ElementPtr1->postprocess_func = NULL;
+ ElementPtr1->turn_wait = (BYTE) which_transition;
+
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ num_turns = GetFrameCount (ElementPtr1->next.image.frame)
+ - ANGLE_TO_FACING (FULL_CIRCLE)
+ + NORMALIZE_FACING (ANGLE_TO_FACING (FULL_CIRCLE)
+ - StarShipPtr->ShipFacing);
+ if (num_turns == 0)
+ num_turns = 1;
+
+ SetVelocityComponents (&ElementPtr1->velocity,
+ dx / num_turns, dy / num_turns);
+}
+
+BOOLEAN
+hyper_transition (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ if (LastActivity & CHECK_LOAD)
+ {
+ LastActivity &= ~CHECK_LOAD;
+
+ ElementPtr->current = ElementPtr->next;
+ SetUpElement (ElementPtr);
+
+ ElementPtr->state_flags |= DEFY_PHYSICS;
+
+ return FALSE;
+ }
+ else
+ {
+ ElementPtr->preprocess_func =
+ (void (*) (struct element *ElementPtr)) hyper_transition;
+ ElementPtr->postprocess_func = NULL;
+ ElementPtr->state_flags |= NONSOLID;
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame,
+ GetFrameCount (ElementPtr->current.image.frame) - 1);
+ }
+ }
+ else
+ {
+ COUNT frame_index;
+
+ frame_index = GetFrameIndex (ElementPtr->current.image.frame);
+ if (frame_index-- <= ANGLE_TO_FACING (FULL_CIRCLE))
+ {
+ STARSHIP *StarShipPtr;
+
+ if (frame_index == ANGLE_TO_FACING (FULL_CIRCLE) - 1)
+ frame_index = 0;
+ else
+ frame_index = NORMALIZE_FACING (frame_index);
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (frame_index == StarShipPtr->ShipFacing)
+ {
+ ElementPtr->preprocess_func = ship_preprocess;
+ ElementPtr->postprocess_func = ship_postprocess;
+ ElementPtr->state_flags &= ~NONSOLID;
+ }
+ }
+
+ ElementPtr->state_flags |= CHANGING;
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame,
+ frame_index);
+
+ if (!(ElementPtr->state_flags & NONSOLID))
+ {
+ ElementPtr->current = ElementPtr->next;
+ SetUpElement (ElementPtr);
+
+ ElementPtr->state_flags |= DEFY_PHYSICS;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+hyper_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if ((ElementPtr1->state_flags & PLAYER_SHIP)
+ && GET_GAME_STATE (PORTAL_COUNTER) == 0)
+ {
+ SIZE dx, dy;
+ POINT pt;
+ STAR_DESC *SDPtr;
+ STARSHIP *StarShipPtr;
+
+ ElementToUniverse (ElementPtr0, &pt);
+
+ SDPtr = FindStar (NULL, &pt, 5, 5);
+
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ GetCurrentVelocityComponents (&ElementPtr1->velocity, &dx, &dy);
+ if (SDPtr == CurStarDescPtr
+ || (ElementPtr1->state_flags & APPEARING)
+ || !(dx || dy || (StarShipPtr->cur_status_flags
+ & (LEFT | RIGHT | THRUST | WEAPON | SPECIAL))))
+ {
+ CurStarDescPtr = SDPtr;
+ ElementPtr0->state_flags |= DEFY_PHYSICS | COLLISION;
+ }
+ else if ((GLOBAL (CurrentActivity) & IN_BATTLE)
+ && (GLOBAL (autopilot.x) == ~0
+ || GLOBAL (autopilot.y) == ~0
+ || (GLOBAL (autopilot.x) == SDPtr->star_pt.x
+ && GLOBAL (autopilot.y) == SDPtr->star_pt.y)))
+ {
+ CurStarDescPtr = SDPtr;
+ ElementPtr0->state_flags |= COLLISION;
+
+ init_transition (ElementPtr0, ElementPtr1,
+ INTERPLANETARY_TRANSITION);
+ }
+ }
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static void
+hyper_death (ELEMENT *ElementPtr)
+{
+ if (!(ElementPtr->state_flags & DEFY_PHYSICS)
+ && (GLOBAL (CurrentActivity) & IN_BATTLE))
+ CurStarDescPtr = 0;
+}
+
+static void
+arilou_space_death (ELEMENT *ElementPtr)
+{
+ if (!(ElementPtr->state_flags & DEFY_PHYSICS)
+ || GET_GAME_STATE (ARILOU_SPACE_COUNTER) == 0)
+ {
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, 0);
+ }
+ else
+ {
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, 3);
+ }
+ }
+}
+
+static void
+arilou_space_collision (ELEMENT *ElementPtr0,
+ POINT *pPt0, ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ COUNT which_side;
+
+ if (!(ElementPtr1->state_flags & PLAYER_SHIP))
+ return;
+
+ which_side = GET_GAME_STATE (ARILOU_SPACE_SIDE);
+ if (which_side == 0 || which_side == 3)
+ {
+ if (ElementPtr1->state_flags & DEFY_PHYSICS)
+ {
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, which_side ^ 1);
+ }
+ else
+ {
+ init_transition (ElementPtr0, ElementPtr1,
+ ARILOU_SPACE_TRANSITION);
+ }
+ }
+
+ ElementPtr0->state_flags |= DEFY_PHYSICS | COLLISION;
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static HELEMENT
+AllocHyperElement (const POINT *elem_pt)
+{
+ HELEMENT hHyperSpaceElement;
+
+ hHyperSpaceElement = AllocElement ();
+ if (hHyperSpaceElement)
+ {
+ ELEMENT *HyperSpaceElementPtr;
+
+ LockElement (hHyperSpaceElement, &HyperSpaceElementPtr);
+ HyperSpaceElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ HyperSpaceElementPtr->state_flags = CHANGING | FINITE_LIFE;
+ HyperSpaceElementPtr->life_span = 1;
+ HyperSpaceElementPtr->mass_points = 1;
+
+ {
+ long lx, ly;
+
+ lx = UNIVERSE_TO_LOGX (elem_pt->x)
+ + (LOG_SPACE_WIDTH >> 1) - GLOBAL_SIS (log_x);
+ HyperSpaceElementPtr->current.location.x = WRAP_X (lx);
+
+ ly = UNIVERSE_TO_LOGY (elem_pt->y)
+ + (LOG_SPACE_HEIGHT >> 1) - GLOBAL_SIS (log_y);
+ HyperSpaceElementPtr->current.location.y = WRAP_Y (ly);
+ }
+
+ SetPrimType (&DisplayArray[HyperSpaceElementPtr->PrimIndex],
+ STAMP_PRIM);
+ HyperSpaceElementPtr->current.image.farray =
+ &hyperstars[1 + (GET_GAME_STATE (ARILOU_SPACE_SIDE) >> 1)];
+
+ UnlockElement (hHyperSpaceElement);
+ }
+
+ return hHyperSpaceElement;
+}
+
+static void
+AddAmbientElement (void)
+{
+ HELEMENT hHyperSpaceElement;
+
+ hHyperSpaceElement = AllocElement ();
+ if (hHyperSpaceElement)
+ {
+ SIZE dx, dy;
+ DWORD rand_val;
+ ELEMENT *HyperSpaceElementPtr;
+
+ LockElement (hHyperSpaceElement, &HyperSpaceElementPtr);
+ HyperSpaceElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ HyperSpaceElementPtr->state_flags =
+ APPEARING | FINITE_LIFE | NONSOLID;
+ SetPrimType (&DisplayArray[HyperSpaceElementPtr->PrimIndex],
+ STAMP_PRIM);
+ HyperSpaceElementPtr->preprocess_func = animation_preprocess;
+
+ rand_val = TFB_Random ();
+ dy = LOWORD (rand_val);
+ dx = (SIZE)(LOBYTE (dy) % SPACE_WIDTH) - (SPACE_WIDTH >> 1);
+ dy = (SIZE)(HIBYTE (dy) % SPACE_HEIGHT) - (SPACE_HEIGHT >> 1);
+ HyperSpaceElementPtr->current.location.x = (LOG_SPACE_WIDTH >> 1)
+ + DISPLAY_TO_WORLD (dx);
+ HyperSpaceElementPtr->current.location.y = (LOG_SPACE_HEIGHT >> 1)
+ + DISPLAY_TO_WORLD (dy);
+ HyperSpaceElementPtr->current.image.farray = &stars_in_space;
+
+ if (HIWORD (rand_val) & 7)
+ {
+ HyperSpaceElementPtr->life_span = 14;
+ HyperSpaceElementPtr->current.image.frame = stars_in_space;
+ }
+ else
+ {
+ HyperSpaceElementPtr->life_span = 12;
+ HyperSpaceElementPtr->current.image.frame =
+ SetAbsFrameIndex (stars_in_space, 14);
+ }
+
+ UnlockElement (hHyperSpaceElement);
+
+ InsertElement (hHyperSpaceElement, GetHeadElement ());
+ }
+}
+
+#define NUM_VORTEX_TRANSITIONS 9
+#define VORTEX_WAIT 1
+
+static void
+encounter_transition (ELEMENT *ElementPtr)
+{
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->life_span = 1;
+ if (ElementPtr->turn_wait)
+ {
+ --ElementPtr->turn_wait;
+ }
+ else
+ {
+ FRAME f;
+
+ if (ElementPtr->hit_points)
+ {
+ f = DecFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->next.image.frame = f;
+ }
+ else
+ {
+ f = IncFrameIndex (ElementPtr->current.image.frame);
+ if (f != ElementPtr->current.image.farray[0])
+ ElementPtr->next.image.frame = f;
+ else
+ ElementPtr->death_func = NULL;
+ }
+
+ ElementPtr->turn_wait = VORTEX_WAIT;
+ }
+}
+
+static HELEMENT
+getSisElement (void)
+{
+ HSTARSHIP hSis;
+ HELEMENT hShip;
+ STARSHIP *StarShipPtr;
+
+ hSis = GetHeadLink (&race_q[RPG_PLAYER_NUM]);
+ if (!hSis)
+ return NULL;
+
+ StarShipPtr = LockStarShip (&race_q[RPG_PLAYER_NUM], hSis);
+ hShip = StarShipPtr->hShip;
+ UnlockStarShip (&race_q[RPG_PLAYER_NUM], hSis);
+
+#ifdef DEBUG
+ {
+ ELEMENT *ElementPtr;
+ LockElement (hShip, &ElementPtr);
+ assert (ElementPtr->state_flags & PLAYER_SHIP);
+ UnlockElement (hShip);
+ }
+#endif
+
+ return hShip;
+}
+
+static void
+encounter_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ HENCOUNTER hEncounter;
+ HENCOUNTER hNextEncounter;
+
+ if (!(ElementPtr1->state_flags & PLAYER_SHIP)
+ || !(GLOBAL (CurrentActivity) & IN_BATTLE))
+ return;
+
+ init_transition (ElementPtr0, ElementPtr1, RANDOM_ENCOUNTER_TRANSITION);
+
+ for (hEncounter = GetHeadEncounter ();
+ hEncounter != 0; hEncounter = hNextEncounter)
+ {
+ ENCOUNTER *EncounterPtr;
+
+ LockEncounter (hEncounter, &EncounterPtr);
+ hNextEncounter = GetSuccEncounter (EncounterPtr);
+ if (EncounterPtr->hElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (EncounterPtr->hElement, &ElementPtr);
+ ElementPtr->state_flags |= NONSOLID | IGNORE_SIMILAR;
+ UnlockElement (EncounterPtr->hElement);
+ }
+ UnlockEncounter (hEncounter);
+ }
+
+ // Mark this element as collided with flagship
+ // XXX: We could simply set hTarget to 1 or to ElementPtr1,
+ // but that would be too hacky ;)
+ ElementPtr0->hTarget = getSisElement ();
+ ZeroVelocityComponents (&ElementPtr0->velocity);
+
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static HELEMENT
+AddEncounterElement (ENCOUNTER *EncounterPtr, POINT *puniverse)
+{
+ BOOLEAN NewEncounter;
+ HELEMENT hElement;
+ POINT enc_pt;
+
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) >= 2)
+ return 0;
+
+ if (EncounterPtr->flags & ENCOUNTER_REFORMING)
+ {
+ EncounterPtr->flags &= ~ENCOUNTER_REFORMING;
+
+ EncounterPtr->transition_state = 100;
+ if ((EncounterPtr->flags & ONE_SHOT_ENCOUNTER)
+ || EncounterPtr->num_ships == 0)
+ return 0;
+ }
+
+ if (EncounterPtr->num_ships)
+ {
+ NewEncounter = FALSE;
+ enc_pt = EncounterPtr->loc_pt;
+ }
+ else
+ {
+ BYTE Type;
+ SIZE dx, dy;
+ COUNT i;
+ COUNT NumShips;
+ DWORD radius_squared;
+ BYTE EncounterMakeup[] =
+ {
+ RACE_ENCOUNTER_MAKEUP
+ };
+
+ NewEncounter = TRUE;
+
+ radius_squared = (DWORD)EncounterPtr->radius * EncounterPtr->radius;
+
+ Type = EncounterPtr->race_id;
+ NumShips = LONIBBLE (EncounterMakeup[Type]);
+ for (i = HINIBBLE (EncounterMakeup[Type]) - NumShips; i; --i)
+ {
+ if ((COUNT)TFB_Random () % 100 < 50)
+ ++NumShips;
+ }
+
+ if (NumShips > MAX_HYPER_SHIPS)
+ NumShips = MAX_HYPER_SHIPS;
+
+ EncounterPtr->num_ships = NumShips;
+ for (i = 0; i < NumShips; ++i)
+ {
+ BRIEF_SHIP_INFO *BSIPtr = &EncounterPtr->ShipList[i];
+ HFLEETINFO hStarShip =
+ GetStarShipFromIndex (&GLOBAL (avail_race_q), Type);
+ FLEET_INFO *FleetPtr =
+ LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ BSIPtr->race_id = Type;
+ BSIPtr->crew_level = FleetPtr->crew_level;
+ BSIPtr->max_crew = FleetPtr->max_crew;
+ BSIPtr->max_energy = FleetPtr->max_energy;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+
+ do
+ {
+ DWORD rand_val;
+
+ rand_val = TFB_Random ();
+
+ enc_pt.x = puniverse->x
+ + (LOWORD (rand_val) % (XOFFS << 1)) - XOFFS;
+ if (enc_pt.x < 0)
+ enc_pt.x = 0;
+ else if (enc_pt.x > MAX_X_UNIVERSE)
+ enc_pt.x = MAX_X_UNIVERSE;
+ enc_pt.y = puniverse->y
+ + (HIWORD (rand_val) % (YOFFS << 1)) - YOFFS;
+ if (enc_pt.y < 0)
+ enc_pt.y = 0;
+ else if (enc_pt.y > MAX_Y_UNIVERSE)
+ enc_pt.y = MAX_Y_UNIVERSE;
+
+ dx = enc_pt.x - EncounterPtr->origin.x;
+ dy = enc_pt.y - EncounterPtr->origin.y;
+ } while ((DWORD)((long)dx * dx + (long)dy * dy) > radius_squared);
+
+ EncounterPtr->loc_pt = enc_pt;
+ EncounterPtr->log_x = UNIVERSE_TO_LOGX (enc_pt.x);
+ EncounterPtr->log_y = UNIVERSE_TO_LOGY (enc_pt.y);
+ }
+
+ hElement = AllocHyperElement (&enc_pt);
+ if (hElement)
+ {
+ SIZE i;
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+
+ i = EncounterPtr->transition_state;
+ if (i || NewEncounter)
+ {
+ if (i < 0)
+ {
+ i = -i;
+ ElementPtr->hit_points = 1;
+ }
+ if (i == 0 || i > NUM_VORTEX_TRANSITIONS)
+ i = NUM_VORTEX_TRANSITIONS;
+
+ ElementPtr->current.image.frame = SetRelFrameIndex (
+ ElementPtr->current.image.farray[0], -i);
+ ElementPtr->death_func = encounter_transition;
+ }
+ else
+ {
+ ElementPtr->current.image.frame =
+ DecFrameIndex (ElementPtr->current.image.farray[0]);
+ }
+
+ ElementPtr->turn_wait = VORTEX_WAIT;
+ ElementPtr->preprocess_func = NULL;
+ ElementPtr->postprocess_func = NULL;
+ ElementPtr->collision_func = encounter_collision;
+
+ SetUpElement (ElementPtr);
+
+ ElementPtr->IntersectControl.IntersectStamp.frame =
+ DecFrameIndex (stars_in_space);
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ ElementPtr->state_flags |= NONSOLID | IGNORE_VELOCITY;
+
+ UnlockElement (hElement);
+
+ InsertElement (hElement, GetTailElement ());
+ }
+
+ EncounterPtr->hElement = hElement;
+ return hElement;
+}
+
+#define GRID_OFFSET 200
+
+static void
+DrawHyperGrid (COORD ux, COORD uy, COORD ox, COORD oy)
+{
+ COORD sx, sy, ex, ey;
+ RECT r;
+
+ ClearDrawable ();
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x10, 0x00), 0x6B));
+
+ sx = ux - (RADAR_SCAN_WIDTH >> 1);
+ if (sx < 0)
+ sx = 0;
+ else
+ sx -= sx % GRID_OFFSET;
+ ex = ux + (RADAR_SCAN_WIDTH >> 1);
+ if (ex > MAX_X_UNIVERSE + 1)
+ ex = MAX_X_UNIVERSE + 1;
+
+ sy = uy - (RADAR_SCAN_HEIGHT >> 1);
+ if (sy < 0)
+ sy = 0;
+ else
+ sy -= sy % GRID_OFFSET;
+ ey = uy + (RADAR_SCAN_HEIGHT >> 1);
+ if (ey > MAX_Y_UNIVERSE + 1)
+ ey = MAX_Y_UNIVERSE + 1;
+
+ r.corner.y = (COORD) ((long)(MAX_Y_UNIVERSE - ey)
+ * RADAR_HEIGHT / RADAR_SCAN_HEIGHT) - oy;
+ r.extent.width = 1;
+ r.extent.height = ((COORD) ((long)(MAX_Y_UNIVERSE - sy)
+ * RADAR_HEIGHT / RADAR_SCAN_HEIGHT) - oy) - r.corner.y + 1;
+ for (ux = sx; ux <= ex; ux += GRID_OFFSET)
+ {
+ r.corner.x = (COORD) ((long)ux * RADAR_WIDTH / RADAR_SCAN_WIDTH) - ox;
+ DrawFilledRectangle (&r);
+ }
+
+ r.corner.x = (COORD) ((long)sx * RADAR_WIDTH / RADAR_SCAN_WIDTH) - ox;
+ r.extent.width = ((COORD) ((long)ex * RADAR_WIDTH / RADAR_SCAN_WIDTH)
+ - ox) - r.corner.x + 1;
+ r.extent.height = 1;
+ for (uy = sy; uy <= ey; uy += GRID_OFFSET)
+ {
+ r.corner.y = (COORD)((long)(MAX_Y_UNIVERSE - uy)
+ * RADAR_HEIGHT / RADAR_SCAN_HEIGHT) - oy;
+ DrawFilledRectangle (&r);
+ }
+}
+
+// Returns false iff the encounter is to be removed.
+static bool
+ProcessEncounter (ENCOUNTER *EncounterPtr, POINT *puniverse,
+ COORD ox, COORD oy, STAMP *stamp)
+{
+ ELEMENT *ElementPtr;
+ COORD ex, ey;
+
+ if (EncounterPtr->hElement == 0
+ && AddEncounterElement (EncounterPtr, puniverse) == 0)
+ return false;
+
+ LockElement (EncounterPtr->hElement, &ElementPtr);
+
+ if (ElementPtr->death_func)
+ {
+ if (EncounterPtr->transition_state && ElementPtr->turn_wait == 0)
+ {
+ --EncounterPtr->transition_state;
+ if (EncounterPtr->transition_state >= NUM_VORTEX_TRANSITIONS)
+ ++ElementPtr->turn_wait;
+ else if (EncounterPtr->transition_state ==
+ -NUM_VORTEX_TRANSITIONS)
+ {
+ ElementPtr->death_func = NULL;
+ UnlockElement (EncounterPtr->hElement);
+ return false;
+ }
+ else
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex],
+ STAMP_PRIM);
+ }
+ }
+ else
+ {
+ SIZE delta_x, delta_y;
+ COUNT encounter_radius;
+
+ ElementPtr->life_span = 1;
+ GetNextVelocityComponents (&ElementPtr->velocity,
+ &delta_x, &delta_y, 1);
+ if (ElementPtr->thrust_wait)
+ --ElementPtr->thrust_wait;
+ else if (!ElementPtr->hTarget)
+ { // This is an encounter that did not collide with flagship
+ // The colliding encounter does not move
+ COUNT cur_facing, delta_facing;
+
+ cur_facing = ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&ElementPtr->velocity));
+ delta_facing = NORMALIZE_FACING (cur_facing - ANGLE_TO_FACING (
+ ARCTAN (puniverse->x - EncounterPtr->loc_pt.x,
+ puniverse->y - EncounterPtr->loc_pt.y)));
+ if (delta_facing || (delta_x == 0 && delta_y == 0))
+ {
+ SIZE speed;
+ const SIZE RaceHyperSpeed[] =
+ {
+ RACE_HYPER_SPEED
+ };
+
+#define ENCOUNTER_TRACK_WAIT 3
+ speed = RaceHyperSpeed[EncounterPtr->race_id];
+ if (delta_facing < ANGLE_TO_FACING (HALF_CIRCLE))
+ --cur_facing;
+ else
+ ++cur_facing;
+ if (NORMALIZE_FACING (delta_facing + ANGLE_TO_FACING (OCTANT))
+ > ANGLE_TO_FACING (QUADRANT))
+ {
+ if (delta_facing < ANGLE_TO_FACING (HALF_CIRCLE))
+ --cur_facing;
+ else
+ ++cur_facing;
+ speed >>= 1;
+ }
+ cur_facing = FACING_TO_ANGLE (cur_facing);
+ SetVelocityComponents (&ElementPtr->velocity,
+ COSINE (cur_facing, speed), SINE (cur_facing, speed));
+ GetNextVelocityComponents (&ElementPtr->velocity,
+ &delta_x, &delta_y, 1);
+
+ ElementPtr->thrust_wait = ENCOUNTER_TRACK_WAIT;
+ }
+ }
+ EncounterPtr->log_x += delta_x;
+ EncounterPtr->log_y -= delta_y;
+ EncounterPtr->loc_pt.x = LOGX_TO_UNIVERSE (EncounterPtr->log_x);
+ EncounterPtr->loc_pt.y = LOGY_TO_UNIVERSE (EncounterPtr->log_y);
+
+ encounter_radius = EncounterPtr->radius + (GRID_OFFSET >> 1);
+ delta_x = EncounterPtr->loc_pt.x - EncounterPtr->origin.x;
+ if (delta_x < 0)
+ delta_x = -delta_x;
+ delta_y = EncounterPtr->loc_pt.y - EncounterPtr->origin.y;
+ if (delta_y < 0)
+ delta_y = -delta_y;
+ if ((COUNT)delta_x >= encounter_radius
+ || (COUNT)delta_y >= encounter_radius
+ || (DWORD)delta_x * delta_x + (DWORD)delta_y * delta_y >=
+ (DWORD)encounter_radius * encounter_radius)
+ {
+ // Encounter globe traveled outside the SoI and now disappears
+ ElementPtr->state_flags |= NONSOLID;
+ ElementPtr->life_span = 0;
+
+ if (EncounterPtr->transition_state == 0)
+ {
+ ElementPtr->death_func = encounter_transition;
+ EncounterPtr->transition_state = -1;
+ ElementPtr->hit_points = 1;
+ }
+ else
+ {
+ ElementPtr->death_func = NULL;
+ UnlockElement (EncounterPtr->hElement);
+ return false;
+ }
+ }
+ }
+
+ ex = EncounterPtr->loc_pt.x;
+ ey = EncounterPtr->loc_pt.y;
+ if (ex - puniverse->x >= -UNIT_SCREEN_WIDTH
+ && ex - puniverse->x <= UNIT_SCREEN_WIDTH
+ && ey - puniverse->y >= -UNIT_SCREEN_HEIGHT
+ && ey - puniverse->y <= UNIT_SCREEN_HEIGHT)
+ {
+ ElementPtr->next.location.x =
+ (SIZE)(EncounterPtr->log_x - GLOBAL_SIS (log_x))
+ + (LOG_SPACE_WIDTH >> 1);
+ ElementPtr->next.location.y =
+ (SIZE)(EncounterPtr->log_y - GLOBAL_SIS (log_y))
+ + (LOG_SPACE_HEIGHT >> 1);
+ if ((ElementPtr->state_flags & NONSOLID)
+ && EncounterPtr->transition_state == 0)
+ {
+ ElementPtr->current.location = ElementPtr->next.location;
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex],
+ STAMP_PRIM);
+ if (ElementPtr->death_func == 0)
+ {
+ InitIntersectStartPoint (ElementPtr);
+ ElementPtr->state_flags &= ~NONSOLID;
+ }
+ }
+ }
+ else
+ {
+ ElementPtr->state_flags |= NONSOLID;
+ if (ex - puniverse->x < -XOFFS || ex - puniverse->x > XOFFS
+ || ey - puniverse->y < -YOFFS || ey - puniverse->y > YOFFS)
+ {
+ ElementPtr->life_span = 0;
+ ElementPtr->death_func = NULL;
+ UnlockElement (EncounterPtr->hElement);
+ return false;
+ }
+
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ }
+
+ UnlockElement (EncounterPtr->hElement);
+
+ stamp->origin.x = (COORD)((long)ex * RADAR_WIDTH / RADAR_SCAN_WIDTH) - ox;
+ stamp->origin.y = (COORD)((long)(MAX_Y_UNIVERSE - ey) * RADAR_HEIGHT
+ / RADAR_SCAN_HEIGHT) - oy;
+ DrawStamp (stamp);
+
+ return true;
+}
+
+static void
+ProcessEncounters (POINT *puniverse, COORD ox, COORD oy)
+{
+ STAMP stamp;
+ HENCOUNTER hEncounter, hNextEncounter;
+
+ stamp.frame = SetAbsFrameIndex (stars_in_space, 91);
+ for (hEncounter = GetHeadEncounter ();
+ hEncounter; hEncounter = hNextEncounter)
+ {
+ ENCOUNTER *EncounterPtr;
+
+ LockEncounter (hEncounter, &EncounterPtr);
+ hNextEncounter = GetSuccEncounter (EncounterPtr);
+
+ if (!ProcessEncounter (EncounterPtr, puniverse, ox, oy, &stamp))
+ {
+ UnlockEncounter (hEncounter);
+ RemoveEncounter (hEncounter);
+ FreeEncounter (hEncounter);
+ continue;
+ }
+
+ UnlockEncounter (hEncounter);
+ }
+}
+
+void
+SeedUniverse (void)
+{
+ COORD ox, oy;
+ COORD sx, sy, ex, ey;
+ SWORD portalCounter, arilouSpaceCounter, arilouSpaceSide;
+ POINT universe;
+ FRAME blip_frame;
+ STAMP s;
+ STAR_DESC *SDPtr;
+ HELEMENT hHyperSpaceElement;
+ ELEMENT *HyperSpaceElementPtr;
+
+ universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+
+ blip_frame = SetAbsFrameIndex (stars_in_space, 90);
+
+ SetContext (RadarContext);
+ BatchGraphics ();
+
+ ox = (COORD)((long)universe.x * RADAR_WIDTH / RADAR_SCAN_WIDTH)
+ - (RADAR_WIDTH >> 1);
+ oy = (COORD)((long)(MAX_Y_UNIVERSE - universe.y)
+ * RADAR_HEIGHT / RADAR_SCAN_HEIGHT) - (RADAR_HEIGHT >> 1);
+
+ ex = (COORD)((long)GLOBAL (ShipStamp.origin.x)
+ * RADAR_WIDTH / RADAR_SCAN_WIDTH) - (RADAR_WIDTH >> 1);
+ ey = (COORD)((long)(MAX_Y_UNIVERSE - GLOBAL (ShipStamp.origin.y))
+ * RADAR_HEIGHT / RADAR_SCAN_HEIGHT) - (RADAR_HEIGHT >> 1);
+
+ arilouSpaceCounter = GET_GAME_STATE (ARILOU_SPACE_COUNTER);
+ arilouSpaceSide = GET_GAME_STATE (ARILOU_SPACE_SIDE);
+
+// if (ox != ex || oy != ey)
+ {
+ DrawHyperGrid (universe.x, universe.y, ox, oy);
+
+ {
+ SDPtr = 0;
+ while ((SDPtr = FindStar (SDPtr, &universe, XOFFS, YOFFS)))
+ {
+ BYTE star_type;
+
+ ex = SDPtr->star_pt.x;
+ ey = SDPtr->star_pt.y;
+ star_type = STAR_TYPE (SDPtr->Type);
+ if (arilouSpaceSide >= 2 &&
+ ex == ARILOU_HOME_X && ey == ARILOU_HOME_Y)
+ star_type = SUPER_GIANT_STAR;
+
+ s.origin.x = (COORD)((long)ex * RADAR_WIDTH
+ / RADAR_SCAN_WIDTH) - ox;
+ s.origin.y = (COORD)((long)(MAX_Y_UNIVERSE - ey)
+ * RADAR_HEIGHT / RADAR_SCAN_HEIGHT) - oy;
+ s.frame = SetRelFrameIndex (blip_frame,
+ star_type + 2);
+ DrawStamp (&s);
+ }
+ }
+ }
+
+ portalCounter = GET_GAME_STATE (PORTAL_COUNTER);
+ if (portalCounter || arilouSpaceCounter)
+ {
+ COUNT i;
+ STAR_DESC SD[2];
+ // This array is filled with the STAR_DESC's of
+ // QuasiSpace portals that need to be taken into account.
+ // i is set to the number of active portals (max 2).
+
+ i = 0;
+ if (portalCounter)
+ {
+ // A player-created QuasiSpace portal is opening.
+ static POINT portal_pt;
+
+ SD[i].Index = ((portalCounter - 1) >> 1) + 18;
+ if (portalCounter == 1)
+ portal_pt = universe;
+ SD[i].star_pt = portal_pt;
+ ++i;
+
+ if (++portalCounter == (10 + 1))
+ portalCounter = (9 + 1);
+
+ SET_GAME_STATE (PORTAL_COUNTER, portalCounter);
+ }
+
+ if (arilouSpaceCounter)
+ {
+ // The periodically appearing QuasiSpace portal is open.
+ SD[i].Index = arilouSpaceCounter >> 1;
+ if (arilouSpaceSide <= 1)
+ {
+ // The player is in HyperSpace
+ SD[i].Index += 18;
+ SD[i].star_pt.x = ARILOU_SPACE_X;
+ SD[i].star_pt.y = ARILOU_SPACE_Y;
+ }
+ else
+ {
+ // The player is in QuasiSpace
+ SD[i].star_pt.x = QUASI_SPACE_X;
+ SD[i].star_pt.y = QUASI_SPACE_Y;
+ }
+ ++i;
+ }
+
+ // Process the i portals from SD.
+ do
+ {
+ --i;
+ sx = SD[i].star_pt.x - universe.x + XOFFS;
+ sy = SD[i].star_pt.y - universe.y + YOFFS;
+ if (sx < 0 || sy < 0 || sx >= (XOFFS << 1) || sy >= (YOFFS << 1))
+ continue;
+
+ ex = SD[i].star_pt.x;
+ ey = SD[i].star_pt.y;
+ s.origin.x = (COORD)((long)ex * RADAR_WIDTH / RADAR_SCAN_WIDTH)
+ - ox;
+ s.origin.y = (COORD)((long)(MAX_Y_UNIVERSE - ey)
+ * RADAR_HEIGHT / RADAR_SCAN_HEIGHT) - oy;
+ s.frame = SetAbsFrameIndex (stars_in_space, 95);
+ DrawStamp (&s);
+
+ ex -= universe.x;
+ if (ex < 0)
+ ex = -ex;
+ ey -= universe.y;
+ if (ey < 0)
+ ey = -ey;
+
+ if (ex > (XOFFS / NUM_RADAR_SCREENS)
+ || ey > (YOFFS / NUM_RADAR_SCREENS))
+ continue;
+
+ hHyperSpaceElement = AllocHyperElement (&SD[i].star_pt);
+ if (hHyperSpaceElement == 0)
+ continue;
+
+ LockElement (hHyperSpaceElement, &HyperSpaceElementPtr);
+ HyperSpaceElementPtr->current.image.frame = SetAbsFrameIndex (
+ hyperstars[1 + (GET_GAME_STATE (ARILOU_SPACE_SIDE) >> 1)],
+ SD[i].Index);
+ HyperSpaceElementPtr->preprocess_func = NULL;
+ HyperSpaceElementPtr->postprocess_func = NULL;
+ HyperSpaceElementPtr->collision_func = arilou_space_collision;
+
+ SetUpElement (HyperSpaceElementPtr);
+
+ if (arilouSpaceSide == 1 || arilouSpaceSide == 2)
+ HyperSpaceElementPtr->death_func = arilou_space_death;
+ else
+ {
+ HyperSpaceElementPtr->death_func = NULL;
+ HyperSpaceElementPtr->IntersectControl.IntersectStamp.frame =
+ DecFrameIndex (stars_in_space);
+ }
+
+ UnlockElement (hHyperSpaceElement);
+
+ InsertElement (hHyperSpaceElement, GetHeadElement ());
+ } while (i);
+ }
+
+ {
+ SDPtr = 0;
+ while ((SDPtr = FindStar (SDPtr, &universe, XOFFS, YOFFS)))
+ {
+ BYTE star_type;
+
+ ex = SDPtr->star_pt.x - universe.x;
+ if (ex < 0)
+ ex = -ex;
+ ey = SDPtr->star_pt.y - universe.y;
+ if (ey < 0)
+ ey = -ey;
+ if (ex > (XOFFS / NUM_RADAR_SCREENS)
+ || ey > (YOFFS / NUM_RADAR_SCREENS))
+ continue;
+
+ hHyperSpaceElement = AllocHyperElement (&SDPtr->star_pt);
+ if (hHyperSpaceElement == 0)
+ continue;
+
+ star_type = SDPtr->Type;
+
+ LockElement (hHyperSpaceElement, &HyperSpaceElementPtr);
+ HyperSpaceElementPtr->current.image.frame = SetAbsFrameIndex (
+ hyperstars[1 + (GET_GAME_STATE (ARILOU_SPACE_SIDE) >> 1)],
+ STAR_TYPE (star_type) * NUM_STAR_COLORS
+ + STAR_COLOR (star_type));
+ HyperSpaceElementPtr->preprocess_func = NULL;
+ HyperSpaceElementPtr->postprocess_func = NULL;
+ HyperSpaceElementPtr->collision_func = hyper_collision;
+
+ SetUpElement (HyperSpaceElementPtr);
+
+ if (SDPtr == CurStarDescPtr
+ && GET_GAME_STATE (PORTAL_COUNTER) == 0)
+ HyperSpaceElementPtr->death_func = hyper_death;
+ else
+ {
+ HyperSpaceElementPtr->death_func = NULL;
+ HyperSpaceElementPtr->IntersectControl.IntersectStamp.frame =
+ DecFrameIndex (stars_in_space);
+ }
+ UnlockElement (hHyperSpaceElement);
+
+ InsertElement (hHyperSpaceElement, GetHeadElement ());
+ }
+ ProcessEncounters (&universe, ox, oy);
+ }
+
+ s.origin.x = RADAR_WIDTH >> 1;
+ s.origin.y = RADAR_HEIGHT >> 1;
+ s.frame = blip_frame;
+ DrawStamp (&s);
+
+ {
+ // draws borders to mini-map
+
+ RECT r;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0E, 0x0E, 0x0E), 0x00));
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = RADAR_WIDTH - 1;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.extent.width = 1;
+ r.extent.height = RADAR_HEIGHT - 1;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x06, 0x06, 0x06), 0x00));
+ r.corner.x = RADAR_WIDTH - 1;
+ r.corner.y = 1;
+ r.extent.height = RADAR_HEIGHT - 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = 1;
+ r.corner.y = RADAR_HEIGHT - 1;
+ r.extent.width = RADAR_WIDTH - 2;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x00));
+ r.corner.x = 0;
+ DrawPoint (&r.corner);
+ r.corner.x = RADAR_WIDTH - 1;
+ r.corner.y = 0;
+ DrawPoint (&r.corner);
+ }
+
+ UnbatchGraphics ();
+
+ SetContext (StatusContext);
+
+ if (!(LOWORD (TFB_Random ()) & 7))
+ AddAmbientElement ();
+
+ if (universe.x != GLOBAL (ShipStamp.origin.x)
+ || universe.y != GLOBAL (ShipStamp.origin.y))
+ {
+ GLOBAL (ShipStamp.origin) = universe;
+ DrawHyperCoords (universe);
+ }
+}
+
+static BOOLEAN
+DoHyperspaceMenu (MENU_STATE *pMS)
+{
+ BOOLEAN select = PulsedInputState.menu[KEY_MENU_SELECT];
+ BOOLEAN handled;
+
+ if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0)
+ return FALSE;
+
+ handled = DoMenuChooser (pMS, PM_STARMAP);
+ if (handled)
+ return TRUE;
+
+ if (!select)
+ return TRUE;
+
+ SetFlashRect (NULL);
+
+ switch (pMS->CurState)
+ {
+ case EQUIP_DEVICE:
+ select = DevicesMenu ();
+ if (GET_GAME_STATE (PORTAL_COUNTER))
+ { // A player-induced portal to QuasiSpace is opening
+ return FALSE;
+ }
+ if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ { // Selected Talking Pet, going into conversation
+ return FALSE;
+ }
+ break;
+ case CARGO:
+ CargoMenu ();
+ break;
+ case ROSTER:
+ select = RosterMenu ();
+ break;
+ case GAME_MENU:
+ if (!GameOptions ())
+ return FALSE; // abort or load
+ break;
+ case STARMAP:
+ StarMap ();
+ return FALSE;
+ case NAVIGATION:
+ return FALSE;
+ }
+
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ if (select)
+ { // 3DO menu jumps to NAVIGATE after a successful submenu run
+ if (optWhichMenu != OPT_PC)
+ pMS->CurState = NAVIGATION;
+ DrawMenuStateStrings (PM_STARMAP, pMS->CurState);
+ }
+ SetFlashRect (SFR_MENU_3DO);
+ }
+
+ return TRUE;
+}
+
+void
+HyperspaceMenu (void)
+{
+ Color OldColor;
+ CONTEXT OldContext;
+ MENU_STATE MenuState;
+
+UnbatchGraphics ();
+
+ OldContext = SetContext (SpaceContext);
+ OldColor = SetContextBackGroundColor (BLACK_COLOR);
+
+
+ memset (&MenuState, 0, sizeof (MenuState));
+ MenuState.InputFunc = DoHyperspaceMenu;
+ MenuState.Initialized = TRUE;
+ MenuState.CurState = STARMAP;
+
+ DrawMenuStateStrings (PM_STARMAP, STARMAP);
+ SetFlashRect (SFR_MENU_3DO);
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ DoInput (&MenuState, TRUE);
+
+ SetFlashRect (NULL);
+
+ SetContext (SpaceContext);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ ClearSISRect (CLEAR_SIS_RADAR);
+ WaitForNoInput (ONE_SECOND / 2, FALSE);
+ }
+
+ SetContextBackGroundColor (OldColor);
+ SetContext (OldContext);
+ if (!(GLOBAL (CurrentActivity) & IN_BATTLE))
+ cleanup_hyperspace ();
+
+BatchGraphics ();
+}
+
+void
+SaveSisHyperState (void)
+{
+ HELEMENT hSisElement;
+ ELEMENT *ElementPtr;
+ STARSHIP *StarShipPtr;
+
+ // Update 'GLOBAL (ShipFacing)' to the direction the flagship is facing
+ hSisElement = getSisElement ();
+ if (!hSisElement)
+ { // Happens when saving a game from Hyperspace encounter screen
+ return;
+ }
+ //if (ElementPtr->state_flags & PLAYER_SHIP)
+ LockElement (hSisElement, &ElementPtr);
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ // XXX: Solar system reentry test depends on ShipFacing != 0
+ GLOBAL (ShipFacing) = StarShipPtr->ShipFacing + 1;
+ UnlockElement (hSisElement);
+}
+
diff --git a/src/uqm/hyper.h b/src/uqm/hyper.h
new file mode 100644
index 0000000..b53c324
--- /dev/null
+++ b/src/uqm/hyper.h
@@ -0,0 +1,90 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_HYPER_H_
+#define UQM_HYPER_H_
+
+#include "element.h"
+#include "units.h"
+ // for UNIT_SCREEN_WIDTH/HEIGHT
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define NUM_RADAR_SCREENS 12
+
+#define RADAR_SCAN_WIDTH (UNIT_SCREEN_WIDTH * NUM_RADAR_SCREENS)
+#define RADAR_SCAN_HEIGHT (UNIT_SCREEN_HEIGHT * NUM_RADAR_SCREENS)
+
+// Hyperspace coordinates of the naturally occuring portal into QuasiSpace
+#define ARILOU_SPACE_X 438
+#define ARILOU_SPACE_Y 6372
+
+// QuasiSpace coordinates of the same portal
+#define QUASI_SPACE_X 5000
+#define QUASI_SPACE_Y 5000
+
+// QuasiSpace coordinates of the Arilou home world
+#define ARILOU_HOME_X (QUASI_SPACE_X + ((RADAR_SCAN_WIDTH >> 1) * 3))
+#define ARILOU_HOME_Y (QUASI_SPACE_Y + ((RADAR_SCAN_HEIGHT >> 1) * 3))
+
+// HyperSpace coordinates of the locations where the QuasiSpace portals
+// take you.
+#define QUASISPACE_PORTALS_HYPERSPACE_ENDPOINTS \
+ { \
+ {4091, 7748}, \
+ {3184, 4906}, \
+ {9211, 6104}, \
+ {5673, 1207}, \
+ {1910, 926}, \
+ {8607, 151}, \
+ { 50, 1647}, \
+ {6117, 4131}, \
+ {5658, 9712}, \
+ {2302, 3988}, \
+ { 112, 9409}, \
+ {7752, 8906}, \
+ { 368, 6332}, \
+ {9735, 3153}, \
+ {5850, 6213}, \
+ }
+
+// Hyperspace coordinates of the Sol system
+// Should be the same as in plandata.c
+#define SOL_X 1752
+#define SOL_Y 1450
+
+
+extern BOOLEAN LoadHyperspace (void);
+extern BOOLEAN FreeHyperspace (void);
+extern void SeedUniverse (void);
+extern void MoveSIS (SIZE *pdx, SIZE *pdy);
+
+extern void FreeHyperData (void);
+extern void check_hyperspace_encounter (void);
+extern BOOLEAN hyper_transition (ELEMENT *ElementPtr);
+
+extern void HyperspaceMenu (void);
+extern void SaveSisHyperState (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_HYPER_H_ */
diff --git a/src/uqm/ifontres.h b/src/uqm/ifontres.h
new file mode 100644
index 0000000..4d9f0a8
--- /dev/null
+++ b/src/uqm/ifontres.h
@@ -0,0 +1,12 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define LANDER_FONT "font.lander"
+#define MICRO_FONT "font.micro"
+#define PLAYER_FONT "font.player"
+#define PT13AA_FONT "credits.font.pt13"
+#define PT17AA_FONT "credits.font.pt17"
+#define PT45AA_FONT "credits.font.pt45"
+#define STARCON_FONT "font.starcon"
+#define TINY_FONT "font.tiny"
diff --git a/src/uqm/igfxres.h b/src/uqm/igfxres.h
new file mode 100644
index 0000000..e31726d
--- /dev/null
+++ b/src/uqm/igfxres.h
@@ -0,0 +1,274 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ACTIVITY_ANIM "graphics.activity"
+#define AMBIENT_MASK_PMAP_ANIM "graphics.ambient"
+#define AQUA_MASK_PMAP_ANIM "graphics.aquahelix"
+#define ARISPACE_MASK_PMAP_ANIM "graphics.quasispace"
+#define ASTEROID_BIG_MASK_PMAP_ANIM "graphics.asteroid.large"
+#define ASTEROID_MED_MASK_PMAP_ANIM "graphics.asteroid.medium"
+#define ASTEROID_SML_MASK_PMAP_ANIM "graphics.asteroid.small"
+#define BLAST_BIG_MASK_PMAP_ANIM "graphics.blast.large"
+#define BLAST_MED_MASK_PMAP_ANIM "graphics.blast.medium"
+#define BLAST_SML_MASK_PMAP_ANIM "graphics.blast.small"
+#define BOMB_MASK_PMAP_ANIM "graphics.utwigbomb"
+#define BOOM_BIG_MASK_PMAP_ANIM "graphics.boom.large"
+#define BOOM_MED_MASK_PMAP_ANIM "graphics.boom.medium"
+#define BOOM_SML_MASK_PMAP_ANIM "graphics.boom.small"
+#define BURV_BCS_MASK_PMAP_ANIM "graphics.burvixcaster"
+#define CANNISTER_MASK_PMAP_ANIM "graphics.lifecan"
+#define CREDITS_BACK_ANIM "credits.background"
+#define EARTH_MASK_ANIM "graphics.earthmask"
+#define EGG_CASE_MASK_PMAP_ANIM "graphics.eggcase"
+#define FLAGSTAT_MASK_PMAP_ANIM "graphics.flagshipstatus"
+#define FONTGRAD_PMAP_ANIM "graphics.fontgradient"
+#define HYPERSTARS_MASK_PMAP_ANIM "graphics.hyperstars"
+#define IPBKGND_MASK_PMAP_ANIM "graphics.orbitbackground"
+#define LANDER_FONTEFF_PMAP_ANIM "graphics.landerfonteffect"
+#define LANDER_LAUNCH_MASK_PMAP_ANIM "graphics.landerlaunch"
+#define LANDER_MASK_PMAP_ANIM "graphics.lander"
+#define LANDER_RETURN_MASK_PMAP_ANIM "graphics.landerreturn"
+#define LANDER_SHIELD_MASK_ANIM "graphics.landershield"
+#define LAVA_MASK_PMAP_ANIM "graphics.lavaspot"
+#define LIFE00_MASK_PMAP_ANIM "graphics.life.0"
+#define LIFE01_MASK_PMAP_ANIM "graphics.life.1"
+#define LIFE02_MASK_PMAP_ANIM "graphics.life.2"
+#define LIFE03_MASK_PMAP_ANIM "graphics.life.3"
+#define LIFE04_MASK_PMAP_ANIM "graphics.life.4"
+#define LIFE05_MASK_PMAP_ANIM "graphics.life.5"
+#define LIFE06_MASK_PMAP_ANIM "graphics.life.6"
+#define LIFE07_MASK_PMAP_ANIM "graphics.life.7"
+#define LIFE08_MASK_PMAP_ANIM "graphics.life.8"
+#define LIFE09_MASK_PMAP_ANIM "graphics.life.9"
+#define LIFE10_MASK_PMAP_ANIM "graphics.life.10"
+#define LIFE11_MASK_PMAP_ANIM "graphics.life.11"
+#define LIFE12_MASK_PMAP_ANIM "graphics.life.12"
+#define LIFE13_MASK_PMAP_ANIM "graphics.life.13"
+#define LIFE14_MASK_PMAP_ANIM "graphics.life.14"
+#define LIFE15_MASK_PMAP_ANIM "graphics.life.15"
+#define LIFE16_MASK_PMAP_ANIM "graphics.life.16"
+#define LIFE17_MASK_PMAP_ANIM "graphics.life.17"
+#define LIFE18_MASK_PMAP_ANIM "graphics.life.18"
+#define LIFE19_MASK_PMAP_ANIM "graphics.life.19"
+#define LIFE20_MASK_PMAP_ANIM "graphics.life.20"
+#define LIFE21_MASK_PMAP_ANIM "graphics.life.21"
+#define LIFE22_MASK_PMAP_ANIM "graphics.life.22"
+#define LIFE23_MASK_PMAP_ANIM "graphics.life.23"
+#define LIFE24_MASK_PMAP_ANIM "graphics.life.24"
+#define LIFE25_MASK_PMAP_ANIM "graphics.life.25"
+#define LIGHTNING_MASK_ANIM "graphics.lightning"
+#define MAIDENS_MASK_PMAP_ANIM "graphics.maidens"
+#define MELEE_PICK_MASK_PMAP_ANIM "graphics.meleepickship"
+#define MELEE_SCREEN_PMAP_ANIM "graphics.meleemenu"
+#define MENUBKG_PMAP_ANIM "graphics.setupmenu"
+#define MISCDATA_MASK_PMAP_ANIM "graphics.miscdata"
+#define MODULES_PMAP_ANIM "graphics.modulesmenu"
+#define MOONBASE_MASK_PMAP_ANIM "graphics.moonbase"
+#define ORBENTER_PMAP_ANIM "graphics.orbitenter"
+#define ORBIT_VIEW_ANIM "graphics.orbview"
+#define ORBPLAN_MASK_PMAP_ANIM "graphics.planets"
+#define OUTFIT_PMAP_ANIM "graphics.outfit"
+#define PLANET00_BIG_MASK_PMAP_ANIM "planet.oolite.large"
+#define PLANET00_MED_MASK_PMAP_ANIM "planet.oolite.medium"
+#define PLANET00_SML_MASK_PMAP_ANIM "planet.oolite.small"
+#define PLANET01_BIG_MASK_PMAP_ANIM "planet.yttric.large"
+#define PLANET01_MED_MASK_PMAP_ANIM "planet.yttric.medium"
+#define PLANET01_SML_MASK_PMAP_ANIM "planet.yttric.small"
+#define PLANET02_BIG_MASK_PMAP_ANIM "planet.quasidegenerate.large"
+#define PLANET02_MED_MASK_PMAP_ANIM "planet.quasidegenerate.medium"
+#define PLANET02_SML_MASK_PMAP_ANIM "planet.quasidegenerate.small"
+#define PLANET03_BIG_MASK_PMAP_ANIM "planet.lanthanide.large"
+#define PLANET03_MED_MASK_PMAP_ANIM "planet.lanthanide.medium"
+#define PLANET03_SML_MASK_PMAP_ANIM "planet.lanthanide.small"
+#define PLANET04_BIG_MASK_PMAP_ANIM "planet.treasure.large"
+#define PLANET04_MED_MASK_PMAP_ANIM "planet.treasure.medium"
+#define PLANET04_SML_MASK_PMAP_ANIM "planet.treasure.small"
+#define PLANET05_BIG_MASK_PMAP_ANIM "planet.urea.large"
+#define PLANET05_MED_MASK_PMAP_ANIM "planet.urea.medium"
+#define PLANET05_SML_MASK_PMAP_ANIM "planet.urea.small"
+#define PLANET06_BIG_MASK_PMAP_ANIM "planet.metal.large"
+#define PLANET06_MED_MASK_PMAP_ANIM "planet.metal.medium"
+#define PLANET06_SML_MASK_PMAP_ANIM "planet.metal.small"
+#define PLANET07_BIG_MASK_PMAP_ANIM "planet.radioactive.large"
+#define PLANET07_MED_MASK_PMAP_ANIM "planet.radioactive.medium"
+#define PLANET07_SML_MASK_PMAP_ANIM "planet.radioactive.small"
+#define PLANET08_BIG_MASK_PMAP_ANIM "planet.opalescent.large"
+#define PLANET08_MED_MASK_PMAP_ANIM "planet.opalescent.medium"
+#define PLANET08_SML_MASK_PMAP_ANIM "planet.opalescent.small"
+#define PLANET09_BIG_MASK_PMAP_ANIM "planet.cyanic.large"
+#define PLANET09_MED_MASK_PMAP_ANIM "planet.cyanic.medium"
+#define PLANET09_SML_MASK_PMAP_ANIM "planet.cyanic.small"
+#define PLANET10_BIG_MASK_PMAP_ANIM "planet.acid.large"
+#define PLANET10_MED_MASK_PMAP_ANIM "planet.acid.medium"
+#define PLANET10_SML_MASK_PMAP_ANIM "planet.acid.small"
+#define PLANET11_BIG_MASK_PMAP_ANIM "planet.alkali.large"
+#define PLANET11_MED_MASK_PMAP_ANIM "planet.alkali.medium"
+#define PLANET11_SML_MASK_PMAP_ANIM "planet.alkali.small"
+#define PLANET12_BIG_MASK_PMAP_ANIM "planet.halide.large"
+#define PLANET12_MED_MASK_PMAP_ANIM "planet.halide.medium"
+#define PLANET12_SML_MASK_PMAP_ANIM "planet.halide.small"
+#define PLANET13_BIG_MASK_PMAP_ANIM "planet.green.large"
+#define PLANET13_MED_MASK_PMAP_ANIM "planet.green.medium"
+#define PLANET13_SML_MASK_PMAP_ANIM "planet.green.small"
+#define PLANET14_BIG_MASK_PMAP_ANIM "planet.copper.large"
+#define PLANET14_MED_MASK_PMAP_ANIM "planet.copper.medium"
+#define PLANET14_SML_MASK_PMAP_ANIM "planet.copper.small"
+#define PLANET15_BIG_MASK_PMAP_ANIM "planet.carbide.large"
+#define PLANET15_MED_MASK_PMAP_ANIM "planet.carbide.medium"
+#define PLANET15_SML_MASK_PMAP_ANIM "planet.carbide.small"
+#define PLANET16_BIG_MASK_PMAP_ANIM "planet.ultramarine.large"
+#define PLANET16_MED_MASK_PMAP_ANIM "planet.ultramarine.medium"
+#define PLANET16_SML_MASK_PMAP_ANIM "planet.ultramarine.small"
+#define PLANET17_BIG_MASK_PMAP_ANIM "planet.noble.large"
+#define PLANET17_MED_MASK_PMAP_ANIM "planet.noble.medium"
+#define PLANET17_SML_MASK_PMAP_ANIM "planet.noble.small"
+#define PLANET18_BIG_MASK_PMAP_ANIM "planet.azure.large"
+#define PLANET18_MED_MASK_PMAP_ANIM "planet.azure.medium"
+#define PLANET18_SML_MASK_PMAP_ANIM "planet.azure.small"
+#define PLANET19_BIG_MASK_PMAP_ANIM "planet.chondrite.large"
+#define PLANET19_MED_MASK_PMAP_ANIM "planet.chondrite.medium"
+#define PLANET19_SML_MASK_PMAP_ANIM "planet.chondrite.small"
+#define PLANET20_BIG_MASK_PMAP_ANIM "planet.purple.large"
+#define PLANET20_MED_MASK_PMAP_ANIM "planet.purple.medium"
+#define PLANET20_SML_MASK_PMAP_ANIM "planet.purple.small"
+#define PLANET21_BIG_MASK_PMAP_ANIM "planet.superdense.large"
+#define PLANET21_MED_MASK_PMAP_ANIM "planet.superdense.medium"
+#define PLANET21_SML_MASK_PMAP_ANIM "planet.superdense.small"
+#define PLANET22_BIG_MASK_PMAP_ANIM "planet.pellucid.large"
+#define PLANET22_MED_MASK_PMAP_ANIM "planet.pellucid.medium"
+#define PLANET22_SML_MASK_PMAP_ANIM "planet.pellucid.small"
+#define PLANET23_BIG_MASK_PMAP_ANIM "planet.dust.large"
+#define PLANET23_MED_MASK_PMAP_ANIM "planet.dust.medium"
+#define PLANET23_SML_MASK_PMAP_ANIM "planet.dust.small"
+#define PLANET24_BIG_MASK_PMAP_ANIM "planet.crimson.large"
+#define PLANET24_MED_MASK_PMAP_ANIM "planet.crimson.medium"
+#define PLANET24_SML_MASK_PMAP_ANIM "planet.crimson.small"
+#define PLANET25_BIG_MASK_PMAP_ANIM "planet.cimmerian.large"
+#define PLANET25_MED_MASK_PMAP_ANIM "planet.cimmerian.medium"
+#define PLANET25_SML_MASK_PMAP_ANIM "planet.cimmerian.small"
+#define PLANET26_BIG_MASK_PMAP_ANIM "planet.infrared.large"
+#define PLANET26_MED_MASK_PMAP_ANIM "planet.infrared.medium"
+#define PLANET26_SML_MASK_PMAP_ANIM "planet.infrared.small"
+#define PLANET27_BIG_MASK_PMAP_ANIM "planet.selenic.large"
+#define PLANET27_MED_MASK_PMAP_ANIM "planet.selenic.medium"
+#define PLANET27_SML_MASK_PMAP_ANIM "planet.selenic.small"
+#define PLANET28_BIG_MASK_PMAP_ANIM "planet.auric.large"
+#define PLANET28_MED_MASK_PMAP_ANIM "planet.auric.medium"
+#define PLANET28_SML_MASK_PMAP_ANIM "planet.auric.small"
+#define PLANET29_BIG_MASK_PMAP_ANIM "planet.fluorescent.large"
+#define PLANET29_MED_MASK_PMAP_ANIM "planet.fluorescent.medium"
+#define PLANET29_SML_MASK_PMAP_ANIM "planet.fluorescent.small"
+#define PLANET30_BIG_MASK_PMAP_ANIM "planet.ultraviolet.large"
+#define PLANET30_MED_MASK_PMAP_ANIM "planet.ultraviolet.medium"
+#define PLANET30_SML_MASK_PMAP_ANIM "planet.ultraviolet.small"
+#define PLANET31_BIG_MASK_PMAP_ANIM "planet.plutonic.large"
+#define PLANET31_MED_MASK_PMAP_ANIM "planet.plutonic.medium"
+#define PLANET31_SML_MASK_PMAP_ANIM "planet.plutonic.small"
+#define PLANET32_BIG_MASK_PMAP_ANIM "planet.rainbow.large"
+#define PLANET32_MED_MASK_PMAP_ANIM "planet.rainbow.medium"
+#define PLANET32_SML_MASK_PMAP_ANIM "planet.rainbow.small"
+#define PLANET33_BIG_MASK_PMAP_ANIM "planet.shattered.large"
+#define PLANET33_MED_MASK_PMAP_ANIM "planet.shattered.medium"
+#define PLANET33_SML_MASK_PMAP_ANIM "planet.shattered.small"
+#define PLANET34_BIG_MASK_PMAP_ANIM "planet.sapphire.large"
+#define PLANET34_MED_MASK_PMAP_ANIM "planet.sapphire.medium"
+#define PLANET34_SML_MASK_PMAP_ANIM "planet.sapphire.small"
+#define PLANET35_BIG_MASK_PMAP_ANIM "planet.organic.large"
+#define PLANET35_MED_MASK_PMAP_ANIM "planet.organic.medium"
+#define PLANET35_SML_MASK_PMAP_ANIM "planet.organic.small"
+#define PLANET36_BIG_MASK_PMAP_ANIM "planet.xenolithic.large"
+#define PLANET36_MED_MASK_PMAP_ANIM "planet.xenolithic.medium"
+#define PLANET36_SML_MASK_PMAP_ANIM "planet.xenolithic.small"
+#define PLANET37_BIG_MASK_PMAP_ANIM "planet.redux.large"
+#define PLANET37_MED_MASK_PMAP_ANIM "planet.redux.medium"
+#define PLANET37_SML_MASK_PMAP_ANIM "planet.redux.small"
+#define PLANET38_BIG_MASK_PMAP_ANIM "planet.primordial.large"
+#define PLANET38_MED_MASK_PMAP_ANIM "planet.primordial.medium"
+#define PLANET38_SML_MASK_PMAP_ANIM "planet.primordial.small"
+#define PLANET39_BIG_MASK_PMAP_ANIM "planet.emerald.large"
+#define PLANET39_MED_MASK_PMAP_ANIM "planet.emerald.medium"
+#define PLANET39_SML_MASK_PMAP_ANIM "planet.emerald.small"
+#define PLANET40_BIG_MASK_PMAP_ANIM "planet.chlorine.large"
+#define PLANET40_MED_MASK_PMAP_ANIM "planet.chlorine.medium"
+#define PLANET40_SML_MASK_PMAP_ANIM "planet.chlorine.small"
+#define PLANET41_BIG_MASK_PMAP_ANIM "planet.magnetic.large"
+#define PLANET41_MED_MASK_PMAP_ANIM "planet.magnetic.medium"
+#define PLANET41_SML_MASK_PMAP_ANIM "planet.magnetic.small"
+#define PLANET42_BIG_MASK_PMAP_ANIM "planet.water.large"
+#define PLANET42_MED_MASK_PMAP_ANIM "planet.water.medium"
+#define PLANET42_SML_MASK_PMAP_ANIM "planet.water.small"
+#define PLANET43_BIG_MASK_PMAP_ANIM "planet.telluric.large"
+#define PLANET43_MED_MASK_PMAP_ANIM "planet.telluric.medium"
+#define PLANET43_SML_MASK_PMAP_ANIM "planet.telluric.small"
+#define PLANET44_BIG_MASK_PMAP_ANIM "planet.hydrocarbon.large"
+#define PLANET44_MED_MASK_PMAP_ANIM "planet.hydrocarbon.medium"
+#define PLANET44_SML_MASK_PMAP_ANIM "planet.hydrocarbon.small"
+#define PLANET45_BIG_MASK_PMAP_ANIM "planet.iodine.large"
+#define PLANET45_MED_MASK_PMAP_ANIM "planet.iodine.medium"
+#define PLANET45_SML_MASK_PMAP_ANIM "planet.iodine.small"
+#define PLANET46_BIG_MASK_PMAP_ANIM "planet.vinylogous.large"
+#define PLANET46_MED_MASK_PMAP_ANIM "planet.vinylogous.medium"
+#define PLANET46_SML_MASK_PMAP_ANIM "planet.vinylogous.small"
+#define PLANET47_BIG_MASK_PMAP_ANIM "planet.ruby.large"
+#define PLANET47_MED_MASK_PMAP_ANIM "planet.ruby.medium"
+#define PLANET47_SML_MASK_PMAP_ANIM "planet.ruby.small"
+#define PLANET48_BIG_MASK_PMAP_ANIM "planet.magma.large"
+#define PLANET48_MED_MASK_PMAP_ANIM "planet.magma.medium"
+#define PLANET48_SML_MASK_PMAP_ANIM "planet.magma.small"
+#define PLANET49_BIG_MASK_PMAP_ANIM "planet.maroon.large"
+#define PLANET49_MED_MASK_PMAP_ANIM "planet.maroon.medium"
+#define PLANET49_SML_MASK_PMAP_ANIM "planet.maroon.small"
+#define PLANET50_BIG_MASK_PMAP_ANIM "planet.bluegas.large"
+#define PLANET50_MED_MASK_PMAP_ANIM "planet.bluegas.medium"
+#define PLANET50_SML_MASK_PMAP_ANIM "planet.bluegas.small"
+#define PLANET51_BIG_MASK_PMAP_ANIM "planet.cyangas.large"
+#define PLANET51_MED_MASK_PMAP_ANIM "planet.cyangas.medium"
+#define PLANET51_SML_MASK_PMAP_ANIM "planet.cyangas.small"
+#define PLANET52_BIG_MASK_PMAP_ANIM "planet.greengas.large"
+#define PLANET52_MED_MASK_PMAP_ANIM "planet.greengas.medium"
+#define PLANET52_SML_MASK_PMAP_ANIM "planet.greengas.small"
+#define PLANET53_BIG_MASK_PMAP_ANIM "planet.greygas.large"
+#define PLANET53_MED_MASK_PMAP_ANIM "planet.greygas.medium"
+#define PLANET53_SML_MASK_PMAP_ANIM "planet.greygas.small"
+#define PLANET54_BIG_MASK_PMAP_ANIM "planet.orangegas.large"
+#define PLANET54_MED_MASK_PMAP_ANIM "planet.orangegas.medium"
+#define PLANET54_SML_MASK_PMAP_ANIM "planet.orangegas.small"
+#define PLANET55_BIG_MASK_PMAP_ANIM "planet.purplegas.large"
+#define PLANET55_MED_MASK_PMAP_ANIM "planet.purplegas.medium"
+#define PLANET55_SML_MASK_PMAP_ANIM "planet.purplegas.small"
+#define PLANET56_BIG_MASK_PMAP_ANIM "planet.redgas.large"
+#define PLANET56_MED_MASK_PMAP_ANIM "planet.redgas.medium"
+#define PLANET56_SML_MASK_PMAP_ANIM "planet.redgas.small"
+#define PLANET57_BIG_MASK_PMAP_ANIM "planet.violetgas.large"
+#define PLANET57_MED_MASK_PMAP_ANIM "planet.violetgas.medium"
+#define PLANET57_SML_MASK_PMAP_ANIM "planet.violetgas.small"
+#define PLANET58_BIG_MASK_PMAP_ANIM "planet.yellowgas.large"
+#define PLANET58_MED_MASK_PMAP_ANIM "planet.yellowgas.medium"
+#define PLANET58_SML_MASK_PMAP_ANIM "planet.yellowgas.small"
+#define PLAYMENU_ANIM "graphics.playmenu"
+#define QUAKE_MASK_PMAP_ANIM "graphics.quake"
+#define RESTART_PMAP_ANIM "graphics.newgame"
+#define RUINS_MASK_PMAP_ANIM "graphics.ruins"
+#define SAMATRA_BIG_MASK_PMAP_ANIM "planet.samatra.large"
+#define SC2_PICK_PMAP_ANIM "graphics.pickship"
+#define SEGUE_PMAP_ANIM "graphics.segue"
+#define SHIELDED_BIG_MASK_PMAP_ANIM "planet.slaveshield.large"
+#define SHIELDED_MED_MASK_PMAP_ANIM "planet.slaveshield.medium"
+#define SHIELDED_SML_MASK_PMAP_ANIM "planet.slaveshield.small"
+#define SHIPYARD_PMAP_ANIM "graphics.shipyard"
+#define SISBLU_MASK_ANIM "graphics.blueprints"
+#define SISIP_MASK_PMAP_ANIM "graphics.flagship"
+#define SISMODS_MASK_PMAP_ANIM "graphics.outfitmodules"
+#define SISSKEL_MASK_PMAP_ANIM "graphics.flagshipskeleton"
+#define SPAPLUTO_MASK_PMAP_ANIM "graphics.fwiffo"
+#define STARBASE_ANIM "graphics.starbase"
+#define STAR_MASK_PMAP_ANIM "graphics.stars"
+#define STATUS_MASK_PMAP_ANIM "graphics.status"
+#define SUN_DEVICE_MASK_PMAP_ANIM "graphics.sundevice"
+#define SUN_MASK_PMAP_ANIM "graphics.truespacesun"
+#define TAALO_DEVICE_MASK_PMAP_ANIM "graphics.taalodevice"
+#define TITLE_ANIM "graphics.title"
+#define UMGAH_BCS_MASK_PMAP_ANIM "graphics.umgahcaster"
+#define VAULT_MASK_PMAP_ANIM "graphics.syreenvault"
+#define WRECK_MASK_PMAP_ANIM "graphics.urquanwreck"
diff --git a/src/uqm/ikey_con.h b/src/uqm/ikey_con.h
new file mode 100644
index 0000000..e780e2c
--- /dev/null
+++ b/src/uqm/ikey_con.h
@@ -0,0 +1,2 @@
+// This is a dead resource file.
+
diff --git a/src/uqm/imusicre.h b/src/uqm/imusicre.h
new file mode 100644
index 0000000..87d3ca4
--- /dev/null
+++ b/src/uqm/imusicre.h
@@ -0,0 +1,20 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define BATTLE_MUSIC "music.battle"
+#define CREDITS_MUSIC "music.credits"
+#define HYPERSPACE_MUSIC "music.hyperspace"
+#define IP_MUSIC "music.space"
+#define MAINMENU_MUSIC "music.mainmenu"
+#define MELEE_MUSIC "music.meleemenu"
+#define ORBIT1_MUSIC "music.orbit1"
+#define ORBIT2_MUSIC "music.orbit2"
+#define ORBIT3_MUSIC "music.orbit3"
+#define ORBIT4_MUSIC "music.orbit4"
+#define ORBIT5_MUSIC "music.orbit5"
+#define OUTFIT_MUSIC "music.outfit"
+#define QUASISPACE_MUSIC "music.quasispace"
+#define REDALERT_MUSIC "music.redalert"
+#define SHIPYARD_MUSIC "music.shipyard"
+#define STARBASE_MUSIC "music.starbase"
diff --git a/src/uqm/init.c b/src/uqm/init.c
new file mode 100644
index 0000000..7831d08
--- /dev/null
+++ b/src/uqm/init.c
@@ -0,0 +1,351 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "build.h"
+#include "colors.h"
+#include "cons_res.h"
+#include "races.h"
+#include "element.h"
+#include "tactrans.h"
+#include "pickship.h"
+#include "process.h"
+#include "globdata.h"
+#include "encount.h"
+#include "hyper.h"
+#include "init.h"
+#include "port.h"
+#include "resinst.h"
+#include "libs/reslib.h"
+#include "nameref.h"
+#include "setup.h"
+#include "units.h"
+
+
+FRAME stars_in_space;
+FRAME asteroid[NUM_VIEWS];
+FRAME blast[NUM_VIEWS];
+FRAME explosion[NUM_VIEWS];
+
+
+BOOLEAN
+load_animation (FRAME *pixarray, RESOURCE big_res, RESOURCE med_res, RESOURCE
+ sml_res)
+{
+ DRAWABLE d;
+
+ d = LoadGraphic (big_res);
+ if (!d)
+ return FALSE;
+ pixarray[0] = CaptureDrawable (d);
+
+ if (med_res != NULL_RESOURCE)
+ {
+ d = LoadGraphic (med_res);
+ if (!d)
+ return FALSE;
+ }
+ pixarray[1] = CaptureDrawable (d);
+
+ if (sml_res != NULL_RESOURCE)
+ {
+ d = LoadGraphic (sml_res);
+ if (!d)
+ return FALSE;
+ }
+ pixarray[2] = CaptureDrawable (d);
+
+ return TRUE;
+}
+
+/* Warning: Some ships (such as the Umgah) will alias their pixarrays,
+ so we need to track to make sure that we do not double-free. */
+BOOLEAN
+free_image (FRAME *pixarray)
+{
+ BOOLEAN retval;
+ COUNT i, j;
+ void *already_freed[NUM_VIEWS];
+
+ retval = TRUE;
+ for (i = 0; i < NUM_VIEWS; ++i)
+ {
+ if (pixarray[i] != NULL)
+ {
+ BOOLEAN ok = TRUE;
+ for (j = 0; j < i; j++)
+ {
+ if (already_freed[j] == pixarray[i])
+ {
+ ok = FALSE;
+ break;
+ }
+ }
+ if (ok)
+ {
+ if (!DestroyDrawable (ReleaseDrawable (pixarray[i])))
+ retval = FALSE;
+ }
+ already_freed[i] = pixarray[i];
+ pixarray[i] = NULL;
+ }
+ }
+
+ return (retval);
+}
+
+static BYTE space_ini_cnt;
+
+BOOLEAN
+InitSpace (void)
+{
+ if (space_ini_cnt++ == 0
+ && LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER)
+ {
+ stars_in_space = CaptureDrawable (
+ LoadGraphic (STAR_MASK_PMAP_ANIM));
+ if (stars_in_space == NULL)
+ return FALSE;
+
+ if (!load_animation (explosion,
+ BOOM_BIG_MASK_PMAP_ANIM,
+ BOOM_MED_MASK_PMAP_ANIM,
+ BOOM_SML_MASK_PMAP_ANIM))
+ return FALSE;
+
+ if (!load_animation (blast,
+ BLAST_BIG_MASK_PMAP_ANIM,
+ BLAST_MED_MASK_PMAP_ANIM,
+ BLAST_SML_MASK_PMAP_ANIM))
+ return FALSE;
+
+ if (!load_animation (asteroid,
+ ASTEROID_BIG_MASK_PMAP_ANIM,
+ ASTEROID_MED_MASK_PMAP_ANIM,
+ ASTEROID_SML_MASK_PMAP_ANIM))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+UninitSpace (void)
+{
+ if (space_ini_cnt && --space_ini_cnt == 0)
+ {
+ free_image (blast);
+ free_image (explosion);
+ free_image (asteroid);
+
+ DestroyDrawable (ReleaseDrawable (stars_in_space));
+ stars_in_space = 0;
+ }
+}
+
+static HSTARSHIP
+BuildSIS (void)
+{
+ HSTARSHIP hStarShip;
+ STARSHIP *StarShipPtr;
+
+ hStarShip = Build (&race_q[0], SIS_SHIP_ID);
+ if (!hStarShip)
+ return 0;
+ StarShipPtr = LockStarShip (&race_q[0], hStarShip);
+ StarShipPtr->playerNr = RPG_PLAYER_NUM;
+ StarShipPtr->captains_name_index = 0;
+ UnlockStarShip (&race_q[0], hStarShip);
+
+ return hStarShip;
+}
+
+SIZE
+InitShips (void)
+{
+ SIZE num_ships;
+
+ InitSpace ();
+
+ SetContext (StatusContext);
+ SetContext (SpaceContext);
+
+ InitDisplayList ();
+ InitGalaxy ();
+
+ if (inHQSpace ())
+ {
+ ReinitQueue (&race_q[0]);
+ ReinitQueue (&race_q[1]);
+
+ BuildSIS ();
+ LoadHyperspace ();
+
+ num_ships = 1;
+ }
+ else
+ {
+ COUNT i;
+ RECT r;
+
+ SetContextFGFrame (Screen);
+ r.corner.x = SAFE_X;
+ r.corner.y = SAFE_Y;
+ r.extent.width = SPACE_WIDTH;
+ r.extent.height = SPACE_HEIGHT;
+ SetContextClipRect (&r);
+
+ SetContextBackGroundColor (BLACK_COLOR);
+ {
+ CONTEXT OldContext;
+
+ OldContext = SetContext (ScreenContext);
+
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+
+ SetContext (OldContext);
+ }
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ free_gravity_well ();
+ else
+ {
+#define NUM_ASTEROIDS 5
+ for (i = 0; i < NUM_ASTEROIDS; ++i)
+ spawn_asteroid (NULL);
+#define NUM_PLANETS 1
+ for (i = 0; i < NUM_PLANETS; ++i)
+ spawn_planet ();
+ }
+
+ num_ships = NUM_SIDES;
+ }
+
+ // FlushInput ();
+
+ return (num_ships);
+}
+
+// Count the crew elements in the display list.
+static COUNT
+CountCrewElements (void)
+{
+ COUNT result;
+ HELEMENT hElement, hNextElement;
+
+ result = 0;
+ for (hElement = GetHeadElement ();
+ hElement != 0; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = GetSuccElement (ElementPtr);
+ if (ElementPtr->state_flags & CREW_OBJECT)
+ ++result;
+
+ UnlockElement (hElement);
+ }
+
+ return result;
+}
+
+void
+UninitShips (void)
+{
+ COUNT crew_retrieved;
+ int i;
+ HELEMENT hElement, hNextElement;
+ STARSHIP *SPtr[NUM_PLAYERS];
+
+ StopSound ();
+
+ UninitSpace ();
+
+ for (i = 0; i < NUM_PLAYERS; ++i)
+ SPtr[i] = 0;
+
+ // Count the crew floating in space.
+ crew_retrieved = CountCrewElements();
+
+ for (hElement = GetHeadElement ();
+ hElement != 0; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = GetSuccElement (ElementPtr);
+ if ((ElementPtr->state_flags & PLAYER_SHIP)
+ || ElementPtr->death_func == new_ship)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ // There should only be one ship left in battle.
+ // He gets the crew still floating in space.
+ if (StarShipPtr->RaceDescPtr->ship_info.crew_level)
+ {
+ if (crew_retrieved >=
+ StarShipPtr->RaceDescPtr->ship_info.max_crew -
+ StarShipPtr->RaceDescPtr->ship_info.crew_level)
+ StarShipPtr->RaceDescPtr->ship_info.crew_level =
+ StarShipPtr->RaceDescPtr->ship_info.max_crew;
+ else
+ StarShipPtr->RaceDescPtr->ship_info.crew_level +=
+ crew_retrieved;
+ }
+
+ /* Record crew left after battle */
+ StarShipPtr->crew_level =
+ StarShipPtr->RaceDescPtr->ship_info.crew_level;
+ SPtr[StarShipPtr->playerNr] = StarShipPtr;
+ free_ship (StarShipPtr->RaceDescPtr, TRUE, TRUE);
+ StarShipPtr->RaceDescPtr = 0;
+ }
+ UnlockElement (hElement);
+ }
+
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER
+ && !(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ // Encounter battle in full game.
+ // Record the crew left in the last ship standing. The crew left
+ // is first recorded into STARSHIP.crew_level just a few lines
+ // above here.
+ for (i = NUM_PLAYERS - 1; i >= 0; --i)
+ {
+ if (SPtr[i] && !FleetIsInfinite (i))
+ UpdateShipFragCrew (SPtr[i]);
+ }
+ }
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) != IN_ENCOUNTER)
+ {
+ // Remove any ships left from the race queue.
+ for (i = 0; i < NUM_PLAYERS; i++)
+ ReinitQueue (&race_q[i]);
+
+ if (inHQSpace ())
+ FreeHyperspace ();
+ }
+}
+
+
diff --git a/src/uqm/init.h b/src/uqm/init.h
new file mode 100644
index 0000000..69867f0
--- /dev/null
+++ b/src/uqm/init.h
@@ -0,0 +1,46 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_INIT_H_
+#define UQM_INIT_H_
+
+#include "libs/gfxlib.h"
+#include "libs/reslib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define NUM_PLAYERS 2
+#define NUM_SIDES 2
+
+extern FRAME stars_in_space;
+
+extern BOOLEAN InitSpace (void);
+extern void UninitSpace (void);
+
+extern SIZE InitShips (void);
+extern void UninitShips (void);
+
+extern BOOLEAN load_animation (FRAME *pixarray, RESOURCE big_res,
+ RESOURCE med_res, RESOURCE sml_res);
+extern BOOLEAN free_image (FRAME *pixarray);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_INIT_H_ */
diff --git a/src/uqm/intel.c b/src/uqm/intel.c
new file mode 100644
index 0000000..eb58736
--- /dev/null
+++ b/src/uqm/intel.c
@@ -0,0 +1,76 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "intel.h"
+
+#include "battlecontrols.h"
+#include "controls.h"
+#include "globdata.h"
+#include "setup.h"
+#include "libs/log.h"
+
+#include <stdio.h>
+
+
+BATTLE_INPUT_STATE
+computer_intelligence (ComputerInputContext *context, STARSHIP *StarShipPtr)
+{
+ BATTLE_INPUT_STATE InputState;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ return 0;
+
+ if (StarShipPtr)
+ {
+ // Selecting the next action for in battle.
+ if (StarShipPtr->control & CYBORG_CONTROL)
+ {
+ InputState = tactical_intelligence (context, StarShipPtr);
+
+ // Allow a player to warp-escape in cyborg mode
+ if (StarShipPtr->playerNr == RPG_PLAYER_NUM)
+ InputState |= CurrentInputToBattleInput (
+ context->playerNr) & BATTLE_ESCAPE;
+ }
+ else
+ InputState = CurrentInputToBattleInput (context->playerNr);
+ }
+ else if (!(PlayerControl[context->playerNr] & PSYTRON_CONTROL))
+ InputState = 0;
+ else
+ {
+ switch (LOBYTE (GLOBAL (CurrentActivity)))
+ {
+ case SUPER_MELEE:
+ {
+ SleepThread (ONE_SECOND >> 1);
+ InputState = BATTLE_WEAPON; /* pick a random ship */
+ break;
+ }
+ default:
+ // Should not happen. Satisfying compiler.
+ log_add (log_Warning, "Warning: Unexpected state in "
+ "computer_intelligence().");
+ InputState = 0;
+ break;
+ }
+ }
+ return InputState;
+}
+
+
diff --git a/src/uqm/intel.h b/src/uqm/intel.h
new file mode 100644
index 0000000..6e63e51
--- /dev/null
+++ b/src/uqm/intel.h
@@ -0,0 +1,85 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_INTEL_H_
+#define UQM_INTEL_H_
+
+#include "battlecontrols.h"
+#include "controls.h"
+#include "element.h"
+#include "races.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define MANEUVERABILITY(pi) ((pi)->ManeuverabilityIndex)
+#define WEAPON_RANGE(pi) ((pi)->WeaponRange)
+
+#define WORLD_TO_TURN(d) ((d)>>6)
+
+#define CLOSE_RANGE_WEAPON DISPLAY_TO_WORLD (50)
+#define LONG_RANGE_WEAPON DISPLAY_TO_WORLD (1000)
+#define FAST_SHIP 150
+#define MEDIUM_SHIP 45
+#define SLOW_SHIP 25
+
+enum
+{
+ ENEMY_SHIP_INDEX = 0,
+ CREW_OBJECT_INDEX,
+ ENEMY_WEAPON_INDEX,
+ GRAVITY_MASS_INDEX,
+ FIRST_EMPTY_INDEX
+};
+
+extern BATTLE_INPUT_STATE computer_intelligence (
+ ComputerInputContext *context, STARSHIP *StarShipPtr);
+extern BATTLE_INPUT_STATE tactical_intelligence (
+ ComputerInputContext *context, STARSHIP *StarShipPtr);
+extern void ship_intelligence (ELEMENT *ShipPtr,
+ EVALUATE_DESC *ObjectsOfConcern, COUNT ConcernCounter);
+extern BOOLEAN ship_weapons (ELEMENT *ShipPtr, ELEMENT *OtherPtr,
+ COUNT margin_of_error);
+
+extern void Pursue (ELEMENT *ShipPtr, EVALUATE_DESC *EvalDescPtr);
+extern void Entice (ELEMENT *ShipPtr, EVALUATE_DESC *EvalDescPtr);
+extern void Avoid (ELEMENT *ShipPtr, EVALUATE_DESC *EvalDescPtr);
+extern BOOLEAN TurnShip (ELEMENT *ShipPtr, COUNT angle);
+extern BOOLEAN ThrustShip (ELEMENT *ShipPtr, COUNT angle);
+
+
+#define HUMAN_CONTROL (BYTE)(1 << 0)
+#define CYBORG_CONTROL (BYTE)(1 << 1)
+ // The computer fights the battles.
+#define PSYTRON_CONTROL (BYTE)(1 << 2)
+ // The computer selects the ships to fight with.
+#define NETWORK_CONTROL (BYTE)(1 << 3)
+#define COMPUTER_CONTROL (CYBORG_CONTROL | PSYTRON_CONTROL)
+#define CONTROL_MASK (HUMAN_CONTROL | COMPUTER_CONTROL | NETWORK_CONTROL)
+
+#define STANDARD_RATING (BYTE)(1 << 4)
+#define GOOD_RATING (BYTE)(1 << 5)
+#define AWESOME_RATING (BYTE)(1 << 6)
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_INTEL_H_ */
diff --git a/src/uqm/intro.c b/src/uqm/intro.c
new file mode 100644
index 0000000..092c428
--- /dev/null
+++ b/src/uqm/intro.c
@@ -0,0 +1,875 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "controls.h"
+#include "options.h"
+#include "settings.h"
+#include "globdata.h"
+#include "sis.h"
+#include "setup.h"
+#include "sounds.h"
+#include "colors.h"
+#include "fmv.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/drawable.h"
+#include "libs/sound/sound.h"
+#include "libs/vidlib.h"
+#include "libs/log.h"
+
+#include <ctype.h>
+
+static BOOLEAN ShowSlidePresentation (STRING PresStr);
+
+typedef struct
+{
+ /* standard state required by DoInput */
+ BOOLEAN (*InputFunc) (void *pInputState);
+
+ /* Presentation state */
+ TimeCount StartTime;
+ TimeCount LastSyncTime;
+ TimeCount TimeOut;
+ int TimeOutOnSkip;
+ STRING SlideShow;
+#define MAX_FONTS 5
+ FONT Fonts[MAX_FONTS];
+ FRAME Frame;
+ MUSIC_REF MusicRef;
+ BOOLEAN Batched;
+ FRAME SisFrame;
+ FRAME RotatedFrame;
+ int LastDrawKind;
+ int LastAngle;
+ COUNT OperIndex;
+ Color TextFadeColor;
+ Color TextColor;
+ Color TextBackColor;
+ int TextVPos;
+ int TextEffect;
+ RECT clip_r;
+ RECT tfade_r;
+#define MAX_TEXT_LINES 15
+ TEXT TextLines[MAX_TEXT_LINES];
+ COUNT LinesCount;
+ char Buffer[512];
+ int MovieFrame;
+ int MovieEndFrame;
+ int InterframeDelay;
+
+} PRESENTATION_INPUT_STATE;
+
+typedef struct {
+ /* standard state required by DoInput */
+ BOOLEAN (*InputFunc) (void *pInputState);
+
+ /* Spinanim state */
+ STAMP anim;
+ TimeCount last_time;
+ int debounce;
+} SPINANIM_INPUT_STATE;
+
+typedef struct
+{
+ // standard state required by DoInput
+ BOOLEAN (*InputFunc) (void *pInputState);
+
+ LEGACY_VIDEO_REF CurVideo;
+
+} VIDEO_INPUT_STATE;
+
+static BOOLEAN DoPresentation (void *pIS);
+
+static BOOLEAN
+ParseColorString (const char *Src, Color* pColor)
+{
+ unsigned clr;
+ if (1 != sscanf (Src, "%x", &clr))
+ return FALSE;
+
+ *pColor = BUILD_COLOR_RGBA (
+ (clr >> 16) & 0xff, (clr >> 8) & 0xff, clr & 0xff, 0);
+ return TRUE;
+}
+
+static BOOLEAN
+DoFadeScreen (PRESENTATION_INPUT_STATE* pPIS, const char *Src, BYTE FadeType)
+{
+ int msecs;
+ if (1 == sscanf (Src, "%d", &msecs))
+ {
+ pPIS->TimeOut = FadeScreen (FadeType, msecs * ONE_SECOND / 1000)
+ + ONE_SECOND / 10;
+ pPIS->TimeOutOnSkip = FALSE;
+ }
+ return TRUE;
+}
+
+static void
+DrawTextEffect (TEXT *pText, Color Fore, Color Back, int Effect)
+{
+ if (Effect == 'T')
+ {
+ font_DrawTracedText (pText, Fore, Back);
+ }
+ else
+ {
+ SetContextForeGroundColor (Fore);
+ font_DrawText (pText);
+ }
+}
+
+static COUNT
+ParseTextLines (TEXT *Lines, COUNT MaxLines, char* Buffer)
+{
+ COUNT i;
+ const char* pEnd = Buffer + strlen (Buffer);
+
+ for (i = 0; i < MaxLines && Buffer < pEnd; ++i, ++Lines)
+ {
+ char* pTerm = strchr (Buffer, '\n');
+ if (!pTerm)
+ pTerm = Buffer + strlen (Buffer);
+ *pTerm = '\0'; /* terminate string */
+ Lines->pStr = Buffer;
+ Lines->CharCount = ~0;
+ Buffer = pTerm + 1;
+ }
+ return i;
+}
+
+static void
+Present_BatchGraphics (PRESENTATION_INPUT_STATE* pPIS)
+{
+ if (!pPIS->Batched)
+ {
+ pPIS->Batched = TRUE;
+ BatchGraphics ();
+ }
+}
+
+static void
+Present_UnbatchGraphics (PRESENTATION_INPUT_STATE* pPIS, BOOLEAN bYield)
+{
+ if (pPIS->Batched)
+ {
+ UnbatchGraphics ();
+ pPIS->Batched = FALSE;
+ if (bYield)
+ TaskSwitch ();
+ }
+}
+
+static void
+Present_GenerateSIS (PRESENTATION_INPUT_STATE* pPIS)
+{
+#define MODULE_YOFS_P (-79)
+#define DRIVE_TOP_Y_P (DRIVE_TOP_Y + MODULE_YOFS_P)
+#define JET_TOP_Y_P (JET_TOP_Y + MODULE_YOFS_P)
+#define MODULE_TOP_Y_P (MODULE_TOP_Y + MODULE_YOFS_P)
+ CONTEXT OldContext;
+ FRAME SisFrame;
+ FRAME ModuleFrame;
+ FRAME SkelFrame;
+ STAMP s;
+ RECT r;
+ HOT_SPOT hs;
+ int slot;
+ COUNT piece;
+ Color SisBack;
+
+ OldContext = SetContext (OffScreenContext);
+
+ SkelFrame = CaptureDrawable (LoadGraphic (SISSKEL_MASK_PMAP_ANIM));
+ ModuleFrame = CaptureDrawable (LoadGraphic (SISMODS_MASK_PMAP_ANIM));
+
+ GetFrameRect (SkelFrame, &r);
+ SisFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP, r.extent.width, r.extent.height, 1
+ ));
+ SetContextFGFrame (SisFrame);
+ SetContextClipRect (NULL);
+ SisBack = BUILD_COLOR (MAKE_RGB15 (0x01, 0x01, 0x01), 0x07);
+ SetContextBackGroundColor (SisBack);
+ ClearDrawable ();
+ SetFrameTransparentColor (SisFrame, SisBack);
+
+ s.frame = SetAbsFrameIndex (SkelFrame, 0);
+ s.origin.x = 0;
+ s.origin.y = 0;
+ DrawStamp (&s);
+
+ for (slot = 0; slot < NUM_DRIVE_SLOTS; ++slot)
+ {
+ piece = GLOBAL_SIS (DriveSlots[slot]);
+ if (piece < EMPTY_SLOT)
+ {
+ s.origin.x = DRIVE_TOP_X;
+ s.origin.y = DRIVE_TOP_Y_P;
+ s.origin.x += slot * SHIP_PIECE_OFFSET;
+ s.frame = SetAbsFrameIndex (ModuleFrame, piece);
+ DrawStamp (&s);
+ }
+ }
+ for (slot = 0; slot < NUM_JET_SLOTS; ++slot)
+ {
+ piece = GLOBAL_SIS (JetSlots[slot]);
+ if (piece < EMPTY_SLOT)
+ {
+ s.origin.x = JET_TOP_X;
+ s.origin.y = JET_TOP_Y_P;
+ s.origin.x += slot * SHIP_PIECE_OFFSET;
+ s.frame = SetAbsFrameIndex (ModuleFrame, piece);
+ DrawStamp (&s);
+ }
+ }
+ for (slot = 0; slot < NUM_MODULE_SLOTS; ++slot)
+ {
+ piece = GLOBAL_SIS (ModuleSlots[slot]);
+ if (piece < EMPTY_SLOT)
+ {
+ s.origin.x = MODULE_TOP_X;
+ s.origin.y = MODULE_TOP_Y_P;
+ s.origin.x += slot * SHIP_PIECE_OFFSET;
+ s.frame = SetAbsFrameIndex (ModuleFrame, piece);
+ DrawStamp (&s);
+ }
+ }
+
+ DestroyDrawable (ReleaseDrawable (SkelFrame));
+ DestroyDrawable (ReleaseDrawable (ModuleFrame));
+
+ hs.x = r.extent.width / 2;
+ hs.y = r.extent.height / 2;
+ SetFrameHot (SisFrame, hs);
+
+ SetContext (OldContext);
+ FlushGraphics ();
+
+ pPIS->SisFrame = SisFrame;
+}
+
+static void
+Present_DrawMovieFrame (PRESENTATION_INPUT_STATE* pPIS)
+{
+ STAMP s;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pPIS->Frame, pPIS->MovieFrame);
+ DrawStamp (&s);
+}
+
+static BOOLEAN
+ShowPresentationFile (const char *name)
+{
+ STRING pres = CaptureStringTable (LoadStringTableFile (contentDir, name));
+ BOOLEAN result = ShowSlidePresentation (pres);
+ DestroyStringTable (ReleaseStringTable (pres));
+ return result;
+}
+
+static BOOLEAN
+DoPresentation (void *pIS)
+{
+ PRESENTATION_INPUT_STATE* pPIS = (PRESENTATION_INPUT_STATE*) pIS;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL]
+ || (GLOBAL (CurrentActivity) & CHECK_ABORT))
+ return FALSE; /* abort requested - we are done */
+
+ if (pPIS->TimeOut)
+ {
+ TimeCount Delay = ONE_SECOND / 84;
+
+ if (GetTimeCounter () >= pPIS->TimeOut)
+ {
+ if (pPIS->MovieFrame >= 0)
+ { /* Movie mode */
+ Present_DrawMovieFrame (pPIS);
+ ++pPIS->MovieFrame;
+ if (pPIS->MovieFrame > pPIS->MovieEndFrame)
+ pPIS->MovieFrame = -1; /* movie is done */
+ Delay = pPIS->InterframeDelay;
+ }
+ else
+ { /* time elapsed - continue normal ops */
+ pPIS->TimeOut = 0;
+ return TRUE;
+ }
+ }
+
+ if (pPIS->TimeOutOnSkip &&
+ (PulsedInputState.menu[KEY_MENU_SELECT]
+ || PulsedInputState.menu[KEY_MENU_SPECIAL]
+ || PulsedInputState.menu[KEY_MENU_RIGHT]) )
+ { /* skip requested - continue normal ops */
+ pPIS->TimeOut = 0;
+ pPIS->MovieFrame = -1; /* abort any movie in progress */
+ return TRUE;
+ }
+
+ SleepThread (Delay);
+ return TRUE;
+ }
+
+ while (pPIS->OperIndex < GetStringTableCount (pPIS->SlideShow))
+ {
+ char Opcode[16];
+ char *pStr = GetStringAddress (pPIS->SlideShow);
+
+ pPIS->OperIndex++;
+ pPIS->SlideShow = SetRelStringTableIndex (pPIS->SlideShow, 1);
+
+ if (!pStr)
+ continue;
+ if (1 != sscanf (pStr, "%15s", Opcode))
+ continue;
+ pStr += strlen (Opcode);
+ if (*pStr != '\0')
+ ++pStr;
+ strupr (Opcode);
+
+ if (strcmp (Opcode, "DIMS") == 0)
+ { /* set dimensions */
+ int w, h;
+ if (2 == sscanf (pStr, "%d %d", &w, &h))
+ {
+ pPIS->clip_r.extent.width = w;
+ pPIS->clip_r.extent.height = h;
+ /* center on screen */
+ pPIS->clip_r.corner.x = (SCREEN_WIDTH - w) / 2;
+ pPIS->clip_r.corner.y = (SCREEN_HEIGHT - h) / 2;
+ SetContextClipRect (&pPIS->clip_r);
+ }
+ }
+ else if (strcmp (Opcode, "FONT") == 0)
+ { /* set and/or load a font */
+ int index;
+ FONT *pFont;
+
+ assert (sizeof (pPIS->Buffer) >= 256);
+
+ pPIS->Buffer[0] = '\0';
+ if (1 > sscanf (pStr, "%d %255[^\n]", &index, pPIS->Buffer) ||
+ index < 0 || index >= MAX_FONTS)
+ {
+ log_add (log_Warning, "Bad FONT command '%s'", pStr);
+ continue;
+ }
+ pFont = &pPIS->Fonts[index];
+
+ if (pPIS->Buffer[0])
+ { /* asked to load a font */
+ if (*pFont)
+ DestroyFont (*pFont);
+ *pFont = LoadFontFile (pPIS->Buffer);
+ }
+
+ SetContextFont (*pFont);
+ }
+ else if (strcmp (Opcode, "ANI") == 0)
+ { /* set ani */
+ utf8StringCopy (pPIS->Buffer, sizeof (pPIS->Buffer), pStr);
+ if (pPIS->Frame)
+ DestroyDrawable (ReleaseDrawable (pPIS->Frame));
+ pPIS->Frame = CaptureDrawable (LoadGraphicFile (pPIS->Buffer));
+ }
+ else if (strcmp (Opcode, "MUSIC") == 0)
+ { /* set music */
+ utf8StringCopy (pPIS->Buffer, sizeof (pPIS->Buffer), pStr);
+ if (pPIS->MusicRef)
+ {
+ StopMusic ();
+ DestroyMusic (pPIS->MusicRef);
+ }
+ pPIS->MusicRef = LoadMusicFile (pPIS->Buffer);
+ PlayMusic (pPIS->MusicRef, FALSE, 1);
+ }
+ else if (strcmp (Opcode, "WAIT") == 0)
+ { /* wait */
+ int msecs;
+ Present_UnbatchGraphics (pPIS, TRUE);
+ if (1 == sscanf (pStr, "%d", &msecs))
+ {
+ pPIS->TimeOut = GetTimeCounter ()
+ + msecs * ONE_SECOND / 1000;
+ pPIS->TimeOutOnSkip = TRUE;
+ return TRUE;
+ }
+ }
+ else if (strcmp (Opcode, "SYNC") == 0)
+ { /* absolute time-sync */
+ int msecs;
+ Present_UnbatchGraphics (pPIS, TRUE);
+ if (1 == sscanf (pStr, "%d", &msecs))
+ {
+ pPIS->LastSyncTime = pPIS->StartTime
+ + msecs * ONE_SECOND / 1000;
+ pPIS->TimeOut = pPIS->LastSyncTime;
+ pPIS->TimeOutOnSkip = FALSE;
+ return TRUE;
+ }
+ }
+ else if (strcmp (Opcode, "RESYNC") == 0)
+ { /* flush and update absolute sync point */
+ pPIS->LastSyncTime = pPIS->StartTime = GetTimeCounter ();
+ }
+ else if (strcmp (Opcode, "DSYNC") == 0)
+ { /* delta time-sync; from the last absolute sync */
+ int msecs;
+ Present_UnbatchGraphics (pPIS, TRUE);
+ if (1 == sscanf (pStr, "%d", &msecs))
+ {
+ pPIS->TimeOut = pPIS->LastSyncTime
+ + msecs * ONE_SECOND / 1000;
+ pPIS->TimeOutOnSkip = FALSE;
+ return TRUE;
+ }
+ }
+ else if (strcmp (Opcode, "TC") == 0)
+ { /* text fore color */
+ ParseColorString (pStr, &pPIS->TextColor);
+ }
+ else if (strcmp (Opcode, "TBC") == 0)
+ { /* text back color */
+ ParseColorString (pStr, &pPIS->TextBackColor);
+ }
+ else if (strcmp (Opcode, "TFC") == 0)
+ { /* text fade color */
+ ParseColorString (pStr, &pPIS->TextFadeColor);
+ }
+ else if (strcmp (Opcode, "TVA") == 0)
+ { /* text vertical align */
+ pPIS->TextVPos = toupper (*pStr);
+ }
+ else if (strcmp (Opcode, "TE") == 0)
+ { /* text vertical align */
+ pPIS->TextEffect = toupper (*pStr);
+ }
+ else if (strcmp (Opcode, "TEXT") == 0)
+ { /* simple text draw */
+ int x, y;
+
+ assert (sizeof (pPIS->Buffer) >= 256);
+
+ if (3 == sscanf (pStr, "%d %d %255[^\n]", &x, &y, pPIS->Buffer))
+ {
+ TEXT t;
+
+ t.align = ALIGN_CENTER;
+ t.pStr = pPIS->Buffer;
+ t.CharCount = (COUNT)~0;
+ t.baseline.x = x;
+ t.baseline.y = y;
+ DrawTextEffect (&t, pPIS->TextColor, pPIS->TextBackColor,
+ pPIS->TextEffect);
+ }
+ }
+ else if (strcmp (Opcode, "TFI") == 0)
+ { /* text fade-in */
+ SIZE leading;
+ COUNT i;
+ COORD y;
+
+ utf8StringCopy (pPIS->Buffer, sizeof (pPIS->Buffer), pStr);
+ pPIS->LinesCount = ParseTextLines (pPIS->TextLines,
+ MAX_TEXT_LINES, pPIS->Buffer);
+
+ Present_UnbatchGraphics (pPIS, TRUE);
+
+ GetContextFontLeading (&leading);
+
+ switch (pPIS->TextVPos)
+ {
+ case 'T': /* top */
+ y = leading;
+ break;
+ case 'M': /* middle */
+ y = (pPIS->clip_r.extent.height
+ - pPIS->LinesCount * leading) / 2;
+ break;
+ default: /* bottom */
+ y = pPIS->clip_r.extent.height - pPIS->LinesCount * leading;
+ }
+ pPIS->tfade_r = pPIS->clip_r;
+ pPIS->tfade_r.corner.y += y - leading;
+ pPIS->tfade_r.extent.height = (pPIS->LinesCount + 1) * leading;
+ for (i = 0; i < pPIS->LinesCount; ++i, y += leading)
+ {
+ pPIS->TextLines[i].align = ALIGN_CENTER;
+ pPIS->TextLines[i].baseline.x = SCREEN_WIDTH / 2;
+ pPIS->TextLines[i].baseline.y = y;
+ }
+
+ for (i = 0; i < pPIS->LinesCount; ++i)
+ DrawTextEffect (pPIS->TextLines + i, pPIS->TextFadeColor,
+ pPIS->TextFadeColor, pPIS->TextEffect);
+
+ /* do transition */
+ SetTransitionSource (&pPIS->tfade_r);
+ BatchGraphics ();
+ for (i = 0; i < pPIS->LinesCount; ++i)
+ DrawTextEffect (pPIS->TextLines + i, pPIS->TextColor,
+ pPIS->TextBackColor, pPIS->TextEffect);
+ ScreenTransition (3, &pPIS->tfade_r);
+ UnbatchGraphics ();
+
+ }
+ else if (strcmp (Opcode, "TFO") == 0)
+ { /* text fade-out */
+ COUNT i;
+
+ Present_UnbatchGraphics (pPIS, TRUE);
+
+ /* do transition */
+ SetTransitionSource (&pPIS->tfade_r);
+ BatchGraphics ();
+ for (i = 0; i < pPIS->LinesCount; ++i)
+ DrawTextEffect (pPIS->TextLines + i, pPIS->TextFadeColor,
+ pPIS->TextFadeColor, pPIS->TextEffect);
+ ScreenTransition (3, &pPIS->tfade_r);
+ UnbatchGraphics ();
+ }
+ else if (strcmp (Opcode, "SAVEBG") == 0)
+ { /* save background */
+ TFB_DrawScreen_Copy (&pPIS->clip_r,
+ TFB_SCREEN_MAIN, TFB_SCREEN_EXTRA);
+ }
+ else if (strcmp (Opcode, "RESTBG") == 0)
+ { /* restore background */
+ TFB_DrawScreen_Copy (&pPIS->clip_r,
+ TFB_SCREEN_EXTRA, TFB_SCREEN_MAIN);
+ }
+ else if (strcmp (Opcode, "DRAW") == 0)
+ { /* draw a graphic */
+#define PRES_DRAW_INDEX 0
+#define PRES_DRAW_SIS 1
+ int cargs;
+ int draw_what;
+ int index = 0;
+ int x, y;
+ int scale;
+ int angle;
+ int scale_mode;
+ char ImgName[16];
+ int old_scale, old_mode;
+ STAMP s;
+
+ if (1 == sscanf (pStr, "%15s", ImgName)
+ && strcmp (strupr (ImgName), "SIS") == 0)
+ {
+ draw_what = PRES_DRAW_SIS;
+ scale_mode = TFB_SCALE_NEAREST;
+ cargs = sscanf (pStr, "%*s %d %d %d %d",
+ &x, &y, &scale, &angle) + 1;
+ }
+ else
+ {
+ draw_what = PRES_DRAW_INDEX;
+ scale_mode = TFB_SCALE_BILINEAR;
+ cargs = sscanf (pStr, "%d %d %d %d %d",
+ &index, &x, &y, &scale, &angle);
+ }
+
+ if (cargs < 1)
+ {
+ log_add (log_Warning, "Bad DRAW command '%s'", pStr);
+ continue;
+ }
+ if (cargs < 5)
+ angle = 0;
+ if (cargs < 4)
+ scale = GSCALE_IDENTITY;
+ if (cargs < 3)
+ {
+ x = 0;
+ y = 0;
+ }
+
+ s.frame = NULL;
+ if (draw_what == PRES_DRAW_INDEX)
+ { /* draw stamp by index */
+ s.frame = SetAbsFrameIndex (pPIS->Frame, (COUNT)index);
+ }
+ else if (draw_what == PRES_DRAW_SIS)
+ { /* draw dynamic SIS image with player's modules */
+ if (!pPIS->SisFrame)
+ Present_GenerateSIS (pPIS);
+
+ s.frame = SetAbsFrameIndex (pPIS->SisFrame, 0);
+ }
+ if (angle != 0)
+ {
+ if (angle != pPIS->LastAngle
+ || draw_what != pPIS->LastDrawKind)
+ {
+ DestroyDrawable (ReleaseDrawable (pPIS->RotatedFrame));
+ pPIS->RotatedFrame = CaptureDrawable (
+ RotateFrame (s.frame, -angle));
+ pPIS->LastAngle = angle;
+ pPIS->LastDrawKind = draw_what;
+ }
+ s.frame = pPIS->RotatedFrame;
+ }
+ s.origin.x = x;
+ s.origin.y = y;
+ old_mode = SetGraphicScaleMode (scale_mode);
+ old_scale = SetGraphicScale (scale);
+ DrawStamp (&s);
+ SetGraphicScale (old_scale);
+ SetGraphicScaleMode (old_mode);
+ }
+ else if (strcmp (Opcode, "BATCH") == 0)
+ { /* batch graphics */
+ Present_BatchGraphics (pPIS);
+ }
+ else if (strcmp (Opcode, "UNBATCH") == 0)
+ { /* unbatch graphics */
+ Present_UnbatchGraphics (pPIS, FALSE);
+ }
+ else if (strcmp (Opcode, "FTC") == 0)
+ { /* fade to color */
+ Present_UnbatchGraphics (pPIS, TRUE);
+ return DoFadeScreen (pPIS, pStr, FadeAllToColor);
+ }
+ else if (strcmp (Opcode, "FTB") == 0)
+ { /* fade to black */
+ Present_UnbatchGraphics (pPIS, TRUE);
+ return DoFadeScreen (pPIS, pStr, FadeAllToBlack);
+ }
+ else if (strcmp (Opcode, "FTW") == 0)
+ { /* fade to white */
+ Present_UnbatchGraphics (pPIS, TRUE);
+ return DoFadeScreen (pPIS, pStr, FadeAllToWhite);
+ }
+ else if (strcmp (Opcode, "CLS") == 0)
+ { /* clear screen */
+ Present_UnbatchGraphics (pPIS, TRUE);
+
+ ClearDrawable ();
+ }
+ else if (strcmp (Opcode, "CALL") == 0)
+ { /* call another script */
+ Present_UnbatchGraphics (pPIS, TRUE);
+
+ utf8StringCopy (pPIS->Buffer, sizeof (pPIS->Buffer), pStr);
+ ShowPresentationFile (pPIS->Buffer);
+ }
+ else if (strcmp (Opcode, "LINE") == 0)
+ {
+ int x1, x2, y1, y2;
+ if (4 == sscanf (pStr, "%d %d %d %d", &x1, &y1, &x2, &y2))
+ {
+ LINE l;
+
+ l.first.x = x1;
+ l.first.y = y1;
+ l.second.x = x2;
+ l.second.y = y2;
+
+ SetContextForeGroundColor (pPIS->TextColor);
+ DrawLine (&l);
+ }
+ else
+ {
+ log_add (log_Warning, "Bad LINE command '%s'", pStr);
+ }
+ }
+ else if (strcmp (Opcode, "MOVIE") == 0)
+ {
+ int fps, from, to;
+
+ if (3 == sscanf (pStr, "%d %d %d", &fps, &from, &to) &&
+ fps > 0 && from >= 0 && to >= 0 && to >= from)
+ {
+ Present_UnbatchGraphics (pPIS, TRUE);
+
+ pPIS->MovieFrame = from;
+ pPIS->MovieEndFrame = to;
+ pPIS->InterframeDelay = ONE_SECOND / fps;
+
+ pPIS->TimeOut = GetTimeCounter ();
+ pPIS->TimeOutOnSkip = TRUE;
+ return TRUE;
+ }
+ else
+ {
+ log_add (log_Warning, "Bad MOVIE command '%s'", pStr);
+ }
+ }
+ else if (strcmp (Opcode, "NOOP") == 0)
+ { /* no operation - must be a comment in script */
+ /* do nothing */
+ }
+ }
+ /* we are all done */
+ return FALSE;
+}
+
+static BOOLEAN
+ShowSlidePresentation (STRING PresStr)
+{
+ CONTEXT OldContext;
+ FONT OldFont;
+ RECT OldRect;
+ PRESENTATION_INPUT_STATE pis;
+ int i;
+
+ memset (&pis, 0, sizeof(pis));
+ pis.SlideShow = PresStr;
+ if (!pis.SlideShow)
+ return FALSE;
+ pis.SlideShow = SetAbsStringTableIndex (pis.SlideShow, 0);
+ pis.OperIndex = 0;
+
+ OldContext = SetContext (ScreenContext);
+ GetContextClipRect (&OldRect);
+ OldFont = SetContextFont (NULL);
+ SetContextBackGroundColor (BLACK_COLOR);
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ pis.InputFunc = DoPresentation;
+ pis.LastDrawKind = -1;
+ pis.TextVPos = 'B';
+ pis.MovieFrame = -1;
+ pis.StartTime = GetTimeCounter ();
+ pis.LastSyncTime = pis.StartTime;
+ DoInput (&pis, TRUE);
+
+ SleepThreadUntil (FadeMusic (0, ONE_SECOND));
+ StopMusic ();
+ FadeMusic (NORMAL_VOLUME, 0);
+
+ DestroyMusic (pis.MusicRef);
+ DestroyDrawable (ReleaseDrawable (pis.RotatedFrame));
+ DestroyDrawable (ReleaseDrawable (pis.Frame));
+ for (i = 0; i < MAX_FONTS; ++i)
+ DestroyFont (pis.Fonts[i]);
+
+ SetContextFont (OldFont);
+ SetContextClipRect (&OldRect);
+ SetContext (OldContext);
+
+ return TRUE;
+}
+
+static BOOLEAN
+DoVideoInput (void *pIS)
+{
+ VIDEO_INPUT_STATE* pVIS = (VIDEO_INPUT_STATE*) pIS;
+
+ if (!PlayingLegacyVideo (pVIS->CurVideo))
+ { // Video probably finished
+ return FALSE;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT]
+ || PulsedInputState.menu[KEY_MENU_CANCEL]
+ || PulsedInputState.menu[KEY_MENU_SPECIAL]
+ || (GLOBAL (CurrentActivity) & CHECK_ABORT))
+ { // abort movie
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_LEFT]
+ || PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ SDWORD newpos = VidGetPosition ();
+ if (PulsedInputState.menu[KEY_MENU_LEFT])
+ newpos -= 2000;
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ newpos += 1000;
+ if (newpos < 0)
+ newpos = 0;
+
+ VidSeek (newpos);
+ }
+ else
+ {
+ if (!VidProcessFrame ())
+ return FALSE;
+
+ SleepThread (ONE_SECOND / 40);
+ }
+
+ return TRUE;
+}
+
+static void
+FadeClearScreen (void)
+{
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2));
+
+ // clear the screen with black
+ SetContext (ScreenContext);
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+
+ FadeScreen (FadeAllToColor, 0);
+}
+
+static BOOLEAN
+ShowLegacyVideo (LEGACY_VIDEO vid)
+{
+ VIDEO_INPUT_STATE vis;
+ LEGACY_VIDEO_REF ref;
+
+ FadeClearScreen ();
+
+ ref = PlayLegacyVideo (vid);
+ if (!ref)
+ return FALSE;
+
+ vis.InputFunc = DoVideoInput;
+ vis.CurVideo = ref;
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ DoInput (&vis, TRUE);
+
+ StopLegacyVideo (ref);
+ FadeClearScreen ();
+
+ return TRUE;
+}
+
+BOOLEAN
+ShowPresentation (RESOURCE res)
+{
+ const char *resType = res_GetResourceType (res);
+ if (!resType)
+ {
+ return FALSE;
+ }
+ if (!strcmp (resType, "STRTAB"))
+ {
+ STRING pres = CaptureStringTable (LoadStringTable (res));
+ BOOLEAN result = ShowSlidePresentation (pres);
+ DestroyStringTable (ReleaseStringTable (pres));
+ return result;
+ }
+ else if (!strcmp (resType, "3DOVID"))
+ {
+ LEGACY_VIDEO vid = LoadLegacyVideoInstance (res);
+ BOOLEAN result = ShowLegacyVideo (vid);
+ DestroyLegacyVideo (vid);
+ return result;
+ }
+
+ log_add (log_Warning, "Tried to present '%s', of non-presentable type '%s'", res, resType);
+ return FALSE;
+}
diff --git a/src/uqm/ipdisp.c b/src/uqm/ipdisp.c
new file mode 100644
index 0000000..09a6fb8
--- /dev/null
+++ b/src/uqm/ipdisp.c
@@ -0,0 +1,777 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "ipdisp.h"
+
+#include "collide.h"
+#include "globdata.h"
+#include "init.h"
+#include "races.h"
+#include "process.h"
+#include "grpinfo.h"
+#include "encount.h"
+ // for EncounterGroup, EncounterRace
+#include "libs/mathlib.h"
+
+
+void
+NotifyOthers (COUNT which_race, BYTE target_loc)
+{
+ HSHIPFRAG hGroup, hNextGroup;
+
+ // NOTE: "Others" includes the group causing the notification too.
+
+ for (hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ hGroup; hGroup = hNextGroup)
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+
+ if (GroupPtr->race_id != which_race)
+ {
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ continue;
+ }
+
+ if (target_loc == IPNL_INTERCEPT_PLAYER)
+ {
+ GroupPtr->task &= ~IGNORE_FLAGSHIP;
+ // XXX: orbit_pos is abused here to store the previous
+ // group destination, before the intercept task.
+ // Returned to dest_loc below.
+ GroupPtr->orbit_pos = GroupPtr->dest_loc;
+ GroupPtr->dest_loc = IPNL_INTERCEPT_PLAYER;
+ }
+ else if (target_loc == IPNL_ALL_CLEAR)
+ {
+ GroupPtr->task |= IGNORE_FLAGSHIP;
+
+ if (GroupPtr->dest_loc == IPNL_INTERCEPT_PLAYER)
+ { // The group was intercepting, so send it back where it came
+ // XXX: orbit_pos was abused to store the previous
+ // group destination, before the intercept task.
+ GroupPtr->dest_loc = GroupPtr->orbit_pos;
+ GroupPtr->orbit_pos = NORMALIZE_FACING (TFB_Random ());
+#ifdef OLD
+ GroupPtr->dest_loc = (BYTE)(((COUNT)TFB_Random ()
+ % pSolarSysState->SunDesc[0].NumPlanets) + 1);
+#endif /* OLD */
+ }
+ // If the group wasn't intercepting, it will just continue
+ // going about its business.
+
+ if (!(GroupPtr->task & REFORM_GROUP))
+ {
+ if ((GroupPtr->task & ~IGNORE_FLAGSHIP) != EXPLORE)
+ GroupPtr->group_counter = 0;
+ else
+ GroupPtr->group_counter = ((COUNT) TFB_Random ()
+ % MAX_REVOLUTIONS) << FACING_SHIFT;
+ }
+ }
+ else
+ { // Send the group to the location.
+ // XXX: There is currently no use of such notify that I know of.
+ GroupPtr->task |= IGNORE_FLAGSHIP;
+ GroupPtr->dest_loc = target_loc;
+ }
+
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+}
+
+static SIZE
+zoomRadiusForLocation (BYTE location)
+{
+ if (location == 0)
+ { // In outer system view; use current zoom radius
+ return pSolarSysState->SunDesc[0].radius;
+ }
+ else
+ { // In inner system view; always max zoom
+ return MAX_ZOOM_RADIUS;
+ }
+}
+
+static inline void
+adjustDeltaVforZoom (SIZE zoom, SIZE *dx, SIZE *dy)
+{
+ if (zoom == MIN_ZOOM_RADIUS)
+ {
+ *dx >>= 2;
+ *dy >>= 2;
+ }
+ else if (zoom < MAX_ZOOM_RADIUS)
+ {
+ *dx >>= 1;
+ *dy >>= 1;
+ }
+}
+
+static BYTE
+getFlagshipLocation (void)
+{
+ if (!playerInInnerSystem ())
+ return 0;
+ else
+ return 1 + planetIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
+}
+
+static void
+ip_group_preprocess (ELEMENT *ElementPtr)
+{
+#define TRACK_WAIT 5
+ BYTE task;
+ BYTE target_loc, group_loc, flagship_loc;
+ SIZE radius;
+ POINT dest_pt;
+ SIZE vdx, vdy;
+ ELEMENT *EPtr;
+ IP_GROUP *GroupPtr;
+
+ EPtr = ElementPtr;
+ EPtr->state_flags &= ~(DISAPPEARING | NONSOLID); // "I'm not quite dead"
+ ++EPtr->life_span; // so that it will 'die' again next time
+
+ GetElementStarShip (EPtr, &GroupPtr);
+ group_loc = GroupPtr->sys_loc; // save old location
+ DisplayArray[EPtr->PrimIndex].Object.Point = GroupPtr->loc;
+
+ radius = zoomRadiusForLocation (group_loc);
+ dest_pt = locationToDisplay (GroupPtr->loc, radius);
+ EPtr->current.location.x = DISPLAY_TO_WORLD (dest_pt.x)
+ + (COORD)(LOG_SPACE_WIDTH >> 1)
+ - (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
+ EPtr->current.location.y = DISPLAY_TO_WORLD (dest_pt.y)
+ + (COORD)(LOG_SPACE_HEIGHT >> 1)
+ - (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
+
+ InitIntersectStartPoint (EPtr);
+
+ flagship_loc = getFlagshipLocation ();
+
+ task = GroupPtr->task;
+
+ if ((task & REFORM_GROUP) && --GroupPtr->group_counter == 0)
+ { // Finished reforming the group
+ task &= ~REFORM_GROUP;
+ GroupPtr->task = task;
+ if ((task & ~IGNORE_FLAGSHIP) != EXPLORE)
+ GroupPtr->group_counter = 0;
+ else
+ GroupPtr->group_counter = ((COUNT)TFB_Random ()
+ % MAX_REVOLUTIONS) << FACING_SHIFT;
+ }
+
+ // If fleeing *and* ignoring flagship
+ if ((task & ~(IGNORE_FLAGSHIP | REFORM_GROUP)) == FLEE
+ && (task & IGNORE_FLAGSHIP))
+ { // Make fleeing groups non-collidable
+ EPtr->state_flags |= NONSOLID;
+ }
+
+ target_loc = GroupPtr->dest_loc;
+ if (!(task & (IGNORE_FLAGSHIP | REFORM_GROUP)))
+ {
+ if (target_loc == IPNL_INTERCEPT_PLAYER && task != FLEE)
+ {
+ /* if intercepting flagship */
+ target_loc = flagship_loc;
+ if (EPtr->thrust_wait > TRACK_WAIT)
+ {
+ EPtr->thrust_wait = 0;
+ ZeroVelocityComponents (&EPtr->velocity);
+ }
+ }
+ else if (group_loc == flagship_loc)
+ {
+ long detect_dist;
+
+ detect_dist = 1200;
+ if (group_loc != 0) /* if in planetary views */
+ {
+ detect_dist *= (MAX_ZOOM_RADIUS / MIN_ZOOM_RADIUS);
+ if (GroupPtr->race_id == URQUAN_DRONE_SHIP)
+ detect_dist <<= 1;
+ }
+ vdx = GLOBAL (ip_location.x) - GroupPtr->loc.x;
+ vdy = GLOBAL (ip_location.y) - GroupPtr->loc.y;
+ if ((long)vdx * vdx
+ + (long)vdy * vdy < (long)detect_dist * detect_dist)
+ {
+ EPtr->thrust_wait = 0;
+ ZeroVelocityComponents (&EPtr->velocity);
+
+ NotifyOthers (GroupPtr->race_id, IPNL_INTERCEPT_PLAYER);
+ task = GroupPtr->task;
+ target_loc = GroupPtr->dest_loc;
+ if (target_loc == IPNL_INTERCEPT_PLAYER)
+ target_loc = flagship_loc;
+ }
+ }
+ }
+
+ GetCurrentVelocityComponents (&EPtr->velocity, &vdx, &vdy);
+
+ task &= ~IGNORE_FLAGSHIP;
+#ifdef NEVER
+ if (task <= FLEE || (task == ON_STATION && GroupPtr->dest_loc == 0))
+#else
+ if (task <= ON_STATION)
+#endif /* NEVER */
+ {
+ BOOLEAN Transition;
+ SIZE dx, dy;
+ SIZE delta_x, delta_y;
+ COUNT angle;
+
+ Transition = FALSE;
+ if (task == FLEE)
+ {
+ dest_pt.x = GroupPtr->loc.x << 1;
+ dest_pt.y = GroupPtr->loc.y << 1;
+ }
+ else if (((task != ON_STATION ||
+ GroupPtr->dest_loc == IPNL_INTERCEPT_PLAYER)
+ && group_loc == target_loc)
+ || (task == ON_STATION &&
+ GroupPtr->dest_loc != IPNL_INTERCEPT_PLAYER
+ && group_loc == 0))
+ {
+ if (GroupPtr->dest_loc == IPNL_INTERCEPT_PLAYER)
+ dest_pt = GLOBAL (ip_location);
+ else
+ {
+ COUNT orbit_dist;
+ POINT org;
+
+ if (task != ON_STATION)
+ {
+ orbit_dist = ORBIT_RADIUS;
+ org.x = org.y = 0;
+ }
+ else
+ {
+ orbit_dist = STATION_RADIUS;
+ org = planetOuterLocation (target_loc - 1);
+ }
+
+ angle = FACING_TO_ANGLE (GroupPtr->orbit_pos + 1);
+ dest_pt.x = org.x + COSINE (angle, orbit_dist);
+ dest_pt.y = org.y + SINE (angle, orbit_dist);
+ if (GroupPtr->loc.x == dest_pt.x
+ && GroupPtr->loc.y == dest_pt.y)
+ {
+ BYTE next_loc;
+
+ GroupPtr->orbit_pos = NORMALIZE_FACING (
+ ANGLE_TO_FACING (angle));
+ angle += FACING_TO_ANGLE (1);
+ dest_pt.x = org.x + COSINE (angle, orbit_dist);
+ dest_pt.y = org.y + SINE (angle, orbit_dist);
+
+ EPtr->thrust_wait = (BYTE)~0;
+ if (GroupPtr->group_counter)
+ --GroupPtr->group_counter;
+ else if (task == EXPLORE
+ && (next_loc = (BYTE)(((COUNT)TFB_Random ()
+ % pSolarSysState->SunDesc[0].NumPlanets)
+ + 1)) != target_loc)
+ {
+ EPtr->thrust_wait = 0;
+ target_loc = next_loc;
+ GroupPtr->dest_loc = next_loc;
+ }
+ }
+ }
+ }
+ else if (group_loc == 0)
+ {
+ if (GroupPtr->dest_loc == IPNL_INTERCEPT_PLAYER)
+ dest_pt = pSolarSysState->SunDesc[0].location;
+ else
+ dest_pt = planetOuterLocation (target_loc - 1);
+ }
+ else
+ {
+ if (task == ON_STATION)
+ target_loc = 0;
+
+ dest_pt.x = GroupPtr->loc.x << 1;
+ dest_pt.y = GroupPtr->loc.y << 1;
+ }
+
+ delta_x = dest_pt.x - GroupPtr->loc.x;
+ delta_y = dest_pt.y - GroupPtr->loc.y;
+ angle = ARCTAN (delta_x, delta_y);
+
+ if (EPtr->thrust_wait && EPtr->thrust_wait != (BYTE)~0)
+ --EPtr->thrust_wait;
+ else if ((vdx == 0 && vdy == 0)
+ || angle != GetVelocityTravelAngle (&EPtr->velocity))
+ {
+ SIZE speed;
+
+ if (EPtr->thrust_wait &&
+ GroupPtr->dest_loc != IPNL_INTERCEPT_PLAYER)
+ {
+#define ORBIT_SPEED 60
+ speed = ORBIT_SPEED;
+ if (task == ON_STATION)
+ speed >>= 1;
+ }
+ else
+ {
+ SIZE RaceIPSpeed[] =
+ {
+ RACE_IP_SPEED
+ };
+
+ speed = RaceIPSpeed[GroupPtr->race_id];
+ EPtr->thrust_wait = TRACK_WAIT;
+ }
+
+ vdx = COSINE (angle, speed);
+ vdy = SINE (angle, speed);
+ SetVelocityComponents (&EPtr->velocity, vdx, vdy);
+ }
+
+ dx = vdx;
+ dy = vdy;
+ if (group_loc == target_loc)
+ {
+ if (target_loc == 0)
+ {
+ if (task == FLEE)
+ goto CheckGetAway;
+ }
+ else if (target_loc == GroupPtr->dest_loc)
+ {
+PartialRevolution:
+ if ((long)((COUNT)(dx * dx) + (COUNT)(dy * dy))
+ >= (long)delta_x * delta_x + (long)delta_y * delta_y)
+ {
+ GroupPtr->loc = dest_pt;
+ vdx = 0;
+ vdy = 0;
+ ZeroVelocityComponents (&EPtr->velocity);
+ }
+ }
+ }
+ else
+ {
+ if (group_loc == 0)
+ { // In outer system
+ adjustDeltaVforZoom (radius, &dx, &dy);
+
+ if (task == ON_STATION && GroupPtr->dest_loc)
+ goto PartialRevolution;
+ else if ((long)((COUNT)(dx * dx) + (COUNT)(dy * dy))
+ >= (long)delta_x * delta_x + (long)delta_y * delta_y)
+ Transition = TRUE;
+ }
+ else
+ { // In inner system; also leaving outer CheckGetAway hack
+CheckGetAway:
+ dest_pt = locationToDisplay (GroupPtr->loc, radius);
+ if (dest_pt.x < 0
+ || dest_pt.x >= SIS_SCREEN_WIDTH
+ || dest_pt.y < 0
+ || dest_pt.y >= SIS_SCREEN_HEIGHT)
+ Transition = TRUE;
+ }
+
+ if (Transition)
+ {
+ /* no collisions during transition */
+ EPtr->state_flags |= NONSOLID;
+
+ vdx = 0;
+ vdy = 0;
+ ZeroVelocityComponents (&EPtr->velocity);
+ if (group_loc != 0)
+ {
+ GroupPtr->loc = planetOuterLocation (group_loc - 1);
+ group_loc = 0;
+ GroupPtr->sys_loc = 0;
+ }
+ else if (target_loc == 0)
+ {
+ /* Group completely left the star system */
+ EPtr->life_span = 0;
+ EPtr->state_flags |= DISAPPEARING | NONSOLID;
+ GroupPtr->in_system = 0;
+ return;
+ }
+ else
+ {
+ POINT entryPt;
+
+ if (target_loc == GroupPtr->dest_loc)
+ {
+ GroupPtr->orbit_pos = NORMALIZE_FACING (
+ ANGLE_TO_FACING (angle + HALF_CIRCLE));
+ GroupPtr->group_counter =
+ ((COUNT)TFB_Random () % MAX_REVOLUTIONS)
+ << FACING_SHIFT;
+ }
+ // The group enters inner system exactly on the edge of a
+ // circle with radius = 9/16 * window-dim, which is
+ // different from how the flagship enters, but similar
+ // in the way that the group will never show up in any
+ // of the corners.
+ entryPt.x = (SIS_SCREEN_WIDTH >> 1) - COSINE (angle,
+ SIS_SCREEN_WIDTH * 9 / 16);
+ entryPt.y = (SIS_SCREEN_HEIGHT >> 1) - SINE (angle,
+ SIS_SCREEN_HEIGHT * 9 / 16);
+ GroupPtr->loc = displayToLocation (entryPt,
+ MAX_ZOOM_RADIUS);
+ group_loc = target_loc;
+ GroupPtr->sys_loc = target_loc;
+ }
+ }
+ }
+ }
+
+ radius = zoomRadiusForLocation (group_loc);
+ adjustDeltaVforZoom (radius, &vdx, &vdy);
+ GroupPtr->loc.x += vdx;
+ GroupPtr->loc.y += vdy;
+
+ dest_pt = locationToDisplay (GroupPtr->loc, radius);
+ EPtr->next.location.x = DISPLAY_TO_WORLD (dest_pt.x)
+ + (COORD)(LOG_SPACE_WIDTH >> 1)
+ - (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
+ EPtr->next.location.y = DISPLAY_TO_WORLD (dest_pt.y)
+ + (COORD)(LOG_SPACE_HEIGHT >> 1)
+ - (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
+
+ // Don't draw the group if it's not at flagship location,
+ // or flash the group while it's reforming
+ if (group_loc != flagship_loc
+ || ((task & REFORM_GROUP)
+ && (GroupPtr->group_counter & 1)))
+ {
+ SetPrimType (&DisplayArray[EPtr->PrimIndex], NO_PRIM);
+ EPtr->state_flags |= NONSOLID;
+ }
+ else
+ {
+ SetPrimType (&DisplayArray[EPtr->PrimIndex], STAMP_PRIM);
+ if (task & REFORM_GROUP)
+ EPtr->state_flags |= NONSOLID;
+ }
+
+ EPtr->state_flags |= CHANGING;
+}
+
+static void
+flag_ship_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ return; // ignore the rest of the collisions
+
+ if (!(ElementPtr1->state_flags & COLLISION))
+ { // The other element's collision has not been processed yet
+ // Defer starting the encounter until it is.
+ ElementPtr0->state_flags |= COLLISION | NONSOLID;
+ }
+ else
+ { // Both element's collisions have now been processed
+ ElementPtr1->state_flags &= ~COLLISION;
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ }
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static void
+ip_group_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ IP_GROUP *GroupPtr;
+ void *OtherPtr;
+
+ if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ return; // ignore the rest of the collisions
+
+ GetElementStarShip (ElementPtr0, &GroupPtr);
+ GetElementStarShip (ElementPtr1, &OtherPtr);
+ if (OtherPtr)
+ { // Collision with another group
+ // Prevent the groups from coalescing into a single ship icon
+ if ((ElementPtr0->state_flags & COLLISION)
+ || (ElementPtr1->current.location.x == ElementPtr1->next.location.x
+ && ElementPtr1->current.location.y == ElementPtr1->next.location.y))
+ {
+ ElementPtr0->state_flags &= ~COLLISION;
+ }
+ else
+ {
+ ElementPtr1->state_flags |= COLLISION;
+
+ GroupPtr->loc = DisplayArray[ElementPtr0->PrimIndex].Object.Point;
+ ElementPtr0->next.location = ElementPtr0->current.location;
+ InitIntersectEndPoint (ElementPtr0);
+ }
+ }
+ else // if (!OtherPtr)
+ { // Collision with a flagship
+ EncounterGroup = GroupPtr->group_id;
+
+ GroupPtr->task |= REFORM_GROUP;
+ GroupPtr->group_counter = 100;
+ // Send "all clear" for the time being. After the encounter, if
+ // the player battles the group, the "intercept" notify will be
+ // resent.
+ NotifyOthers (GroupPtr->race_id, IPNL_ALL_CLEAR);
+
+ if (!(ElementPtr1->state_flags & COLLISION))
+ { // The other element's collision has not been processed yet
+ // Defer starting the encounter until it is.
+ ElementPtr0->state_flags |= COLLISION | NONSOLID;
+ }
+ else
+ { // Both element's collisions have now been processed
+ ElementPtr1->state_flags &= ~COLLISION;
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ }
+ }
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static void
+spawn_ip_group (IP_GROUP *GroupPtr)
+{
+ HELEMENT hIPSHIPElement;
+
+ hIPSHIPElement = AllocElement ();
+ if (hIPSHIPElement)
+ {
+ ELEMENT *IPSHIPElementPtr;
+
+ LockElement (hIPSHIPElement, &IPSHIPElementPtr);
+ // Must have mass_points for collisions to work
+ IPSHIPElementPtr->mass_points = 1;
+ IPSHIPElementPtr->hit_points = 1;
+ IPSHIPElementPtr->state_flags =
+ CHANGING | FINITE_LIFE | IGNORE_VELOCITY;
+
+ SetPrimType (&DisplayArray[IPSHIPElementPtr->PrimIndex], STAMP_PRIM);
+ // XXX: Hack: farray points to FRAME[3] and given FRAME
+ IPSHIPElementPtr->current.image.farray = &GroupPtr->melee_icon;
+ IPSHIPElementPtr->current.image.frame = SetAbsFrameIndex (
+ GroupPtr->melee_icon, 1);
+ /* preprocessing has a side effect
+ * we wish to avoid. So death_func
+ * is used instead, but will achieve
+ * same result without the side
+ * effect (InitIntersectFrame)
+ */
+ IPSHIPElementPtr->death_func = ip_group_preprocess;
+ IPSHIPElementPtr->collision_func = ip_group_collision;
+
+ {
+ SIZE radius;
+ POINT pt;
+
+ radius = zoomRadiusForLocation (GroupPtr->sys_loc);
+ pt = locationToDisplay (GroupPtr->loc, radius);
+
+ IPSHIPElementPtr->current.location.x =
+ DISPLAY_TO_WORLD (pt.x)
+ + (COORD)(LOG_SPACE_WIDTH >> 1)
+ - (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
+ IPSHIPElementPtr->current.location.y =
+ DISPLAY_TO_WORLD (pt.y)
+ + (COORD)(LOG_SPACE_HEIGHT >> 1)
+ - (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
+ }
+
+ SetElementStarShip (IPSHIPElementPtr, GroupPtr);
+
+ SetUpElement (IPSHIPElementPtr);
+ IPSHIPElementPtr->IntersectControl.IntersectStamp.frame =
+ DecFrameIndex (stars_in_space);
+
+ UnlockElement (hIPSHIPElement);
+
+ PutElement (hIPSHIPElement);
+ }
+}
+
+#define FLIP_WAIT 42
+
+static void
+flag_ship_preprocess (ELEMENT *ElementPtr)
+{
+ if (--ElementPtr->thrust_wait == 0)
+ /* juggle list after flagship */
+ {
+ HELEMENT hSuccElement;
+
+ if ((hSuccElement = GetSuccElement (ElementPtr))
+ && hSuccElement != GetTailElement ())
+ {
+ HELEMENT hPredElement;
+ ELEMENT *TailPtr;
+
+ LockElement (GetTailElement (), &TailPtr);
+ hPredElement = _GetPredLink (TailPtr);
+ UnlockElement (GetTailElement ());
+
+ RemoveElement (hSuccElement);
+ PutElement (hSuccElement);
+ }
+
+ ElementPtr->thrust_wait = FLIP_WAIT;
+ }
+
+ {
+ BYTE flagship_loc, ec;
+ SIZE vdx, vdy, radius;
+ POINT pt;
+
+ GetCurrentVelocityComponents (&GLOBAL (velocity), &vdx, &vdy);
+
+ flagship_loc = getFlagshipLocation ();
+ radius = zoomRadiusForLocation (flagship_loc);
+ adjustDeltaVforZoom (radius, &vdx, &vdy);
+
+ pt = locationToDisplay (GLOBAL (ip_location), radius);
+ ElementPtr->current.location.x = DISPLAY_TO_WORLD (pt.x)
+ + (COORD)(LOG_SPACE_WIDTH >> 1)
+ - (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
+ ElementPtr->current.location.y = DISPLAY_TO_WORLD (pt.y)
+ + (COORD)(LOG_SPACE_HEIGHT >> 1)
+ - (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
+ InitIntersectStartPoint (ElementPtr);
+
+ GLOBAL (ip_location.x) += vdx;
+ GLOBAL (ip_location.y) += vdy;
+
+ pt = locationToDisplay (GLOBAL (ip_location), radius);
+ ElementPtr->next.location.x = DISPLAY_TO_WORLD (pt.x)
+ + (COORD)(LOG_SPACE_WIDTH >> 1)
+ - (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
+ ElementPtr->next.location.y = DISPLAY_TO_WORLD (pt.y)
+ + (COORD)(LOG_SPACE_HEIGHT >> 1)
+ - (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
+
+ GLOBAL (ShipStamp.origin) = pt;
+ ElementPtr->next.image.frame = GLOBAL (ShipStamp.frame);
+
+ if (ElementPtr->sys_loc == flagship_loc)
+ {
+ if (ElementPtr->state_flags & NONSOLID)
+ ElementPtr->state_flags &= ~NONSOLID;
+ }
+ else /* no collisions during transition */
+ {
+ ElementPtr->state_flags |= NONSOLID;
+ ElementPtr->sys_loc = flagship_loc;
+ }
+
+ if ((ec = GET_GAME_STATE (ESCAPE_COUNTER))
+ && !(GLOBAL (CurrentActivity) & START_ENCOUNTER))
+ {
+ ElementPtr->state_flags |= NONSOLID;
+
+ --ec;
+ SET_GAME_STATE (ESCAPE_COUNTER, ec);
+ }
+
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+static void
+spawn_flag_ship (void)
+{
+ HELEMENT hFlagShipElement;
+
+ hFlagShipElement = AllocElement ();
+ if (hFlagShipElement)
+ {
+ ELEMENT *FlagShipElementPtr;
+
+ LockElement (hFlagShipElement, &FlagShipElementPtr);
+ FlagShipElementPtr->hit_points = 1;
+ // Must have mass_points for collisions to work
+ FlagShipElementPtr->mass_points = 1;
+ FlagShipElementPtr->sys_loc = getFlagshipLocation ();
+ FlagShipElementPtr->state_flags = APPEARING | IGNORE_VELOCITY;
+ if (GET_GAME_STATE (ESCAPE_COUNTER))
+ FlagShipElementPtr->state_flags |= NONSOLID;
+ FlagShipElementPtr->life_span = NORMAL_LIFE;
+ FlagShipElementPtr->thrust_wait = FLIP_WAIT;
+ SetPrimType (&DisplayArray[FlagShipElementPtr->PrimIndex], STAMP_PRIM);
+ FlagShipElementPtr->current.image.farray =
+ &GLOBAL (ShipStamp.frame);
+ FlagShipElementPtr->current.image.frame =
+ GLOBAL (ShipStamp.frame);
+ FlagShipElementPtr->preprocess_func = flag_ship_preprocess;
+ FlagShipElementPtr->collision_func = flag_ship_collision;
+
+ FlagShipElementPtr->current.location.x =
+ DISPLAY_TO_WORLD (GLOBAL (ShipStamp.origin.x))
+ + (COORD)(LOG_SPACE_WIDTH >> 1)
+ - (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
+ FlagShipElementPtr->current.location.y =
+ DISPLAY_TO_WORLD (GLOBAL (ShipStamp.origin.y))
+ + (COORD)(LOG_SPACE_HEIGHT >> 1)
+ - (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
+
+ UnlockElement (hFlagShipElement);
+
+ PutElement (hFlagShipElement);
+ }
+}
+
+void
+DoMissions (void)
+{
+ HSHIPFRAG hGroup, hNextGroup;
+
+ spawn_flag_ship ();
+
+ if (EncounterRace >= 0)
+ { // There was a battle. Call in reinforcements.
+ NotifyOthers (EncounterRace, IPNL_INTERCEPT_PLAYER);
+ EncounterRace = -1;
+ }
+
+ for (hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ hGroup; hGroup = hNextGroup)
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+
+ if (GroupPtr->in_system)
+ spawn_ip_group (GroupPtr);
+
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+}
+
diff --git a/src/uqm/ipdisp.h b/src/uqm/ipdisp.h
new file mode 100644
index 0000000..6b910ca
--- /dev/null
+++ b/src/uqm/ipdisp.h
@@ -0,0 +1,37 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_IPDISP_H_INCL_
+#define UQM_IPDISP_H_INCL_
+
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern void NotifyOthers (COUNT which_race, BYTE target_loc);
+// Special target locations for NotifyOthers()
+#define IPNL_INTERCEPT_PLAYER 0
+#define IPNL_ALL_CLEAR ((BYTE)-1)
+
+extern void DoMissions (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_IPDISP_H_INCL_ */
diff --git a/src/uqm/isndres.h b/src/uqm/isndres.h
new file mode 100644
index 0000000..6de89e6
--- /dev/null
+++ b/src/uqm/isndres.h
@@ -0,0 +1,7 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define GAME_SOUNDS "sounds.battle"
+#define LANDER_SOUNDS "sounds.lander"
+#define MENU_SOUNDS "sounds.menu"
diff --git a/src/uqm/istrtab.h b/src/uqm/istrtab.h
new file mode 100644
index 0000000..503b495
--- /dev/null
+++ b/src/uqm/istrtab.h
@@ -0,0 +1,154 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ACID_COLOR_TAB "planet.acid.colortable"
+#define ACID_XLAT_TAB "planet.acid.translatetable"
+#define ALKALI_COLOR_TAB "planet.alkali.colortable"
+#define ALKALI_XLAT_TAB "planet.alkali.translatetable"
+#define ANDROSYNTH_RUINS_STRTAB "text.androsynthruins"
+#define AQUA_STRTAB "text.aquahelix"
+#define ARISPACE_COLOR_TAB "colortable.quasispace"
+#define AURIC_COLOR_TAB "planet.auric.colortable"
+#define AURIC_XLAT_TAB "planet.auric.translatetable"
+#define AZURE_COLOR_TAB "planet.azure.colortable"
+#define AZURE_XLAT_TAB "planet.azure.translatetable"
+#define BEAST_STRTAB "text.vuxbeast"
+#define BLU_GAS_COLOR_TAB "planet.bluegas.colortable"
+#define BLU_GAS_XLAT_TAB "planet.bluegas.translatetable"
+#define BOMB_STRTAB "text.utwigbomb"
+#define BURV_BCS_STRTAB "text.burvixcaster"
+#define BURV_RUINS_STRTAB "text.burvixeseruins"
+#define CARBIDE_COLOR_TAB "planet.carbide.colortable"
+#define CARBIDE_XLAT_TAB "planet.carbide.translatetable"
+#define CHLORINE_COLOR_TAB "planet.chlorine.colortable"
+#define CHLORINE_XLAT_TAB "planet.chlorine.translatetable"
+#define CHMMR_BASE_STRTAB "text.chmmrbase"
+#define CHONDRITE_COLOR_TAB "planet.chondrite.colortable"
+#define CHONDRITE_XLAT_TAB "planet.chondrite.translatetable"
+#define CIMMERIAN_COLOR_TAB "planet.cimmerian.colortable"
+#define CIMMERIAN_XLAT_TAB "planet.cimmerian.translatetable"
+#define COPPER_COLOR_TAB "planet.copper.colortable"
+#define COPPER_XLAT_TAB "planet.copper.translatetable"
+#define CREDITS_STRTAB "credits.credits"
+#define CRIMSON_COLOR_TAB "planet.crimson.colortable"
+#define CRIMSON_XLAT_TAB "planet.crimson.translatetable"
+#define CYANIC_COLOR_TAB "planet.cyanic.colortable"
+#define CYANIC_XLAT_TAB "planet.cyanic.translatetable"
+#define CYA_GAS_COLOR_TAB "planet.cyangas.colortable"
+#define CYA_GAS_XLAT_TAB "planet.cyangas.translatetable"
+#define DRUUGE_RUINS_STRTAB "text.sphere"
+#define DUST_COLOR_TAB "planet.dust.colortable"
+#define DUST_XLAT_TAB "planet.dust.translatetable"
+#define EGG_CASE_STRTAB "text.eggcase"
+#define EMERALD_COLOR_TAB "planet.emerald.colortable"
+#define EMERALD_XLAT_TAB "planet.emerald.translatetable"
+#define FINALPRES_STRTAB "slides.ending"
+#define FLUORESCENT_COLOR_TAB "planet.fluorescent.colortable"
+#define FLUORESCENT_XLAT_TAB "planet.fluorescent.translatetable"
+#define GREEN_COLOR_TAB "planet.green.colortable"
+#define GREEN_XLAT_TAB "planet.green.translatetable"
+#define GRN_GAS_COLOR_TAB "planet.greengas.colortable"
+#define GRN_GAS_XLAT_TAB "planet.greengas.translatetable"
+#define GRY_GAS_COLOR_TAB "planet.greygas.colortable"
+#define GRY_GAS_XLAT_TAB "planet.greygas.translatetable"
+#define HALIDE_COLOR_TAB "planet.halide.colortable"
+#define HALIDE_XLAT_TAB "planet.halide.translatetable"
+#define HANGAR_COLOR_TAB "colortable.hangar"
+#define HYDROCARBON_COLOR_TAB "planet.hydrocarbon.colortable"
+#define HYDROCARBON_XLAT_TAB "planet.hydrocarbon.translatetable"
+#define HYPER_COLOR_TAB "colortable.hyperspace"
+#define INFRARED_COLOR_TAB "planet.infrared.colortable"
+#define INFRARED_XLAT_TAB "planet.infrared.translatetable"
+#define INTROPRES_STRTAB "slides.intro"
+#define IODINE_COLOR_TAB "planet.iodine.colortable"
+#define IODINE_XLAT_TAB "planet.iodine.translatetable"
+#define IPSUN_COLOR_MAP "colortable.truespace"
+#define JOYSTICK_ALPHA_STRTAB "text.joyalpha"
+#define LANTHANIDE_COLOR_TAB "planet.lanthanide.colortable"
+#define LANTHANIDE_XLAT_TAB "planet.lanthanide.translatetable"
+#define MAGMA_COLOR_TAB "planet.magma.colortable"
+#define MAGMA_XLAT_TAB "planet.magma.translatetable"
+#define MAGNETIC_COLOR_TAB "planet.magnetic.colortable"
+#define MAGNETIC_XLAT_TAB "planet.magnetic.translatetable"
+#define MAIDENS_STRTAB "text.maidens"
+#define MAROON_COLOR_TAB "planet.maroon.colortable"
+#define MAROON_XLAT_TAB "planet.maroon.translatetable"
+#define METAL_COLOR_TAB "planet.metal.colortable"
+#define METAL_XLAT_TAB "planet.metal.translatetable"
+#define MOONBASE_STRTAB "text.moonbase"
+#define NOBLE_COLOR_TAB "planet.noble.colortable"
+#define NOBLE_XLAT_TAB "planet.noble.translatetable"
+#define OOLITE_COLOR_TAB "planet.oolite.colortable"
+#define OOLITE_XLAT_TAB "planet.oolite.translatetable"
+#define OPALESCENT_COLOR_TAB "planet.opalescent.colortable"
+#define OPALESCENT_XLAT_TAB "planet.opalescent.translatetable"
+#define ORA_GAS_COLOR_TAB "planet.orangegas.colortable"
+#define ORA_GAS_XLAT_TAB "planet.orangegas.translatetable"
+#define ORBPLAN_COLOR_MAP "colortable.orbplan"
+#define ORGANIC_COLOR_TAB "planet.organic.colortable"
+#define ORGANIC_XLAT_TAB "planet.organic.translatetable"
+#define PELLUCID_COLOR_TAB "planet.pellucid.colortable"
+#define PELLUCID_XLAT_TAB "planet.pellucid.translatetable"
+#define PKUNK_RUINS_STRTAB "text.spindle"
+#define PLUTONIC_COLOR_TAB "planet.plutonic.colortable"
+#define PLUTONIC_XLAT_TAB "planet.plutonic.translatetable"
+#define PRIMORDIAL_COLOR_TAB "planet.primordial.colortable"
+#define PRIMORDIAL_XLAT_TAB "planet.primordial.translatetable"
+#define PURPLE_COLOR_TAB "planet.purple.colortable"
+#define PURPLE_XLAT_TAB "planet.purple.translatetable"
+#define PUR_GAS_COLOR_TAB "planet.purplegas.colortable"
+#define PUR_GAS_XLAT_TAB "planet.purplegas.translatetable"
+#define QUASI_DEGENERATE_COLOR_TAB "planet.quasidegenerate.colortable"
+#define QUASI_DEGENERATE_XLAT_TAB "planet.quasidegenerate.translatetable"
+#define RADIOACTIVE_COLOR_TAB "planet.radioactive.colortable"
+#define RADIOACTIVE_XLAT_TAB "planet.radioactive.translatetable"
+#define RAINBOW_COLOR_TAB "planet.rainbow.colortable"
+#define RAINBOW_XLAT_TAB "planet.rainbow.translatetable"
+#define REDUX_COLOR_TAB "planet.redux.colortable"
+#define REDUX_XLAT_TAB "planet.redux.translatetable"
+#define RED_GAS_COLOR_TAB "planet.redgas.colortable"
+#define RED_GAS_XLAT_TAB "planet.redgas.translatetable"
+#define RUBY_COLOR_TAB "planet.ruby.colortable"
+#define RUBY_XLAT_TAB "planet.ruby.translatetable"
+#define RUINS_STRTAB "text.ruins"
+#define SAPPHIRE_COLOR_TAB "planet.sapphire.colortable"
+#define SAPPHIRE_XLAT_TAB "planet.sapphire.translatetable"
+#define SELENIC_COLOR_TAB "planet.selenic.colortable"
+#define SELENIC_XLAT_TAB "planet.selenic.translatetable"
+#define SETUP_MENU_STRTAB "text.setupmenu"
+#define SHATTERED_COLOR_TAB "planet.shattered.colortable"
+#define SHATTERED_XLAT_TAB "planet.shattered.translatetable"
+#define SPAPLUTO_STRTAB "text.fwiffo"
+#define STARCON_COLOR_MAP "colortable.main"
+#define STARCON_GAME_STRINGS "text.starcon"
+#define SUN_DEVICE_STRTAB "text.sundevice"
+#define SUPER_DENSE_COLOR_TAB "planet.superdense.colortable"
+#define SUPER_DENSE_XLAT_TAB "planet.superdense.translatetable"
+#define SUPOX_RUINS_STRTAB "text.ultron"
+#define TAALO_DEVICE_STRTAB "text.taalodevice"
+#define TELLURIC_COLOR_TAB "planet.telluric.colortable"
+#define TELLURIC_XLAT_TAB "planet.telluric.translatetable"
+#define TREASURE_COLOR_TAB "planet.treasure.colortable"
+#define TREASURE_XLAT_TAB "planet.treasure.translatetable"
+#define ULTRAMARINE_COLOR_TAB "planet.ultramarine.colortable"
+#define ULTRAMARINE_XLAT_TAB "planet.ultramarine.translatetable"
+#define ULTRAVIOLET_COLOR_TAB "planet.ultraviolet.colortable"
+#define ULTRAVIOLET_XLAT_TAB "planet.ultraviolet.translatetable"
+#define UMGAH_BCS_STRTAB "text.umgahcaster"
+#define UREA_COLOR_TAB "planet.urea.colortable"
+#define UREA_XLAT_TAB "planet.urea.translatetable"
+#define VAULT_STRTAB "text.syreenvault"
+#define VINYLOGOUS_COLOR_TAB "planet.vinylogous.colortable"
+#define VINYLOGOUS_XLAT_TAB "planet.vinylogous.translatetable"
+#define VIO_GAS_COLOR_TAB "planet.violetgas.colortable"
+#define VIO_GAS_XLAT_TAB "planet.violetgas.translatetable"
+#define WATER_COLOR_TAB "planet.water.colortable"
+#define WATER_XLAT_TAB "planet.water.translatetable"
+#define WRECK_STRTAB "text.urquanwreck"
+#define XENOLITHIC_COLOR_TAB "planet.xenolithic.colortable"
+#define XENOLITHIC_XLAT_TAB "planet.xenolithic.translatetable"
+#define YEL_GAS_COLOR_TAB "planet.yellowgas.colortable"
+#define YEL_GAS_XLAT_TAB "planet.yellowgas.translatetable"
+#define YTTRIC_COLOR_TAB "planet.yttric.colortable"
+#define YTTRIC_XLAT_TAB "planet.yttric.translatetable"
diff --git a/src/uqm/load.c b/src/uqm/load.c
new file mode 100644
index 0000000..2e7dcbd
--- /dev/null
+++ b/src/uqm/load.c
@@ -0,0 +1,774 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <assert.h>
+
+#include "build.h"
+#include "encount.h"
+#include "starmap.h"
+#include "libs/file.h"
+#include "globdata.h"
+#include "options.h"
+#include "save.h"
+#include "setup.h"
+#include "state.h"
+#include "grpintrn.h"
+
+#include "libs/tasklib.h"
+#include "libs/log.h"
+#include "libs/misc.h"
+
+//#define DEBUG_LOAD
+
+ACTIVITY NextActivity;
+
+static inline size_t
+read_8 (void *fp, BYTE *v)
+{
+ BYTE t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return ReadResFile (v, 1, 1, fp);
+}
+
+static inline size_t
+read_16 (void *fp, UWORD *v)
+{
+ UWORD t = 0;
+ int shift, i;
+ for (i = 0, shift = 0; i < 2; ++i, shift += 8)
+ {
+ BYTE b;
+ if (read_8 (fp, &b) != 1)
+ return 0;
+ t |= ((UWORD)b) << shift;
+ }
+
+ if (v)
+ *v = t;
+
+ return 1;
+}
+
+static inline size_t
+read_16s (void *fp, SWORD *v)
+{
+ return read_16 (fp, (UWORD *) v);
+}
+
+static inline size_t
+read_32 (void *fp, DWORD *v)
+{
+ DWORD t = 0;
+ int shift, i;
+ for (i = 0, shift = 0; i < 4; ++i, shift += 8)
+ {
+ BYTE b;
+ if (read_8 (fp, &b) != 1)
+ return 0;
+ t |= ((DWORD)b) << shift;
+ }
+
+ if (v)
+ *v = t;
+
+ return 1;
+}
+
+static inline size_t
+read_32s (void *fp, SDWORD *v)
+{
+ return read_32 (fp, (DWORD *) v);
+}
+
+static inline size_t
+read_a8 (void *fp, BYTE *ar, COUNT count)
+{
+ assert (ar != NULL);
+ return ReadResFile (ar, 1, count, fp) == count;
+}
+
+static inline size_t
+read_a8s (void *fp, char *ar, COUNT count)
+{
+ return read_a8(fp, (BYTE *) ar, count);
+}
+
+static inline size_t
+skip_8 (void *fp, COUNT count)
+{
+ int i;
+ for (i = 0; i < count; ++i)
+ {
+ if (read_8(fp, NULL) != 1)
+ return 0;
+ }
+ return 1;
+}
+
+static inline size_t
+read_str (void *fp, char *str, COUNT count)
+{
+ // no type conversion needed for strings
+ return read_a8 (fp, (BYTE *)str, count);
+}
+
+static inline size_t
+read_a16 (void *fp, UWORD *ar, COUNT count)
+{
+ assert (ar != NULL);
+
+ for ( ; count > 0; --count, ++ar)
+ {
+ if (read_16 (fp, ar) != 1)
+ return 0;
+ }
+ return 1;
+}
+
+static void
+LoadShipQueue (void *fh, QUEUE *pQueue, DWORD size)
+{
+ COUNT num_links = size / 11;
+
+ while (num_links--)
+ {
+ HSHIPFRAG hStarShip;
+ SHIP_FRAGMENT *FragPtr;
+ COUNT Index;
+
+ read_16 (fh, &Index);
+
+ hStarShip = CloneShipFragment (Index, pQueue, 0);
+ FragPtr = LockShipFrag (pQueue, hStarShip);
+
+ // Read SHIP_FRAGMENT elements
+ read_8 (fh, &FragPtr->captains_name_index);
+ read_8 (fh, &FragPtr->race_id);
+ read_8 (fh, &FragPtr->index);
+ read_16 (fh, &FragPtr->crew_level);
+ read_16 (fh, &FragPtr->max_crew);
+ read_8 (fh, &FragPtr->energy_level);
+ read_8 (fh, &FragPtr->max_energy);
+
+ UnlockShipFrag (pQueue, hStarShip);
+ }
+}
+
+static void
+LoadRaceQueue (void *fh, QUEUE *pQueue, DWORD size)
+{
+ COUNT num_links = size / 30;
+
+ while (num_links--)
+ {
+ HFLEETINFO hStarShip;
+ FLEET_INFO *FleetPtr;
+ COUNT Index;
+
+ read_16 (fh, &Index);
+
+ hStarShip = GetStarShipFromIndex (pQueue, Index);
+ FleetPtr = LockFleetInfo (pQueue, hStarShip);
+
+ // Read FLEET_INFO elements
+ read_16 (fh, &FleetPtr->allied_state);
+ read_8 (fh, &FleetPtr->days_left);
+ read_8 (fh, &FleetPtr->growth_fract);
+ read_16 (fh, &FleetPtr->crew_level);
+ read_16 (fh, &FleetPtr->max_crew);
+ read_8 (fh, &FleetPtr->growth);
+ read_8 (fh, &FleetPtr->max_energy);
+ read_16s(fh, &FleetPtr->loc.x);
+ read_16s(fh, &FleetPtr->loc.y);
+
+ read_16 (fh, &FleetPtr->actual_strength);
+ read_16 (fh, &FleetPtr->known_strength);
+ read_16s(fh, &FleetPtr->known_loc.x);
+ read_16s(fh, &FleetPtr->known_loc.y);
+ read_8 (fh, &FleetPtr->growth_err_term);
+ read_8 (fh, &FleetPtr->func_index);
+ read_16s(fh, &FleetPtr->dest_loc.x);
+ read_16s(fh, &FleetPtr->dest_loc.y);
+
+ UnlockFleetInfo (pQueue, hStarShip);
+ }
+}
+
+static void
+LoadGroupQueue (void *fh, QUEUE *pQueue, DWORD size)
+{
+ COUNT num_links = size / 13;
+
+ while (num_links--)
+ {
+ HIPGROUP hGroup;
+ IP_GROUP *GroupPtr;
+
+ hGroup = BuildGroup (pQueue, 0);
+ GroupPtr = LockIpGroup (pQueue, hGroup);
+
+ read_16 (fh, &GroupPtr->group_counter);
+ read_8 (fh, &GroupPtr->race_id);
+ read_8 (fh, &GroupPtr->sys_loc);
+ read_8 (fh, &GroupPtr->task);
+ read_8 (fh, &GroupPtr->in_system); /* was crew_level */
+ read_8 (fh, &GroupPtr->dest_loc);
+ read_8 (fh, &GroupPtr->orbit_pos);
+ read_8 (fh, &GroupPtr->group_id); /* was max_energy */
+ read_16s(fh, &GroupPtr->loc.x);
+ read_16s(fh, &GroupPtr->loc.y);
+
+ UnlockIpGroup (pQueue, hGroup);
+ }
+}
+
+static void
+LoadEncounter (ENCOUNTER *EncounterPtr, void *fh)
+{
+ COUNT i;
+
+ EncounterPtr->pred = 0;
+ EncounterPtr->succ = 0;
+ EncounterPtr->hElement = 0;
+ read_16s (fh, &EncounterPtr->transition_state);
+ read_16s (fh, &EncounterPtr->origin.x);
+ read_16s (fh, &EncounterPtr->origin.y);
+ read_16 (fh, &EncounterPtr->radius);
+ // former STAR_DESC fields
+ read_16s (fh, &EncounterPtr->loc_pt.x);
+ read_16s (fh, &EncounterPtr->loc_pt.y);
+ read_8 (fh, &EncounterPtr->race_id);
+ read_8 (fh, &EncounterPtr->num_ships);
+ read_8 (fh, &EncounterPtr->flags);
+
+ // Load each entry in the BRIEF_SHIP_INFO array
+ for (i = 0; i < MAX_HYPER_SHIPS; i++)
+ {
+ BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i];
+
+ read_8 (fh, &ShipInfo->race_id);
+ read_16 (fh, &ShipInfo->crew_level);
+ read_16 (fh, &ShipInfo->max_crew);
+ read_8 (fh, &ShipInfo->max_energy);
+ }
+
+ // Load the stuff after the BRIEF_SHIP_INFO array
+ read_32s (fh, &EncounterPtr->log_x);
+ read_32s (fh, &EncounterPtr->log_y);
+}
+
+static void
+LoadEvent (EVENT *EventPtr, void *fh)
+{
+ EventPtr->pred = 0;
+ EventPtr->succ = 0;
+ read_8 (fh, &EventPtr->day_index);
+ read_8 (fh, &EventPtr->month_index);
+ read_16 (fh, &EventPtr->year_index);
+ read_8 (fh, &EventPtr->func_index);
+}
+
+static void
+LoadClockState (CLOCK_STATE *ClockPtr, void *fh)
+{
+ read_8 (fh, &ClockPtr->day_index);
+ read_8 (fh, &ClockPtr->month_index);
+ read_16 (fh, &ClockPtr->year_index);
+ read_16s (fh, &ClockPtr->tick_count);
+ read_16s (fh, &ClockPtr->day_in_ticks);
+}
+
+static BOOLEAN
+LoadGameState (GAME_STATE *GSPtr, void *fh)
+{
+ DWORD magic;
+ read_32 (fh, &magic);
+ if (magic != GLOBAL_STATE_TAG)
+ {
+ return FALSE;
+ }
+ read_32 (fh, &magic);
+ if (magic != 75)
+ {
+ /* Chunk is the wrong size. */
+ return FALSE;
+ }
+ read_8 (fh, &GSPtr->glob_flags);
+ read_8 (fh, &GSPtr->CrewCost);
+ read_8 (fh, &GSPtr->FuelCost);
+ read_a8 (fh, GSPtr->ModuleCost, NUM_MODULES);
+ read_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES);
+ read_16 (fh, &GSPtr->CurrentActivity);
+
+ LoadClockState (&GSPtr->GameClock, fh);
+
+ read_16s (fh, &GSPtr->autopilot.x);
+ read_16s (fh, &GSPtr->autopilot.y);
+ read_16s (fh, &GSPtr->ip_location.x);
+ read_16s (fh, &GSPtr->ip_location.y);
+ /* STAMP ShipStamp */
+ read_16s (fh, &GSPtr->ShipStamp.origin.x);
+ read_16s (fh, &GSPtr->ShipStamp.origin.y);
+ read_16 (fh, &GSPtr->ShipFacing);
+ read_8 (fh, &GSPtr->ip_planet);
+ read_8 (fh, &GSPtr->in_orbit);
+
+ /* VELOCITY_DESC velocity */
+ read_16 (fh, &GSPtr->velocity.TravelAngle);
+ read_16s (fh, &GSPtr->velocity.vector.width);
+ read_16s (fh, &GSPtr->velocity.vector.height);
+ read_16s (fh, &GSPtr->velocity.fract.width);
+ read_16s (fh, &GSPtr->velocity.fract.height);
+ read_16s (fh, &GSPtr->velocity.error.width);
+ read_16s (fh, &GSPtr->velocity.error.height);
+ read_16s (fh, &GSPtr->velocity.incr.width);
+ read_16s (fh, &GSPtr->velocity.incr.height);
+
+ read_32 (fh, &magic);
+ if (magic != GAME_STATE_TAG)
+ {
+ return FALSE;
+ }
+ memset (GSPtr->GameState, 0, sizeof (GSPtr->GameState));
+ read_32 (fh, &magic);
+ if (magic > sizeof (GSPtr->GameState))
+ {
+ read_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState));
+ skip_8 (fh, magic - sizeof (GSPtr->GameState));
+ }
+ else
+ {
+ read_a8 (fh, GSPtr->GameState, magic);
+ }
+ return TRUE;
+}
+
+static BOOLEAN
+LoadSisState (SIS_STATE *SSPtr, void *fp)
+{
+ if (
+ read_32s (fp, &SSPtr->log_x) != 1 ||
+ read_32s (fp, &SSPtr->log_y) != 1 ||
+ read_32 (fp, &SSPtr->ResUnits) != 1 ||
+ read_32 (fp, &SSPtr->FuelOnBoard) != 1 ||
+ read_16 (fp, &SSPtr->CrewEnlisted) != 1 ||
+ read_16 (fp, &SSPtr->TotalElementMass) != 1 ||
+ read_16 (fp, &SSPtr->TotalBioMass) != 1 ||
+ read_a8 (fp, SSPtr->ModuleSlots, NUM_MODULE_SLOTS) != 1 ||
+ read_a8 (fp, SSPtr->DriveSlots, NUM_DRIVE_SLOTS) != 1 ||
+ read_a8 (fp, SSPtr->JetSlots, NUM_JET_SLOTS) != 1 ||
+ read_8 (fp, &SSPtr->NumLanders) != 1 ||
+ read_a16 (fp, SSPtr->ElementAmounts, NUM_ELEMENT_CATEGORIES) != 1 ||
+
+ read_str (fp, SSPtr->ShipName, SIS_NAME_SIZE) != 1 ||
+ read_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE) != 1 ||
+ read_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1
+ )
+ return FALSE;
+ return TRUE;
+}
+
+static BOOLEAN
+LoadSummary (SUMMARY_DESC *SummPtr, void *fp)
+{
+ DWORD magic;
+ DWORD nameSize = 0;
+ if (!read_32 (fp, &magic))
+ return FALSE;
+ if (magic == SAVEFILE_TAG)
+ {
+ if (read_32 (fp, &magic) != 1 || magic != SUMMARY_TAG)
+ return FALSE;
+ if (read_32 (fp, &magic) != 1 || magic < 160)
+ return FALSE;
+ nameSize = magic - 160;
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ if (!LoadSisState (&SummPtr->SS, fp))
+ return FALSE;
+
+ if ( read_8 (fp, &SummPtr->Activity) != 1 ||
+ read_8 (fp, &SummPtr->Flags) != 1 ||
+ read_8 (fp, &SummPtr->day_index) != 1 ||
+ read_8 (fp, &SummPtr->month_index) != 1 ||
+ read_16 (fp, &SummPtr->year_index) != 1 ||
+ read_8 (fp, &SummPtr->MCreditLo) != 1 ||
+ read_8 (fp, &SummPtr->MCreditHi) != 1 ||
+ read_8 (fp, &SummPtr->NumShips) != 1 ||
+ read_8 (fp, &SummPtr->NumDevices) != 1 ||
+ read_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 ||
+ read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1
+ )
+ return FALSE;
+
+ if (nameSize < SAVE_NAME_SIZE)
+ {
+ if (read_a8s (fp, SummPtr->SaveName, nameSize) != 1)
+ return FALSE;
+ SummPtr->SaveName[nameSize] = 0;
+ }
+ else
+ {
+ DWORD remaining = nameSize - SAVE_NAME_SIZE + 1;
+ if (read_a8s (fp, SummPtr->SaveName, SAVE_NAME_SIZE-1) != 1)
+ return FALSE;
+ SummPtr->SaveName[SAVE_NAME_SIZE-1] = 0;
+ if (skip_8 (fp, remaining) != 1)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+LoadStarDesc (STAR_DESC *SDPtr, void *fh)
+{
+ read_16s(fh, &SDPtr->star_pt.x);
+ read_16s(fh, &SDPtr->star_pt.y);
+ read_8 (fh, &SDPtr->Type);
+ read_8 (fh, &SDPtr->Index);
+ read_8 (fh, &SDPtr->Prefix);
+ read_8 (fh, &SDPtr->Postfix);
+}
+
+static void
+LoadScanInfo (uio_Stream *fh, DWORD flen)
+{
+ GAME_STATE_FILE *fp = OpenStateFile (STARINFO_FILE, "wb");
+ if (fp)
+ {
+ while (flen)
+ {
+ DWORD val;
+ read_32 (fh, &val);
+ swrite_32 (fp, val);
+ flen -= 4;
+ }
+ CloseStateFile (fp);
+ }
+}
+
+static void
+LoadGroupList (uio_Stream *fh, DWORD chunksize)
+{
+ GAME_STATE_FILE *fp = OpenStateFile (RANDGRPINFO_FILE, "rb");
+ if (fp)
+ {
+ GROUP_HEADER h;
+ BYTE LastEnc, NumGroups;
+ int i;
+ ReadGroupHeader (fp, &h);
+ /* There's only supposed to be one of these, so group 0 should be
+ * zero here whenever we're here. We add the group list to the
+ * end here. */
+ h.GroupOffset[0] = LengthStateFile (fp);
+ SeekStateFile (fp, 0, SEEK_SET);
+ WriteGroupHeader (fp, &h);
+ SeekStateFile (fp, h.GroupOffset[0], SEEK_SET);
+ read_8 (fh, &LastEnc);
+ NumGroups = (chunksize - 1) / 14;
+ swrite_8 (fp, LastEnc);
+ swrite_8 (fp, NumGroups);
+ for (i = 0; i < NumGroups; ++i)
+ {
+ BYTE race_outer;
+ IP_GROUP ip;
+ read_8 (fh, &race_outer);
+ read_16 (fh, &ip.group_counter);
+ read_8 (fh, &ip.race_id);
+ read_8 (fh, &ip.sys_loc);
+ read_8 (fh, &ip.task);
+ read_8 (fh, &ip.in_system);
+ read_8 (fh, &ip.dest_loc);
+ read_8 (fh, &ip.orbit_pos);
+ read_8 (fh, &ip.group_id);
+ read_16s (fh, &ip.loc.x);
+ read_16s (fh, &ip.loc.y);
+
+ swrite_8 (fp, race_outer);
+ WriteIpGroup (fp, &ip);
+ }
+ CloseStateFile (fp);
+ }
+}
+
+static void
+LoadBattleGroup (uio_Stream *fh, DWORD chunksize)
+{
+ GAME_STATE_FILE *fp;
+ GROUP_HEADER h;
+ DWORD encounter, offset;
+ BYTE current;
+ int i;
+
+ read_32 (fh, &encounter);
+ read_8 (fh, &current);
+ chunksize -= 5;
+ if (encounter)
+ {
+ /* This is a defined group, so it's new */
+ fp = OpenStateFile (DEFGRPINFO_FILE, "rb");
+ offset = LengthStateFile (fp);
+ memset (&h, 0, sizeof (GROUP_HEADER));
+ }
+ else
+ {
+ /* This is the random group. Load in what was there,
+ * as we might have already seen the Group List. */
+ fp = OpenStateFile (RANDGRPINFO_FILE, "rb");
+ current = FALSE;
+ offset = 0;
+ ReadGroupHeader (fp, &h);
+ }
+ if (!fp)
+ {
+ skip_8 (fh, chunksize);
+ return;
+ }
+ read_16 (fh, &h.star_index);
+ read_8 (fh, &h.day_index);
+ read_8 (fh, &h.month_index);
+ read_16 (fh, &h.year_index);
+ read_8 (fh, &h.NumGroups);
+ chunksize -= 7;
+ /* Write out the half-finished state file so that we can use
+ * the file size to compute group offsets */
+ SeekStateFile (fp, offset, SEEK_SET);
+ WriteGroupHeader (fp, &h);
+ for (i = 1; i <= h.NumGroups; ++i)
+ {
+ int j;
+ BYTE icon, NumShips;
+ read_8 (fh, &icon);
+ read_8 (fh, &NumShips);
+ chunksize -= 2;
+ h.GroupOffset[i] = LengthStateFile (fp);
+ SeekStateFile (fp, h.GroupOffset[i], SEEK_SET);
+ swrite_8 (fp, icon);
+ swrite_8 (fp, NumShips);
+ for (j = 0; j < NumShips; ++j)
+ {
+ BYTE race_outer;
+ SHIP_FRAGMENT sf;
+ read_8 (fh, &race_outer);
+ read_8 (fh, &sf.captains_name_index);
+ read_8 (fh, &sf.race_id);
+ read_8 (fh, &sf.index);
+ read_16 (fh, &sf.crew_level);
+ read_16 (fh, &sf.max_crew);
+ read_8 (fh, &sf.energy_level);
+ read_8 (fh, &sf.max_energy);
+ chunksize -= 10;
+
+ swrite_8 (fp, race_outer);
+ WriteShipFragment (fp, &sf);
+ }
+ }
+ /* Now that the GroupOffset array is properly initialized,
+ * write the header back out. */
+ SeekStateFile (fp, offset, SEEK_SET);
+ WriteGroupHeader (fp, &h);
+ CloseStateFile (fp);
+ /* And update the gamestate accordingly, if we're a defined group. */
+ if (encounter)
+ {
+ SET_GAME_STATE_32 (SHOFIXTI_GRPOFFS0 + (encounter - 1) * 32, offset);
+ if (current)
+ {
+ GLOBAL (BattleGroupRef) = offset;
+ }
+ }
+ /* Consistency check. */
+ if (chunksize)
+ {
+ log_add (log_Warning, "BattleGroup chunk mis-sized!");
+ }
+}
+
+BOOLEAN
+LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr)
+{
+ uio_Stream *in_fp;
+ char file[PATH_MAX];
+ SUMMARY_DESC loc_sd;
+ COUNT num_links;
+ STAR_DESC SD;
+ ACTIVITY Activity;
+ DWORD chunk, chunkSize;
+ BOOLEAN first_group_spec = TRUE;
+
+ sprintf (file, "uqmsave.%02u", which_game);
+ in_fp = res_OpenResFile (saveDir, file, "rb");
+ if (!in_fp)
+ return LoadLegacyGame (which_game, SummPtr);
+
+ if (!LoadSummary (&loc_sd, in_fp))
+ {
+ res_CloseResFile (in_fp);
+ return LoadLegacyGame (which_game, SummPtr);
+ }
+
+ if (!SummPtr)
+ {
+ SummPtr = &loc_sd;
+ }
+ else
+ { // only need summary for displaying to user
+ memcpy (SummPtr, &loc_sd, sizeof (*SummPtr));
+ res_CloseResFile (in_fp);
+ return TRUE;
+ }
+
+ GlobData.SIS_state = SummPtr->SS;
+
+ ReinitQueue (&GLOBAL (GameClock.event_q));
+ ReinitQueue (&GLOBAL (encounter_q));
+ ReinitQueue (&GLOBAL (ip_group_q));
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ ReinitQueue (&GLOBAL (built_ship_q));
+
+ memset (&GLOBAL (GameState[0]), 0, sizeof (GLOBAL (GameState)));
+ Activity = GLOBAL (CurrentActivity);
+ if (!LoadGameState (&GlobData.Game_state, in_fp))
+ {
+ res_CloseResFile (in_fp);
+ return FALSE;
+ }
+ NextActivity = GLOBAL (CurrentActivity);
+ GLOBAL (CurrentActivity) = Activity;
+
+ chunk = 0;
+ while (TRUE)
+ {
+ if (read_32(in_fp, &chunk) != 1)
+ {
+ break;
+ }
+ if (read_32(in_fp, &chunkSize) != 1)
+ {
+ res_CloseResFile (in_fp);
+ return FALSE;
+ }
+ switch (chunk)
+ {
+ case RACE_Q_TAG:
+ LoadRaceQueue (in_fp, &GLOBAL (avail_race_q), chunkSize);
+ break;
+ case IP_GRP_Q_TAG:
+ LoadGroupQueue (in_fp, &GLOBAL (ip_group_q), chunkSize);
+ break;
+ case ENCOUNTERS_TAG:
+ num_links = chunkSize / 65;
+ while (num_links--)
+ {
+ HENCOUNTER hEncounter;
+ ENCOUNTER *EncounterPtr;
+
+ hEncounter = AllocEncounter ();
+ LockEncounter (hEncounter, &EncounterPtr);
+
+ LoadEncounter (EncounterPtr, in_fp);
+
+ UnlockEncounter (hEncounter);
+ PutEncounter (hEncounter);
+ }
+ break;
+ case EVENTS_TAG:
+ num_links = chunkSize / 5;
+#ifdef DEBUG_LOAD
+ log_add (log_Debug, "EVENTS:");
+#endif /* DEBUG_LOAD */
+ while (num_links--)
+ {
+ HEVENT hEvent;
+ EVENT *EventPtr;
+
+ hEvent = AllocEvent ();
+ LockEvent (hEvent, &EventPtr);
+
+ LoadEvent (EventPtr, in_fp);
+
+#ifdef DEBUG_LOAD
+ log_add (log_Debug, "\t%u/%u/%u -- %u",
+ EventPtr->month_index,
+ EventPtr->day_index,
+ EventPtr->year_index,
+ EventPtr->func_index);
+#endif /* DEBUG_LOAD */
+ UnlockEvent (hEvent);
+ PutEvent (hEvent);
+ }
+ break;
+ case STAR_TAG:
+ LoadStarDesc (&SD, in_fp);
+ break;
+ case NPC_SHIP_Q_TAG:
+ LoadShipQueue (in_fp, &GLOBAL (npc_built_ship_q), chunkSize);
+ break;
+ case SHIP_Q_TAG:
+ LoadShipQueue (in_fp, &GLOBAL (built_ship_q), chunkSize);
+ break;
+ case SCAN_TAG:
+ LoadScanInfo (in_fp, chunkSize);
+ break;
+ case GROUP_LIST_TAG:
+ if (first_group_spec)
+ {
+ InitGroupInfo (TRUE);
+ GLOBAL (BattleGroupRef) = 0;
+ first_group_spec = FALSE;
+ }
+ LoadGroupList (in_fp, chunkSize);
+ break;
+ case BATTLE_GROUP_TAG:
+ if (first_group_spec)
+ {
+ InitGroupInfo (TRUE);
+ GLOBAL (BattleGroupRef) = 0;
+ first_group_spec = FALSE;
+ }
+ LoadBattleGroup (in_fp, chunkSize);
+ break;
+ default:
+ log_add (log_Debug, "Skipping chunk of tag %08X (size %u)", chunk, chunkSize);
+ if (skip_8(in_fp, chunkSize) != 1)
+ {
+ res_CloseResFile (in_fp);
+ return FALSE;
+ }
+ break;
+ }
+ }
+ res_CloseResFile (in_fp);
+
+ EncounterGroup = 0;
+ EncounterRace = -1;
+
+ ReinitQueue (&race_q[0]);
+ ReinitQueue (&race_q[1]);
+ CurStarDescPtr = FindStar (NULL, &SD.star_pt, 0, 0);
+ if (!(NextActivity & START_ENCOUNTER)
+ && LOBYTE (NextActivity) == IN_INTERPLANETARY)
+ NextActivity |= START_INTERPLANETARY;
+
+ return TRUE;
+}
diff --git a/src/uqm/load_legacy.c b/src/uqm/load_legacy.c
new file mode 100644
index 0000000..6470a52
--- /dev/null
+++ b/src/uqm/load_legacy.c
@@ -0,0 +1,821 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <assert.h>
+
+#include "build.h"
+#include "libs/declib.h"
+#include "encount.h"
+#include "starmap.h"
+#include "libs/file.h"
+#include "globdata.h"
+#include "options.h"
+#include "save.h"
+#include "setup.h"
+#include "state.h"
+#include "grpinfo.h"
+
+#include "libs/tasklib.h"
+#include "libs/log.h"
+#include "libs/misc.h"
+
+//#define DEBUG_LOAD
+
+// XXX: these should handle endian conversions later
+static inline COUNT
+cread_8 (DECODE_REF fh, BYTE *v)
+{
+ BYTE t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return cread (v, 1, 1, fh);
+}
+
+static inline COUNT
+cread_16 (DECODE_REF fh, UWORD *v)
+{
+ UWORD t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return cread (v, 2, 1, fh);
+}
+
+static inline COUNT
+cread_16s (DECODE_REF fh, SWORD *v)
+{
+ UWORD t;
+ COUNT ret;
+ // value was converted to unsigned when saved
+ ret = cread_16 (fh, &t);
+ // unsigned to signed conversion
+ if (v)
+ *v = t;
+ return ret;
+}
+
+static inline COUNT
+cread_32 (DECODE_REF fh, DWORD *v)
+{
+ DWORD t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return cread (v, 4, 1, fh);
+}
+
+static inline COUNT
+cread_32s (DECODE_REF fh, SDWORD *v)
+{
+ DWORD t;
+ COUNT ret;
+ // value was converted to unsigned when saved
+ ret = cread_32 (fh, &t);
+ // unsigned to signed conversion
+ if (v)
+ *v = t;
+ return ret;
+}
+
+static inline COUNT
+cread_ptr (DECODE_REF fh)
+{
+ DWORD t;
+ return cread_32 (fh, &t); /* ptrs are useless in saves */
+}
+
+static inline COUNT
+cread_a8 (DECODE_REF fh, BYTE *ar, COUNT count)
+{
+ assert (ar != NULL);
+ return cread (ar, 1, count, fh) == count;
+}
+
+static inline size_t
+read_8 (void *fp, BYTE *v)
+{
+ BYTE t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return ReadResFile (v, 1, 1, fp);
+}
+
+static inline size_t
+read_16 (void *fp, UWORD *v)
+{
+ UWORD t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return ReadResFile (v, 2, 1, fp);
+}
+
+static inline size_t
+read_32 (void *fp, DWORD *v)
+{
+ DWORD t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return ReadResFile (v, 4, 1, fp);
+}
+
+static inline size_t
+read_32s (void *fp, SDWORD *v)
+{
+ DWORD t;
+ COUNT ret;
+ // value was converted to unsigned when saved
+ ret = read_32 (fp, &t);
+ // unsigned to signed conversion
+ if (v)
+ *v = t;
+ return ret;
+}
+
+static inline size_t
+read_ptr (void *fp)
+{
+ DWORD t;
+ return read_32 (fp, &t); /* ptrs are useless in saves */
+}
+
+static inline size_t
+read_a8 (void *fp, BYTE *ar, COUNT count)
+{
+ assert (ar != NULL);
+ return ReadResFile (ar, 1, count, fp) == count;
+}
+
+static inline size_t
+read_str (void *fp, char *str, COUNT count)
+{
+ // no type conversion needed for strings
+ return read_a8 (fp, (BYTE *)str, count);
+}
+
+static inline size_t
+read_a16 (void *fp, UWORD *ar, COUNT count)
+{
+ assert (ar != NULL);
+
+ for ( ; count > 0; --count, ++ar)
+ {
+ if (read_16 (fp, ar) != 1)
+ return 0;
+ }
+ return 1;
+}
+
+typedef struct struct_GAMESTATE_TRANSPOSE {
+ int start, end, target;
+} GAMESTATE_TRANSPOSE;
+
+#define LEGACY_GAMESTATE_SIZE 155
+
+/* The *_GRPOFFS* states are no longer intermingled with the rest of
+ * the state. We need to shuffle all the rest of the state data
+ * down. */
+static GAMESTATE_TRANSPOSE transpose[] = {
+ { 0, 51, 0 },
+ { 404, 450, 52 },
+ { 483, 878, 99 },
+ { 911, 930, 495 },
+ { 963, 1237, 515 },
+ { -1, -1, -1 } };
+
+static DWORD old_defgrp_offsets[] = { 0, 52, 84, 116, 148, 180, 212, 244,
+ 276, 308, 340, 372, 451, 879, 931 };
+
+static DWORD new_defgrp_offsets[] = {
+ 0,
+ SHOFIXTI_GRPOFFS0,
+ ZOQFOT_GRPOFFS0,
+ MELNORME0_GRPOFFS0,
+ MELNORME1_GRPOFFS0,
+ MELNORME2_GRPOFFS0,
+ MELNORME3_GRPOFFS0,
+ MELNORME4_GRPOFFS0,
+ MELNORME5_GRPOFFS0,
+ MELNORME6_GRPOFFS0,
+ MELNORME7_GRPOFFS0,
+ MELNORME8_GRPOFFS0,
+ URQUAN_PROBE_GRPOFFS0,
+ COLONY_GRPOFFS0,
+ SAMATRA_GRPOFFS0
+};
+
+static void
+InterpretLegacyGameState (BYTE *result, BYTE *legacy)
+{
+ int i;
+ DWORD grpoffs[NUM_DEFGRPS];
+ GAMESTATE_TRANSPOSE *t = &transpose[0];
+ grpoffs[0] = 0;
+ for (i = 1; i < NUM_DEFGRPS; ++i)
+ {
+ grpoffs[i] = getGameState32 (legacy, old_defgrp_offsets[i]);
+ }
+ while (t->start >= 0)
+ {
+ copyGameState (result, t->target, legacy, t->start, t->end);
+ ++t;
+ }
+ for (i = 1; i < NUM_DEFGRPS; ++i)
+ {
+ setGameState32 (result, new_defgrp_offsets[i], grpoffs[i]);
+ }
+}
+
+static void
+LoadEmptyQueue (DECODE_REF fh)
+{
+ COUNT num_links;
+
+ cread_16 (fh, &num_links);
+ if (num_links)
+ {
+ log_add (log_Error, "LoadEmptyQueue(): BUG: the queue is not empty!");
+#ifdef DEBUG
+ explode ();
+#endif
+ }
+}
+
+static void
+LoadShipQueue (DECODE_REF fh, QUEUE *pQueue)
+{
+ COUNT num_links;
+
+ cread_16 (fh, &num_links);
+
+ while (num_links--)
+ {
+ HSHIPFRAG hStarShip;
+ SHIP_FRAGMENT *FragPtr;
+ COUNT Index;
+ BYTE tmpb;
+
+ cread_16 (fh, &Index);
+
+ hStarShip = CloneShipFragment (Index, pQueue, 0);
+ FragPtr = LockShipFrag (pQueue, hStarShip);
+
+ // Read SHIP_FRAGMENT elements
+ cread_16 (fh, NULL); /* unused: was which_side */
+ cread_8 (fh, &FragPtr->captains_name_index);
+ cread_8 (fh, NULL); /* padding */
+ cread_16 (fh, NULL); /* unused: was ship_flags */
+ cread_8 (fh, &FragPtr->race_id);
+ cread_8 (fh, &FragPtr->index);
+ // XXX: reading crew as BYTE to maintain savegame compatibility
+ cread_8 (fh, &tmpb);
+ FragPtr->crew_level = tmpb;
+ cread_8 (fh, &tmpb);
+ FragPtr->max_crew = tmpb;
+ cread_8 (fh, &FragPtr->energy_level);
+ cread_8 (fh, &FragPtr->max_energy);
+ cread_16 (fh, NULL); /* unused; was loc.x */
+ cread_16 (fh, NULL); /* unused; was loc.y */
+
+ UnlockShipFrag (pQueue, hStarShip);
+ }
+}
+
+static void
+LoadRaceQueue (DECODE_REF fh, QUEUE *pQueue)
+{
+ COUNT num_links;
+
+ cread_16 (fh, &num_links);
+
+ while (num_links--)
+ {
+ HFLEETINFO hStarShip;
+ FLEET_INFO *FleetPtr;
+ COUNT Index;
+ BYTE tmpb;
+
+ cread_16 (fh, &Index);
+
+ hStarShip = GetStarShipFromIndex (pQueue, Index);
+ FleetPtr = LockFleetInfo (pQueue, hStarShip);
+
+ // Read FLEET_INFO elements
+ cread_16 (fh, &FleetPtr->allied_state);
+ cread_8 (fh, &FleetPtr->days_left);
+ cread_8 (fh, &FleetPtr->growth_fract);
+ cread_8 (fh, &tmpb);
+ FleetPtr->crew_level = tmpb;
+ cread_8 (fh, &tmpb);
+ FleetPtr->max_crew = tmpb;
+ cread_8 (fh, &FleetPtr->growth);
+ cread_8 (fh, &FleetPtr->max_energy);
+ cread_16s(fh, &FleetPtr->loc.x);
+ cread_16s(fh, &FleetPtr->loc.y);
+
+ cread_16 (fh, &FleetPtr->actual_strength);
+ cread_16 (fh, &FleetPtr->known_strength);
+ cread_16s(fh, &FleetPtr->known_loc.x);
+ cread_16s(fh, &FleetPtr->known_loc.y);
+ cread_8 (fh, &FleetPtr->growth_err_term);
+ cread_8 (fh, &FleetPtr->func_index);
+ cread_16s(fh, &FleetPtr->dest_loc.x);
+ cread_16s(fh, &FleetPtr->dest_loc.y);
+ cread_16 (fh, NULL); /* alignment padding */
+
+ UnlockFleetInfo (pQueue, hStarShip);
+ }
+}
+
+static void
+LoadGroupQueue (DECODE_REF fh, QUEUE *pQueue)
+{
+ COUNT num_links;
+
+ cread_16 (fh, &num_links);
+
+ while (num_links--)
+ {
+ HIPGROUP hGroup;
+ IP_GROUP *GroupPtr;
+ BYTE tmpb;
+
+ cread_16 (fh, NULL); /* unused; was race_id */
+
+ hGroup = BuildGroup (pQueue, 0);
+ GroupPtr = LockIpGroup (pQueue, hGroup);
+
+ cread_16 (fh, NULL); /* unused; was which_side */
+ cread_8 (fh, NULL); /* unused; was captains_name_index */
+ cread_8 (fh, NULL); /* padding; for savegame compat */
+ cread_16 (fh, &GroupPtr->group_counter);
+ cread_8 (fh, &GroupPtr->race_id);
+ cread_8 (fh, &tmpb); /* was var2 */
+ GroupPtr->sys_loc = LONIBBLE (tmpb);
+ GroupPtr->task = HINIBBLE (tmpb);
+ cread_8 (fh, &GroupPtr->in_system); /* was crew_level */
+ cread_8 (fh, NULL); /* unused; was max_crew */
+ cread_8 (fh, &tmpb); /* was energy_level */
+ GroupPtr->dest_loc = LONIBBLE (tmpb);
+ GroupPtr->orbit_pos = HINIBBLE (tmpb);
+ cread_8 (fh, &GroupPtr->group_id); /* was max_energy */
+ cread_16s(fh, &GroupPtr->loc.x);
+ cread_16s(fh, &GroupPtr->loc.y);
+
+ UnlockIpGroup (pQueue, hGroup);
+ }
+}
+
+static void
+LoadEncounter (ENCOUNTER *EncounterPtr, DECODE_REF fh)
+{
+ COUNT i;
+ BYTE tmpb;
+
+ cread_ptr (fh); /* useless ptr; HENCOUNTER pred */
+ EncounterPtr->pred = 0;
+ cread_ptr (fh); /* useless ptr; HENCOUNTER succ */
+ EncounterPtr->succ = 0;
+ cread_ptr (fh); /* useless ptr; HELEMENT hElement */
+ EncounterPtr->hElement = 0;
+ cread_16s (fh, &EncounterPtr->transition_state);
+ cread_16s (fh, &EncounterPtr->origin.x);
+ cread_16s (fh, &EncounterPtr->origin.y);
+ cread_16 (fh, &EncounterPtr->radius);
+ // former STAR_DESC fields
+ cread_16s (fh, &EncounterPtr->loc_pt.x);
+ cread_16s (fh, &EncounterPtr->loc_pt.y);
+ cread_8 (fh, &EncounterPtr->race_id);
+ cread_8 (fh, &tmpb);
+ EncounterPtr->num_ships = tmpb & ENCOUNTER_SHIPS_MASK;
+ EncounterPtr->flags = tmpb & ENCOUNTER_FLAGS_MASK;
+ cread_16 (fh, NULL); /* alignment padding */
+
+ // Load each entry in the BRIEF_SHIP_INFO array
+ for (i = 0; i < MAX_HYPER_SHIPS; i++)
+ {
+ BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i];
+
+ cread_16 (fh, NULL); /* useless; was SHIP_INFO.ship_flags */
+ cread_8 (fh, &ShipInfo->race_id);
+ cread_8 (fh, NULL); /* useless; was SHIP_INFO.var2 */
+ // XXX: reading crew as BYTE to maintain savegame compatibility
+ cread_8 (fh, &tmpb);
+ ShipInfo->crew_level = tmpb;
+ cread_8 (fh, &tmpb);
+ ShipInfo->max_crew = tmpb;
+ cread_8 (fh, NULL); /* useless; was SHIP_INFO.energy_level */
+ cread_8 (fh, &ShipInfo->max_energy);
+ cread_16 (fh, NULL); /* useless; was SHIP_INFO.loc.x */
+ cread_16 (fh, NULL); /* useless; was SHIP_INFO.loc.y */
+ cread_32 (fh, NULL); /* useless val; STRING race_strings */
+ cread_ptr (fh); /* useless ptr; FRAME icons */
+ cread_ptr (fh); /* useless ptr; FRAME melee_icon */
+ }
+
+ // Load the stuff after the BRIEF_SHIP_INFO array
+ cread_32s (fh, &EncounterPtr->log_x);
+ cread_32s (fh, &EncounterPtr->log_y);
+}
+
+static void
+LoadEvent (EVENT *EventPtr, DECODE_REF fh)
+{
+ cread_ptr (fh); /* useless ptr; HEVENT pred */
+ EventPtr->pred = 0;
+ cread_ptr (fh); /* useless ptr; HEVENT succ */
+ EventPtr->succ = 0;
+ cread_8 (fh, &EventPtr->day_index);
+ cread_8 (fh, &EventPtr->month_index);
+ cread_16 (fh, &EventPtr->year_index);
+ cread_8 (fh, &EventPtr->func_index);
+ cread_8 (fh, NULL); /* padding */
+ cread_16 (fh, NULL); /* padding */
+}
+
+static void
+DummyLoadQueue (QUEUE *QueuePtr, DECODE_REF fh)
+{
+ /* QUEUE should never actually be loaded since it contains
+ * purely internal representation and the lists
+ * involved are actually loaded separately */
+ (void)QueuePtr; /* silence compiler */
+
+ /* QUEUE format with QUEUE_TABLE defined -- UQM default */
+ cread_ptr (fh); /* HLINK head */
+ cread_ptr (fh); /* HLINK tail */
+ cread_ptr (fh); /* BYTE* pq_tab */
+ cread_ptr (fh); /* HLINK free_list */
+ cread_16 (fh, NULL); /* MEM_HANDLE hq_tab */
+ cread_16 (fh, NULL); /* COUNT object_size */
+ cread_8 (fh, NULL); /* BYTE num_objects */
+
+ cread_8 (fh, NULL); /* padding */
+ cread_16 (fh, NULL); /* padding */
+}
+
+static void
+LoadClockState (CLOCK_STATE *ClockPtr, DECODE_REF fh)
+{
+ cread_8 (fh, &ClockPtr->day_index);
+ cread_8 (fh, &ClockPtr->month_index);
+ cread_16 (fh, &ClockPtr->year_index);
+ cread_16s (fh, &ClockPtr->tick_count);
+ cread_16s (fh, &ClockPtr->day_in_ticks);
+ cread_ptr (fh); /* not loading ptr; Semaphore clock_sem */
+ cread_ptr (fh); /* not loading ptr; Task clock_task */
+ cread_32 (fh, NULL); /* not loading; DWORD TimeCounter */
+
+ DummyLoadQueue (&ClockPtr->event_q, fh);
+}
+
+static void
+LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh)
+{
+ BYTE dummy8, oldstate[LEGACY_GAMESTATE_SIZE];
+
+ cread_8 (fh, &dummy8); /* obsolete */
+ cread_8 (fh, &GSPtr->glob_flags);
+ cread_8 (fh, &GSPtr->CrewCost);
+ cread_8 (fh, &GSPtr->FuelCost);
+ cread_a8 (fh, GSPtr->ModuleCost, NUM_MODULES);
+ cread_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES);
+ cread_ptr (fh); /* not loading ptr; PRIMITIVE *DisplayArray */
+ cread_16 (fh, &GSPtr->CurrentActivity);
+
+ cread_16 (fh, NULL); /* CLOCK_STATE alignment padding */
+ LoadClockState (&GSPtr->GameClock, fh);
+
+ cread_16s (fh, &GSPtr->autopilot.x);
+ cread_16s (fh, &GSPtr->autopilot.y);
+ cread_16s (fh, &GSPtr->ip_location.x);
+ cread_16s (fh, &GSPtr->ip_location.y);
+ /* STAMP ShipStamp */
+ cread_16s (fh, &GSPtr->ShipStamp.origin.x);
+ cread_16s (fh, &GSPtr->ShipStamp.origin.y);
+ cread_16 (fh, &GSPtr->ShipFacing);
+ cread_8 (fh, &GSPtr->ip_planet);
+ cread_8 (fh, &GSPtr->in_orbit);
+
+ /* VELOCITY_DESC velocity */
+ cread_16 (fh, &GSPtr->velocity.TravelAngle);
+ cread_16s (fh, &GSPtr->velocity.vector.width);
+ cread_16s (fh, &GSPtr->velocity.vector.height);
+ cread_16s (fh, &GSPtr->velocity.fract.width);
+ cread_16s (fh, &GSPtr->velocity.fract.height);
+ cread_16s (fh, &GSPtr->velocity.error.width);
+ cread_16s (fh, &GSPtr->velocity.error.height);
+ cread_16s (fh, &GSPtr->velocity.incr.width);
+ cread_16s (fh, &GSPtr->velocity.incr.height);
+ cread_16 (fh, NULL); /* VELOCITY_DESC padding */
+
+ cread_32 (fh, &GSPtr->BattleGroupRef);
+
+ DummyLoadQueue (&GSPtr->avail_race_q, fh);
+ DummyLoadQueue (&GSPtr->npc_built_ship_q, fh);
+ // Not loading ip_group_q, was not there originally
+ DummyLoadQueue (&GSPtr->encounter_q, fh);
+ DummyLoadQueue (&GSPtr->built_ship_q, fh);
+
+ cread_a8 (fh, oldstate, LEGACY_GAMESTATE_SIZE);
+ InterpretLegacyGameState (GSPtr->GameState, oldstate);
+
+ cread_8 (fh, NULL); /* GAME_STATE alignment padding */
+}
+
+static BOOLEAN
+LoadSisState (SIS_STATE *SSPtr, void *fp)
+{
+ if (
+ read_32s (fp, &SSPtr->log_x) != 1 ||
+ read_32s (fp, &SSPtr->log_y) != 1 ||
+ read_32 (fp, &SSPtr->ResUnits) != 1 ||
+ read_32 (fp, &SSPtr->FuelOnBoard) != 1 ||
+ read_16 (fp, &SSPtr->CrewEnlisted) != 1 ||
+ read_16 (fp, &SSPtr->TotalElementMass) != 1 ||
+ read_16 (fp, &SSPtr->TotalBioMass) != 1 ||
+ read_a8 (fp, SSPtr->ModuleSlots, NUM_MODULE_SLOTS) != 1 ||
+ read_a8 (fp, SSPtr->DriveSlots, NUM_DRIVE_SLOTS) != 1 ||
+ read_a8 (fp, SSPtr->JetSlots, NUM_JET_SLOTS) != 1 ||
+ read_8 (fp, &SSPtr->NumLanders) != 1 ||
+ read_a16 (fp, SSPtr->ElementAmounts, NUM_ELEMENT_CATEGORIES) != 1 ||
+
+ read_str (fp, SSPtr->ShipName, SIS_NAME_SIZE) != 1 ||
+ read_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE) != 1 ||
+ read_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1 ||
+
+ read_16 (fp, NULL) != 1 /* padding */
+ )
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static BOOLEAN
+LoadSummary (SUMMARY_DESC *SummPtr, void *fp)
+{
+ if (!LoadSisState (&SummPtr->SS, fp))
+ return FALSE;
+
+ if (
+ read_8 (fp, &SummPtr->Activity) != 1 ||
+ read_8 (fp, &SummPtr->Flags) != 1 ||
+ read_8 (fp, &SummPtr->day_index) != 1 ||
+ read_8 (fp, &SummPtr->month_index) != 1 ||
+ read_16 (fp, &SummPtr->year_index) != 1 ||
+ read_8 (fp, &SummPtr->MCreditLo) != 1 ||
+ read_8 (fp, &SummPtr->MCreditHi) != 1 ||
+ read_8 (fp, &SummPtr->NumShips) != 1 ||
+ read_8 (fp, &SummPtr->NumDevices) != 1 ||
+ read_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 ||
+ read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 ||
+
+ read_16 (fp, NULL) != 1 /* padding */
+ )
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static void
+LoadStarDesc (STAR_DESC *SDPtr, DECODE_REF fh)
+{
+ cread_16s(fh, &SDPtr->star_pt.x);
+ cread_16s(fh, &SDPtr->star_pt.y);
+ cread_8 (fh, &SDPtr->Type);
+ cread_8 (fh, &SDPtr->Index);
+ cread_8 (fh, &SDPtr->Prefix);
+ cread_8 (fh, &SDPtr->Postfix);
+}
+
+BOOLEAN
+LoadLegacyGame (COUNT which_game, SUMMARY_DESC *SummPtr)
+{
+ uio_Stream *in_fp;
+ char file[PATH_MAX];
+ char buf[256];
+ SUMMARY_DESC loc_sd;
+ GAME_STATE_FILE *fp;
+ DECODE_REF fh;
+ COUNT num_links;
+ STAR_DESC SD;
+ ACTIVITY Activity;
+
+ sprintf (file, "starcon2.%02u", which_game);
+ in_fp = res_OpenResFile (saveDir, file, "rb");
+ if (!in_fp)
+ return FALSE;
+
+ loc_sd.SaveName[0] = '\0';
+ if (!LoadSummary (&loc_sd, in_fp))
+ {
+ log_add (log_Error, "Warning: Savegame is corrupt");
+ res_CloseResFile (in_fp);
+ return FALSE;
+ }
+
+ if (!SummPtr)
+ {
+ SummPtr = &loc_sd;
+ }
+ else
+ { // only need summary for displaying to user
+ memcpy (SummPtr, &loc_sd, sizeof (*SummPtr));
+ res_CloseResFile (in_fp);
+ return TRUE;
+ }
+
+ // Crude check for big-endian/little-endian incompatibilities.
+ // year_index is suitable as it's a multi-byte value within
+ // a specific recognisable range.
+ if (SummPtr->year_index < START_YEAR ||
+ SummPtr->year_index >= START_YEAR +
+ YEARS_TO_KOHRAH_VICTORY + 1 /* Utwig intervention */ +
+ 1 /* time to destroy all races, plenty */ +
+ 25 /* for cheaters */)
+ {
+ log_add (log_Error, "Warning: Savegame corrupt or from "
+ "an incompatible platform.");
+ res_CloseResFile (in_fp);
+ return FALSE;
+ }
+
+ GlobData.SIS_state = SummPtr->SS;
+
+ if ((fh = copen (in_fp, FILE_STREAM, STREAM_READ)) == 0)
+ {
+ res_CloseResFile (in_fp);
+ return FALSE;
+ }
+
+ ReinitQueue (&GLOBAL (GameClock.event_q));
+ ReinitQueue (&GLOBAL (encounter_q));
+ ReinitQueue (&GLOBAL (ip_group_q));
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ ReinitQueue (&GLOBAL (built_ship_q));
+
+ memset (&GLOBAL (GameState[0]), 0, sizeof (GLOBAL (GameState)));
+ Activity = GLOBAL (CurrentActivity);
+ LoadGameState (&GlobData.Game_state, fh);
+ NextActivity = GLOBAL (CurrentActivity);
+ GLOBAL (CurrentActivity) = Activity;
+
+ LoadRaceQueue (fh, &GLOBAL (avail_race_q));
+ // START_INTERPLANETARY is only set when saving from Homeworld
+ // encounter screen. When the game is loaded, the
+ // GenerateOrbitalFunction for the current star system will
+ // create the encounter anew and populate the npc queue.
+ if (!(NextActivity & START_INTERPLANETARY))
+ {
+ if (NextActivity & START_ENCOUNTER)
+ LoadShipQueue (fh, &GLOBAL (npc_built_ship_q));
+ else if (LOBYTE (NextActivity) == IN_INTERPLANETARY)
+ // XXX: Technically, this queue does not need to be
+ // saved/loaded at all. IP groups will be reloaded
+ // from group state files. But the original code did,
+ // and so will we until we can prove we do not need to.
+ LoadGroupQueue (fh, &GLOBAL (ip_group_q));
+ else
+ // XXX: The empty queue read is only needed to maintain
+ // the savegame compatibility
+ LoadEmptyQueue (fh);
+ }
+ LoadShipQueue (fh, &GLOBAL (built_ship_q));
+
+ // Load the game events (compressed)
+ cread_16 (fh, &num_links);
+ {
+#ifdef DEBUG_LOAD
+ log_add (log_Debug, "EVENTS:");
+#endif /* DEBUG_LOAD */
+ while (num_links--)
+ {
+ HEVENT hEvent;
+ EVENT *EventPtr;
+
+ hEvent = AllocEvent ();
+ LockEvent (hEvent, &EventPtr);
+
+ LoadEvent (EventPtr, fh);
+
+#ifdef DEBUG_LOAD
+ log_add (log_Debug, "\t%u/%u/%u -- %u",
+ EventPtr->month_index,
+ EventPtr->day_index,
+ EventPtr->year_index,
+ EventPtr->func_index);
+#endif /* DEBUG_LOAD */
+ UnlockEvent (hEvent);
+ PutEvent (hEvent);
+ }
+ }
+
+ // Load the encounters (black globes in HS/QS (compressed))
+ cread_16 (fh, &num_links);
+ {
+ while (num_links--)
+ {
+ HENCOUNTER hEncounter;
+ ENCOUNTER *EncounterPtr;
+
+ hEncounter = AllocEncounter ();
+ LockEncounter (hEncounter, &EncounterPtr);
+
+ LoadEncounter (EncounterPtr, fh);
+
+ UnlockEncounter (hEncounter);
+ PutEncounter (hEncounter);
+ }
+ }
+
+ // Copy the star info file from the compressed stream
+ fp = OpenStateFile (STARINFO_FILE, "wb");
+ if (fp)
+ {
+ DWORD flen;
+
+ cread_32 (fh, &flen);
+ while (flen)
+ {
+ COUNT num_bytes;
+
+ num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen;
+ cread (buf, num_bytes, 1, fh);
+ WriteStateFile (buf, num_bytes, 1, fp);
+
+ flen -= num_bytes;
+ }
+ CloseStateFile (fp);
+ }
+
+ // Copy the defined groupinfo file from the compressed stream
+ fp = OpenStateFile (DEFGRPINFO_FILE, "wb");
+ if (fp)
+ {
+ DWORD flen;
+
+ cread_32 (fh, &flen);
+ while (flen)
+ {
+ COUNT num_bytes;
+
+ num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen;
+ cread (buf, num_bytes, 1, fh);
+ WriteStateFile (buf, num_bytes, 1, fp);
+
+ flen -= num_bytes;
+ }
+ CloseStateFile (fp);
+ }
+
+ // Copy the random groupinfo file from the compressed stream
+ fp = OpenStateFile (RANDGRPINFO_FILE, "wb");
+ if (fp)
+ {
+ DWORD flen;
+
+ cread_32 (fh, &flen);
+ while (flen)
+ {
+ COUNT num_bytes;
+
+ num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen;
+ cread (buf, num_bytes, 1, fh);
+ WriteStateFile (buf, num_bytes, 1, fp);
+
+ flen -= num_bytes;
+ }
+ CloseStateFile (fp);
+ }
+
+ LoadStarDesc (&SD, fh);
+
+ cclose (fh);
+ res_CloseResFile (in_fp);
+
+ EncounterGroup = 0;
+ EncounterRace = -1;
+
+ ReinitQueue (&race_q[0]);
+ ReinitQueue (&race_q[1]);
+ CurStarDescPtr = FindStar (NULL, &SD.star_pt, 0, 0);
+ if (!(NextActivity & START_ENCOUNTER)
+ && LOBYTE (NextActivity) == IN_INTERPLANETARY)
+ NextActivity |= START_INTERPLANETARY;
+
+ return TRUE;
+}
+
+
diff --git a/src/uqm/loadship.c b/src/uqm/loadship.c
new file mode 100644
index 0000000..3396134
--- /dev/null
+++ b/src/uqm/loadship.c
@@ -0,0 +1,200 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "build.h"
+#include "coderes.h"
+#include "corecode.h"
+#include "globdata.h"
+#include "nameref.h"
+#include "races.h"
+#include "init.h"
+
+static RESOURCE code_resources[] = {
+ NULL_RESOURCE,
+ ARILOU_CODE,
+ CHMMR_CODE,
+ HUMAN_CODE,
+ ORZ_CODE,
+ PKUNK_CODE,
+ SHOFIXTI_CODE,
+ SPATHI_CODE,
+ SUPOX_CODE,
+ THRADDASH_CODE,
+ UTWIG_CODE,
+ VUX_CODE,
+ YEHAT_CODE,
+ MELNORME_CODE,
+ DRUUGE_CODE,
+ ILWRATH_CODE,
+ MYCON_CODE,
+ SLYLANDRO_CODE,
+ UMGAH_CODE,
+ URQUAN_CODE,
+ ZOQFOTPIK_CODE,
+ SYREEN_CODE,
+ KOHR_AH_CODE,
+ ANDROSYNTH_CODE,
+ CHENJESU_CODE,
+ MMRNMHRM_CODE,
+ SIS_CODE,
+ SAMATRA_CODE,
+ URQUAN_DRONE_CODE };
+
+RACE_DESC *
+load_ship (SPECIES_ID SpeciesID, BOOLEAN LoadBattleData)
+{
+ RACE_DESC *RDPtr = 0;
+ void *CodeRef;
+
+ if (SpeciesID >= NUM_SPECIES_ID)
+ return NULL;
+
+ CodeRef = CaptureCodeRes (LoadCodeRes (code_resources[SpeciesID]),
+ &GlobData, (void **)(&RDPtr));
+
+ if (!CodeRef)
+ goto BadLoad;
+ RDPtr->CodeRef = CodeRef;
+
+ if (RDPtr->ship_info.icons_rsc != NULL_RESOURCE)
+ {
+ RDPtr->ship_info.icons = CaptureDrawable (LoadGraphic (
+ RDPtr->ship_info.icons_rsc));
+ if (!RDPtr->ship_info.icons)
+ {
+ /* goto BadLoad */
+ }
+ }
+
+ if (RDPtr->ship_info.melee_icon_rsc != NULL_RESOURCE)
+ {
+ RDPtr->ship_info.melee_icon = CaptureDrawable (LoadGraphic (
+ RDPtr->ship_info.melee_icon_rsc));
+ if (!RDPtr->ship_info.melee_icon)
+ {
+ /* goto BadLoad */
+ }
+ }
+
+ if (RDPtr->ship_info.race_strings_rsc != NULL_RESOURCE)
+ {
+ RDPtr->ship_info.race_strings = CaptureStringTable (LoadStringTable (
+ RDPtr->ship_info.race_strings_rsc));
+ if (!RDPtr->ship_info.race_strings)
+ {
+ /* goto BadLoad */
+ }
+ }
+
+ if (LoadBattleData)
+ {
+ DATA_STUFF *RawPtr = &RDPtr->ship_data;
+ if (!load_animation (RawPtr->ship,
+ RawPtr->ship_rsc[0],
+ RawPtr->ship_rsc[1],
+ RawPtr->ship_rsc[2]))
+ goto BadLoad;
+
+ if (RawPtr->weapon_rsc[0] != NULL_RESOURCE)
+ {
+ if (!load_animation (RawPtr->weapon,
+ RawPtr->weapon_rsc[0],
+ RawPtr->weapon_rsc[1],
+ RawPtr->weapon_rsc[2]))
+ goto BadLoad;
+ }
+
+ if (RawPtr->special_rsc[0] != NULL_RESOURCE)
+ {
+ if (!load_animation (RawPtr->special,
+ RawPtr->special_rsc[0],
+ RawPtr->special_rsc[1],
+ RawPtr->special_rsc[2]))
+ goto BadLoad;
+ }
+
+ if (RawPtr->captain_control.captain_rsc != NULL_RESOURCE)
+ {
+ RawPtr->captain_control.background = CaptureDrawable (LoadGraphic (
+ RawPtr->captain_control.captain_rsc));
+ if (!RawPtr->captain_control.background)
+ goto BadLoad;
+ }
+
+ if (RawPtr->victory_ditty_rsc != NULL_RESOURCE)
+ {
+ RawPtr->victory_ditty =
+ LoadMusic (RawPtr->victory_ditty_rsc);
+ if (!RawPtr->victory_ditty)
+ goto BadLoad;
+ }
+
+ if (RawPtr->ship_sounds_rsc != NULL_RESOURCE)
+ {
+ RawPtr->ship_sounds = CaptureSound (
+ LoadSound (RawPtr->ship_sounds_rsc));
+ if (!RawPtr->ship_sounds)
+ goto BadLoad;
+ }
+ }
+
+ExitFunc:
+ return RDPtr;
+
+ // TODO: We should really free the resources that did load here
+BadLoad:
+ if (CodeRef)
+ DestroyCodeRes (ReleaseCodeRes (CodeRef));
+
+ RDPtr = 0; /* failed */
+
+ goto ExitFunc;
+}
+
+void
+free_ship (RACE_DESC *raceDescPtr, BOOLEAN FreeIconData,
+ BOOLEAN FreeBattleData)
+{
+ if (raceDescPtr->uninit_func != NULL)
+ (*raceDescPtr->uninit_func) (raceDescPtr);
+
+ if (FreeBattleData)
+ {
+ DATA_STUFF *shipData = &raceDescPtr->ship_data;
+
+ free_image (shipData->special);
+ free_image (shipData->weapon);
+ free_image (shipData->ship);
+
+ DestroyDrawable (
+ ReleaseDrawable (shipData->captain_control.background));
+ DestroyMusic (shipData->victory_ditty);
+ DestroySound (ReleaseSound (shipData->ship_sounds));
+ }
+
+ if (FreeIconData)
+ {
+ SHIP_INFO *shipInfo = &raceDescPtr->ship_info;
+
+ DestroyDrawable (ReleaseDrawable (shipInfo->melee_icon));
+ DestroyDrawable (ReleaseDrawable (shipInfo->icons));
+ DestroyStringTable (ReleaseStringTable (shipInfo->race_strings));
+ }
+
+ DestroyCodeRes (ReleaseCodeRes (raceDescPtr->CodeRef));
+}
diff --git a/src/uqm/master.c b/src/uqm/master.c
new file mode 100644
index 0000000..5c35aab
--- /dev/null
+++ b/src/uqm/master.c
@@ -0,0 +1,217 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "master.h"
+
+#include "build.h"
+#include "resinst.h"
+#include "displist.h"
+#include "supermelee/melee.h"
+
+
+QUEUE master_q;
+
+void
+LoadMasterShipList (void (* YieldProcessing)(void))
+{
+ COUNT num_entries;
+ SPECIES_ID s_id = ARILOU_ID;
+ num_entries = LAST_MELEE_ID - ARILOU_ID + 1;
+ InitQueue (&master_q, num_entries, sizeof (MASTER_SHIP_INFO));
+ while (num_entries--)
+ {
+ HMASTERSHIP hBuiltShip;
+ char *builtName;
+ HMASTERSHIP hStarShip, hNextShip;
+ MASTER_SHIP_INFO *BuiltPtr;
+ RACE_DESC *RDPtr;
+
+ hBuiltShip = AllocLink (&master_q);
+ if (!hBuiltShip)
+ continue;
+
+ // Allow other things to run
+ // supposedly, loading ship packages and data takes some time
+ if (YieldProcessing)
+ YieldProcessing ();
+
+ BuiltPtr = LockMasterShip (&master_q, hBuiltShip);
+ BuiltPtr->SpeciesID = s_id++;
+ RDPtr = load_ship (BuiltPtr->SpeciesID, FALSE);
+ if (!RDPtr)
+ {
+ UnlockMasterShip (&master_q, hBuiltShip);
+ continue;
+ }
+
+ // Grab a copy of loaded icons, strings and info
+ // XXX: SHIP_INFO implicitly referenced here
+ BuiltPtr->ShipInfo = RDPtr->ship_info;
+ BuiltPtr->Fleet = RDPtr->fleet;
+ free_ship (RDPtr, FALSE, FALSE);
+
+ builtName = GetStringAddress (SetAbsStringTableIndex (
+ BuiltPtr->ShipInfo.race_strings, 2));
+ UnlockMasterShip (&master_q, hBuiltShip);
+
+ // Insert the ship in the master queue in the right location
+ // to keep the list sorted on the name of the race.
+ for (hStarShip = GetHeadLink (&master_q);
+ hStarShip; hStarShip = hNextShip)
+ {
+ char *curName;
+ MASTER_SHIP_INFO *MasterPtr;
+
+ MasterPtr = LockMasterShip (&master_q, hStarShip);
+ hNextShip = _GetSuccLink (MasterPtr);
+ curName = GetStringAddress (SetAbsStringTableIndex (
+ MasterPtr->ShipInfo.race_strings, 2));
+ UnlockMasterShip (&master_q, hStarShip);
+
+ if (strcmp (builtName, curName) < 0)
+ break;
+ }
+ InsertQueue (&master_q, hBuiltShip, hStarShip);
+ }
+}
+
+void
+FreeMasterShipList (void)
+{
+ HMASTERSHIP hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&master_q);
+ hStarShip != 0; hStarShip = hNextShip)
+ {
+ MASTER_SHIP_INFO *MasterPtr;
+
+ MasterPtr = LockMasterShip (&master_q, hStarShip);
+ hNextShip = _GetSuccLink (MasterPtr);
+
+ DestroyDrawable (ReleaseDrawable (MasterPtr->ShipInfo.melee_icon));
+ DestroyDrawable (ReleaseDrawable (MasterPtr->ShipInfo.icons));
+ DestroyStringTable (ReleaseStringTable (
+ MasterPtr->ShipInfo.race_strings));
+
+ UnlockMasterShip (&master_q, hStarShip);
+ }
+
+ UninitQueue (&master_q);
+}
+
+HMASTERSHIP
+FindMasterShip (SPECIES_ID ship_ref)
+{
+ HMASTERSHIP hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&master_q); hStarShip; hStarShip = hNextShip)
+ {
+ SPECIES_ID ref;
+ MASTER_SHIP_INFO *MasterPtr;
+
+ MasterPtr = LockMasterShip (&master_q, hStarShip);
+ hNextShip = _GetSuccLink (MasterPtr);
+ ref = MasterPtr->SpeciesID;
+ UnlockMasterShip (&master_q, hStarShip);
+
+ if (ref == ship_ref)
+ break;
+ }
+
+ return (hStarShip);
+}
+
+int
+FindMasterShipIndex (SPECIES_ID ship_ref)
+{
+ HMASTERSHIP hStarShip, hNextShip;
+ int index;
+
+ for (index = 0, hStarShip = GetHeadLink (&master_q); hStarShip;
+ ++index, hStarShip = hNextShip)
+ {
+ SPECIES_ID ref;
+ MASTER_SHIP_INFO *MasterPtr;
+
+ MasterPtr = LockMasterShip (&master_q, hStarShip);
+ hNextShip = _GetSuccLink (MasterPtr);
+ ref = MasterPtr->SpeciesID;
+ UnlockMasterShip (&master_q, hStarShip);
+
+ if (ref == ship_ref)
+ break;
+ }
+
+ return hStarShip ? index : -1;
+}
+
+COUNT
+GetShipCostFromIndex (unsigned Index)
+{
+ HMASTERSHIP hMasterShip;
+ MASTER_SHIP_INFO *MasterPtr;
+ COUNT val;
+
+ hMasterShip = GetStarShipFromIndex (&master_q, Index);
+ if (!hMasterShip)
+ return 0;
+
+ MasterPtr = LockMasterShip (&master_q, hMasterShip);
+ val = MasterPtr->ShipInfo.ship_cost;
+ UnlockMasterShip (&master_q, hMasterShip);
+
+ return val;
+}
+
+FRAME
+GetShipIconsFromIndex (unsigned Index)
+{
+ HMASTERSHIP hMasterShip;
+ MASTER_SHIP_INFO *MasterPtr;
+ FRAME val;
+
+ hMasterShip = GetStarShipFromIndex (&master_q, Index);
+ if (!hMasterShip)
+ return 0;
+
+ MasterPtr = LockMasterShip (&master_q, hMasterShip);
+ val = MasterPtr->ShipInfo.icons;
+ UnlockMasterShip (&master_q, hMasterShip);
+
+ return val;
+}
+
+FRAME
+GetShipMeleeIconsFromIndex (unsigned Index)
+{
+ HMASTERSHIP hMasterShip;
+ MASTER_SHIP_INFO *MasterPtr;
+ FRAME val;
+
+ hMasterShip = GetStarShipFromIndex (&master_q, Index);
+ if (!hMasterShip)
+ return 0;
+
+ MasterPtr = LockMasterShip (&master_q, hMasterShip);
+ val = MasterPtr->ShipInfo.melee_icon;
+ UnlockMasterShip (&master_q, hMasterShip);
+
+ return val;
+}
+
+
diff --git a/src/uqm/master.h b/src/uqm/master.h
new file mode 100644
index 0000000..adcf6e5
--- /dev/null
+++ b/src/uqm/master.h
@@ -0,0 +1,69 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_MASTER_H_
+#define UQM_MASTER_H_
+
+#include "races.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef HLINK HMASTERSHIP;
+
+typedef struct
+{
+ // LINK elements; must be first
+ HMASTERSHIP pred;
+ HMASTERSHIP succ;
+
+ SPECIES_ID SpeciesID;
+
+ SHIP_INFO ShipInfo;
+ FLEET_STUFF Fleet;
+ // FLEET_STUFF is only necessary here because avail_race_q
+ // is initialized in part from master_q (kinda hacky)
+} MASTER_SHIP_INFO;
+
+extern QUEUE master_q;
+ /* List of ships available in SuperMelee;
+ * queue element is MASTER_SHIP_INFO */
+
+static inline MASTER_SHIP_INFO *
+LockMasterShip (const QUEUE *pq, HMASTERSHIP h)
+{
+ assert (GetLinkSize (pq) == sizeof (MASTER_SHIP_INFO));
+ return (MASTER_SHIP_INFO *) LockLink (pq, h);
+}
+
+#define UnlockMasterShip(pq, h) UnlockLink (pq, h)
+#define FreeMasterShip(pq, h) FreeLink (pq, h)
+
+extern void LoadMasterShipList (void (* YieldProcessing)(void));
+extern void FreeMasterShipList (void);
+extern HMASTERSHIP FindMasterShip (SPECIES_ID ship_ref);
+extern int FindMasterShipIndex (SPECIES_ID ship_ref);
+COUNT GetShipCostFromIndex (unsigned Index);
+FRAME GetShipIconsFromIndex (unsigned Index);
+FRAME GetShipMeleeIconsFromIndex (unsigned Index);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_MASTER_H_ */
diff --git a/src/uqm/menu.c b/src/uqm/menu.c
new file mode 100644
index 0000000..fc46e3b
--- /dev/null
+++ b/src/uqm/menu.c
@@ -0,0 +1,603 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "menustat.h"
+
+#include "colors.h"
+#include "controls.h"
+#include "units.h"
+#include "options.h"
+#include "setup.h"
+#include "gamestr.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/tasklib.h"
+#include "libs/log.h"
+
+static BYTE GetEndMenuState (BYTE BaseState);
+static BYTE GetBeginMenuState (BYTE BaseState);
+static BYTE FixMenuState (BYTE BadState);
+static BYTE NextMenuState (BYTE BaseState, BYTE CurState);
+static BYTE PreviousMenuState (BYTE BaseState, BYTE CurState);
+static BOOLEAN GetAlternateMenu (BYTE *BaseState, BYTE *CurState);
+static BYTE ConvertAlternateMenu (BYTE BaseState, BYTE NewState);
+
+
+/* Draw the blue background for PC Menu Text, with a border around it.
+ * The specified rectangle includes the border. */
+static void
+DrawPCMenuFrame (RECT *r)
+{
+ // Draw the top and left of the outer border.
+ // This actually draws a rectangle, but the bottom and right parts
+ // are drawn over in the next step.
+ SetContextForeGroundColor (PCMENU_TOP_LEFT_BORDER_COLOR);
+ DrawRectangle (r);
+
+ // Draw the right and bottom of the outer border.
+ // This actually draws a rectangle, with the top and left segments just
+ // within the total area, but these segments are drawn over in the next
+ // step.
+ r->corner.x++;
+ r->corner.y++;
+ r->extent.height--;
+ r->extent.width--;
+ SetContextForeGroundColor (PCMENU_BOTTOM_RIGHT_BORDER_COLOR);
+ DrawRectangle (r);
+
+ // Draw the background.
+ r->extent.height--;
+ r->extent.width--;
+ SetContextForeGroundColor (PCMENU_BACKGROUND_COLOR);
+ DrawFilledRectangle (r);
+}
+
+#define ALT_MANIFEST 0x80
+#define ALT_EXIT_MANIFEST 0x81
+
+static UNICODE pm_crew_str[128];
+static UNICODE pm_fuel_str[128];
+
+/* Actually display the menu text */
+static void
+DrawPCMenu (BYTE beg_index, BYTE end_index, BYTE NewState, BYTE hilite, RECT *r)
+{
+#define PC_MENU_HEIGHT 8
+ BYTE pos;
+ COUNT i;
+ int num_items;
+ FONT OldFont;
+ TEXT t;
+ UNICODE buf[256];
+
+ pos = beg_index + NewState;
+ num_items = 1 + end_index - beg_index;
+ r->corner.x -= 1;
+ r->extent.width += 1;
+ DrawFilledRectangle (r);
+ if (num_items * PC_MENU_HEIGHT > r->extent.height)
+ log_add (log_Error, "Warning, no room for all menu items!");
+ else
+ r->corner.y += (r->extent.height - num_items * PC_MENU_HEIGHT) / 2;
+ r->extent.height = num_items * PC_MENU_HEIGHT + 4;
+ DrawPCMenuFrame (r);
+ OldFont = SetContextFont (StarConFont);
+ t.align = ALIGN_LEFT;
+ t.baseline.x = r->corner.x + 2;
+ t.baseline.y = r->corner.y + PC_MENU_HEIGHT -1;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ r->corner.x++;
+ r->extent.width -= 2;
+ for (i = beg_index; i <= end_index; i++)
+ {
+ utf8StringCopy (buf, sizeof buf,
+ (i == PM_FUEL) ? pm_fuel_str :
+ (i == PM_CREW) ? pm_crew_str :
+ GAME_STRING (MAINMENU_STRING_BASE + i));
+ if (hilite && pos == i)
+ {
+ // Currently selected menu option.
+
+ // Draw the background of the selection.
+ SetContextForeGroundColor (PCMENU_SELECTION_BACKGROUND_COLOR);
+ r->corner.y = t.baseline.y - PC_MENU_HEIGHT + 2;
+ r->extent.height = PC_MENU_HEIGHT - 1;
+ DrawFilledRectangle (r);
+
+ // Draw the text of the selected item.
+ SetContextForeGroundColor (PCMENU_SELECTION_TEXT_COLOR);
+ font_DrawText (&t);
+ }
+ else
+ {
+ // Draw the text of an unselected item.
+ SetContextForeGroundColor (PCMENU_TEXT_COLOR);
+ font_DrawText (&t);
+ }
+ t.baseline.y += PC_MENU_HEIGHT;
+ }
+ SetContextFont (OldFont);
+}
+
+/* Determine the last text item to display */
+static BYTE
+GetEndMenuState (BYTE BaseState)
+{
+ switch (BaseState)
+ {
+ case PM_SCAN:
+ case PM_STARMAP:
+ return PM_NAVIGATE;
+ break;
+ case PM_MIN_SCAN:
+ return PM_LAUNCH_LANDER;
+ break;
+ case PM_SAVE_GAME:
+ return PM_EXIT_GAME_MENU;
+ break;
+ case PM_CONVERSE:
+ return PM_ENCOUNTER_GAME_MENU;
+ break;
+ case PM_FUEL:
+ return PM_EXIT_OUTFIT;
+ break;
+ case PM_CREW:
+ return PM_EXIT_SHIPYARD;
+ break;
+ case PM_SOUND_ON:
+ return PM_EXIT_SETTINGS;
+ break;
+ case PM_ALT_SCAN:
+ case PM_ALT_STARMAP:
+ return PM_ALT_NAVIGATE;
+ break;
+ case PM_ALT_CARGO:
+ return PM_ALT_EXIT_MANIFEST;
+ break;
+ case PM_ALT_MSCAN:
+ return PM_ALT_EXIT_SCAN;
+ break;
+ }
+ return BaseState;
+}
+
+static BYTE
+GetBeginMenuState (BYTE BaseState)
+{
+ return BaseState;
+}
+
+/* Correct Menu State for cases where the Menu shouldn't move */
+static BYTE
+FixMenuState (BYTE BadState)
+{
+ switch (BadState)
+ {
+ case PM_SOUND_ON:
+ if (GLOBAL (glob_flags) & SOUND_DISABLED)
+ return PM_SOUND_OFF;
+ else
+ return PM_SOUND_ON;
+ case PM_MUSIC_ON:
+ if (GLOBAL (glob_flags) & MUSIC_DISABLED)
+ return PM_MUSIC_OFF;
+ else
+ return PM_MUSIC_ON;
+ case PM_CYBORG_OFF:
+ return (PM_CYBORG_OFF +
+ ((BYTE)(GLOBAL (glob_flags) & COMBAT_SPEED_MASK) >>
+ COMBAT_SPEED_SHIFT));
+ }
+ return BadState;
+}
+
+/* Choose the next menu to hilight in the 'forward' direction */
+static BYTE
+NextMenuState (BYTE BaseState, BYTE CurState)
+{
+ BYTE NextState;
+ BYTE AdjBase = BaseState;
+
+ if (BaseState == PM_STARMAP)
+ AdjBase--;
+
+ switch (AdjBase + CurState)
+ {
+ case PM_SOUND_ON:
+ case PM_SOUND_OFF:
+ NextState = PM_MUSIC_ON;
+ break;
+ case PM_MUSIC_ON:
+ case PM_MUSIC_OFF:
+ NextState = PM_CYBORG_OFF;
+ break;
+ case PM_CYBORG_OFF:
+ case PM_CYBORG_NORMAL:
+ case PM_CYBORG_DOUBLE:
+ case PM_CYBORG_SUPER:
+ NextState = PM_CHANGE_CAPTAIN;
+ break;
+ default:
+ NextState = AdjBase + CurState + 1;
+ }
+ if (NextState > GetEndMenuState (BaseState))
+ NextState = GetBeginMenuState (BaseState);
+ return (FixMenuState (NextState) - AdjBase);
+}
+
+/* Choose the next menu to hilight in the 'back' direction */
+BYTE
+PreviousMenuState (BYTE BaseState, BYTE CurState)
+{
+ SWORD NextState;
+ BYTE AdjBase = BaseState;
+
+ if (BaseState == PM_STARMAP)
+ AdjBase--;
+
+ switch (AdjBase + CurState)
+ {
+ case PM_SOUND_OFF:
+ NextState = PM_EXIT_SETTINGS;
+ break;
+ case PM_MUSIC_ON:
+ case PM_MUSIC_OFF:
+ NextState = PM_SOUND_ON;
+ break;
+ case PM_CYBORG_OFF:
+ case PM_CYBORG_NORMAL:
+ case PM_CYBORG_DOUBLE:
+ case PM_CYBORG_SUPER:
+ NextState = PM_MUSIC_ON;
+ break;
+ case PM_CHANGE_CAPTAIN:
+ NextState = PM_CYBORG_OFF;
+ break;
+ default:
+ NextState = AdjBase + CurState - 1;
+ }
+ if (NextState < GetBeginMenuState (BaseState))
+ NextState = GetEndMenuState (BaseState);
+ return (FixMenuState ((BYTE)NextState) - AdjBase);
+}
+
+
+/* When using PC hierarchy, convert 3do->PC */
+static BOOLEAN
+GetAlternateMenu (BYTE *BaseState, BYTE *CurState)
+{
+ BYTE AdjBase = *BaseState;
+ BYTE adj = 0;
+ if (*BaseState == PM_STARMAP)
+ {
+ AdjBase--;
+ adj = 1;
+ }
+ if (*CurState & 0x80)
+ {
+ switch (*CurState)
+ {
+ case ALT_MANIFEST:
+ *BaseState = PM_ALT_SCAN + adj;
+ *CurState = PM_ALT_MANIFEST - PM_ALT_SCAN - adj;
+ return TRUE;
+ case ALT_EXIT_MANIFEST:
+ *BaseState = PM_ALT_CARGO;
+ *CurState = PM_ALT_EXIT_MANIFEST - PM_ALT_CARGO;
+ return TRUE;
+ }
+ log_add (log_Error, "Unknown state combination: %d, %d",
+ *BaseState, *CurState);
+ return FALSE;
+ }
+ else
+ {
+ switch (AdjBase + *CurState)
+ {
+ case PM_SCAN:
+ *BaseState = PM_ALT_SCAN;
+ *CurState = PM_ALT_SCAN - PM_ALT_SCAN;
+ return TRUE;
+ case PM_STARMAP:
+ *BaseState = PM_ALT_SCAN + adj;
+ *CurState = PM_ALT_STARMAP - PM_ALT_SCAN - adj;
+ return TRUE;
+ case PM_DEVICES:
+ *BaseState = PM_ALT_CARGO;
+ *CurState = PM_ALT_DEVICES - PM_ALT_CARGO;
+ return TRUE;
+ case PM_CARGO:
+ *BaseState = PM_ALT_CARGO;
+ *CurState = PM_ALT_CARGO - PM_ALT_CARGO;
+ return TRUE;
+ case PM_ROSTER:
+ *BaseState = PM_ALT_CARGO;
+ *CurState = PM_ALT_ROSTER - PM_ALT_CARGO;
+ return TRUE;
+ case PM_GAME_MENU:
+ *BaseState = PM_ALT_SCAN + adj;
+ *CurState = PM_ALT_GAME_MENU - PM_ALT_SCAN - adj;
+ return TRUE;
+ case PM_NAVIGATE:
+ *BaseState = PM_ALT_SCAN + adj;
+ *CurState = PM_ALT_NAVIGATE - PM_ALT_SCAN - adj;
+ return TRUE;
+ case PM_MIN_SCAN:
+ *BaseState = PM_ALT_MSCAN;
+ *CurState = PM_ALT_MSCAN - PM_ALT_MSCAN;
+ return TRUE;
+ case PM_ENE_SCAN:
+ *BaseState = PM_ALT_MSCAN;
+ *CurState = PM_ALT_ESCAN - PM_ALT_MSCAN;
+ return TRUE;
+ case PM_BIO_SCAN:
+ *BaseState = PM_ALT_MSCAN;
+ *CurState = PM_ALT_BSCAN - PM_ALT_MSCAN;
+ return TRUE;
+ case PM_EXIT_SCAN:
+ *BaseState = PM_ALT_MSCAN;
+ *CurState = PM_ALT_EXIT_SCAN - PM_ALT_MSCAN;
+ return TRUE;
+ case PM_AUTO_SCAN:
+ *BaseState = PM_ALT_MSCAN;
+ *CurState = PM_ALT_ASCAN - PM_ALT_MSCAN;
+ return TRUE;
+ case PM_LAUNCH_LANDER:
+ *BaseState = PM_ALT_MSCAN;
+ *CurState = PM_ALT_DISPATCH - PM_ALT_MSCAN;
+ return TRUE;
+ }
+ return FALSE;
+ }
+}
+
+/* When using PC hierarchy, convert PC->3DO */
+static BYTE
+ConvertAlternateMenu (BYTE BaseState, BYTE NewState)
+{
+ switch (BaseState + NewState)
+ {
+ case PM_ALT_SCAN:
+ return (PM_SCAN - PM_SCAN);
+ case PM_ALT_STARMAP:
+ return (PM_STARMAP - PM_SCAN);
+ case PM_ALT_MANIFEST:
+ return (ALT_MANIFEST);
+ case PM_ALT_GAME_MENU:
+ return (PM_GAME_MENU - PM_SCAN);
+ case PM_ALT_NAVIGATE:
+ return (PM_NAVIGATE - PM_SCAN);
+ case PM_ALT_CARGO:
+ return (PM_CARGO - PM_SCAN);
+ case PM_ALT_DEVICES:
+ return (PM_DEVICES - PM_SCAN);
+ case PM_ALT_ROSTER:
+ return (PM_ROSTER - PM_SCAN);
+ case PM_ALT_EXIT_MANIFEST:
+ return (ALT_EXIT_MANIFEST);
+ case PM_ALT_MSCAN:
+ return (PM_MIN_SCAN - PM_MIN_SCAN);
+ case PM_ALT_ESCAN:
+ return (PM_ENE_SCAN - PM_MIN_SCAN);
+ case PM_ALT_BSCAN:
+ return (PM_BIO_SCAN - PM_MIN_SCAN);
+ case PM_ALT_ASCAN:
+ return (PM_AUTO_SCAN - PM_MIN_SCAN);
+ case PM_ALT_DISPATCH:
+ return (PM_LAUNCH_LANDER - PM_MIN_SCAN);
+ case PM_ALT_EXIT_SCAN:
+ return (PM_EXIT_SCAN - PM_MIN_SCAN);
+ }
+ return (NewState);
+}
+
+BOOLEAN
+DoMenuChooser (MENU_STATE *pMS, BYTE BaseState)
+{
+ BYTE NewState = pMS->CurState;
+ BYTE OrigBase = BaseState;
+ BOOLEAN useAltMenu = FALSE;
+ if (optWhichMenu == OPT_PC)
+ useAltMenu = GetAlternateMenu (&BaseState, &NewState);
+ if (PulsedInputState.menu[KEY_MENU_LEFT] ||
+ PulsedInputState.menu[KEY_MENU_UP])
+ NewState = PreviousMenuState (BaseState, NewState);
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT] ||
+ PulsedInputState.menu[KEY_MENU_DOWN])
+ NewState = NextMenuState (BaseState, NewState);
+ else if (useAltMenu && PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ NewState = ConvertAlternateMenu (BaseState, NewState);
+ if (NewState == ALT_MANIFEST)
+ {
+ DrawMenuStateStrings (PM_ALT_CARGO, 0);
+ pMS->CurState = PM_CARGO - PM_SCAN;
+ return TRUE;
+ }
+ if (NewState == ALT_EXIT_MANIFEST)
+ {
+ if (OrigBase == PM_SCAN)
+ DrawMenuStateStrings (PM_ALT_SCAN,
+ PM_ALT_MANIFEST - PM_ALT_SCAN);
+ else
+ DrawMenuStateStrings (PM_ALT_STARMAP,
+ PM_ALT_MANIFEST - PM_ALT_STARMAP);
+ pMS->CurState = ALT_MANIFEST;
+ return TRUE;
+ }
+ return FALSE;
+ }
+ else if ((optWhichMenu == OPT_PC) &&
+ PulsedInputState.menu[KEY_MENU_CANCEL] &&
+ (BaseState == PM_ALT_CARGO))
+ {
+ if (OrigBase == PM_SCAN)
+ DrawMenuStateStrings (PM_ALT_SCAN,
+ PM_ALT_MANIFEST - PM_ALT_SCAN);
+ else
+ DrawMenuStateStrings (PM_ALT_STARMAP,
+ PM_ALT_MANIFEST - PM_ALT_STARMAP);
+ pMS->CurState = ALT_MANIFEST;
+ return TRUE;
+ }
+ else
+ return FALSE;
+
+ DrawMenuStateStrings (BaseState, NewState);
+ if (useAltMenu)
+ NewState = ConvertAlternateMenu (BaseState, NewState);
+ pMS->CurState = NewState;
+ SleepThread (ONE_SECOND / 20);
+ return TRUE;
+}
+
+void
+DrawMenuStateStrings (BYTE beg_index, SWORD NewState)
+{
+ BYTE end_index;
+ RECT r;
+ STAMP s;
+ CONTEXT OldContext;
+ BYTE hilite = 1;
+ extern FRAME PlayFrame;
+
+ if (NewState < 0)
+ {
+ NewState = -NewState;
+ hilite = 0;
+ }
+
+ if (optWhichMenu == OPT_PC)
+ {
+ BYTE tmpState = (BYTE)NewState;
+ GetAlternateMenu (&beg_index, &tmpState);
+ NewState = tmpState;
+ }
+
+ if (beg_index == PM_STARMAP)
+ NewState--;
+ end_index = GetEndMenuState (beg_index);
+
+ s.frame = 0;
+ if (NewState <= end_index - beg_index)
+ s.frame = SetAbsFrameIndex (PlayFrame, beg_index + NewState);
+
+ PreUpdateFlashRect ();
+ OldContext = SetContext (StatusContext);
+ GetContextClipRect (&r);
+ s.origin.x = RADAR_X - r.corner.x;
+ s.origin.y = RADAR_Y - r.corner.y;
+ r.corner.x = s.origin.x - 1;
+ r.corner.y = s.origin.y - 11;
+ r.extent.width = RADAR_WIDTH + 2;
+ BatchGraphics ();
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ if (s.frame && optWhichMenu == OPT_PC)
+ {
+ if (beg_index == PM_CREW)
+ snprintf (pm_crew_str, sizeof pm_crew_str, "%s(%d)",
+ GAME_STRING (MAINMENU_STRING_BASE + PM_CREW),
+ GLOBAL (CrewCost));
+ if (beg_index == PM_FUEL)
+ snprintf (pm_fuel_str, sizeof pm_fuel_str, "%s(%d)",
+ GAME_STRING (MAINMENU_STRING_BASE + PM_FUEL),
+ GLOBAL (FuelCost));
+ if (beg_index == PM_SOUND_ON)
+ {
+ end_index = beg_index + 5;
+ switch (beg_index + NewState)
+ {
+ case PM_SOUND_ON:
+ case PM_SOUND_OFF:
+ NewState = 0;
+ break;
+ case PM_MUSIC_ON:
+ case PM_MUSIC_OFF:
+ NewState = 1;
+ break;
+ case PM_CYBORG_OFF:
+ case PM_CYBORG_NORMAL:
+ case PM_CYBORG_DOUBLE:
+ case PM_CYBORG_SUPER:
+ NewState = 2;
+ break;
+ case PM_CHANGE_CAPTAIN:
+ NewState = 3;
+ break;
+ case PM_CHANGE_SHIP:
+ NewState = 4;
+ break;
+ case PM_EXIT_SETTINGS:
+ NewState = 5;
+ break;
+ }
+ }
+ r.extent.height = RADAR_HEIGHT + 11;
+ DrawPCMenu (beg_index, end_index, (BYTE)NewState, hilite, &r);
+ s.frame = 0;
+ }
+ else
+ {
+ if (optWhichMenu == OPT_PC)
+ {
+ r.corner.x -= 1;
+ r.extent.width += 1;
+ r.extent.height = RADAR_HEIGHT + 11;
+ }
+ else
+ r.extent.height = 11;
+ DrawFilledRectangle (&r);
+ }
+ if (s.frame)
+ {
+ DrawStamp (&s);
+ switch (beg_index + NewState)
+ {
+ TEXT t;
+ UNICODE buf[20];
+
+ case PM_CREW:
+ t.baseline.x = s.origin.x + RADAR_WIDTH - 2;
+ t.baseline.y = s.origin.y + RADAR_HEIGHT - 2;
+ t.align = ALIGN_RIGHT;
+ t.CharCount = (COUNT)~0;
+ t.pStr = buf;
+ snprintf (buf, sizeof buf, "%u", GLOBAL (CrewCost));
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (THREEDOMENU_TEXT_COLOR);
+ font_DrawText (&t);
+ break;
+ case PM_FUEL:
+ t.baseline.x = s.origin.x + RADAR_WIDTH - 2;
+ t.baseline.y = s.origin.y + RADAR_HEIGHT - 2;
+ t.align = ALIGN_RIGHT;
+ t.CharCount = (COUNT)~0;
+ t.pStr = buf;
+ snprintf (buf, sizeof buf, "%u", GLOBAL (FuelCost));
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (THREEDOMENU_TEXT_COLOR);
+ font_DrawText (&t);
+ break;
+ }
+ }
+ UnbatchGraphics ();
+ SetContext (OldContext);
+ PostUpdateFlashRect ();
+}
+
diff --git a/src/uqm/menustat.h b/src/uqm/menustat.h
new file mode 100644
index 0000000..2aa864d
--- /dev/null
+++ b/src/uqm/menustat.h
@@ -0,0 +1,131 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_MENUSTAT_H_
+#define UQM_MENUSTAT_H_
+
+#include "libs/gfxlib.h"
+#include "libs/sndlib.h"
+#include "flash.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct menu_state
+{
+ // Standard field required by DoInput()
+ BOOLEAN (*InputFunc) (struct menu_state *pMS);
+
+ SIZE Initialized;
+
+ BYTE CurState;
+ FRAME CurFrame;
+ STRING CurString;
+ POINT first_item;
+ SIZE delta_item;
+
+ FRAME ModuleFrame;
+ RECT flash_rect0, flash_rect1;
+ FRAME flash_frame0, flash_frame1;
+ FlashContext *flashContext;
+
+ MUSIC_REF hMusic;
+
+ // For private use by various menus
+ // Usually, a menu associates its internal data struct using this
+ void *privData;
+
+} MENU_STATE;
+
+// XXX: Should probably go to menu.h (does not yet exist)
+enum
+{
+ PM_SCAN = 0,
+ PM_STARMAP,
+ PM_DEVICES,
+ PM_CARGO,
+ PM_ROSTER,
+ PM_GAME_MENU,
+ PM_NAVIGATE,
+
+ PM_MIN_SCAN,
+ PM_ENE_SCAN,
+ PM_BIO_SCAN,
+ PM_EXIT_SCAN,
+ PM_AUTO_SCAN,
+ PM_LAUNCH_LANDER,
+
+ PM_SAVE_GAME,
+ PM_LOAD_GAME,
+ PM_QUIT_GAME,
+ PM_CHANGE_SETTINGS,
+ PM_EXIT_GAME_MENU,
+
+ PM_CONVERSE,
+ PM_ATTACK,
+ PM_ENCOUNTER_GAME_MENU,
+
+ PM_FUEL,
+ PM_MODULE,
+ PM_OUTFIT_GAME_MENU,
+ PM_EXIT_OUTFIT,
+
+ PM_CREW,
+ PM_SHIPYARD_GAME_MENU,
+ PM_EXIT_SHIPYARD,
+
+ PM_SOUND_ON,
+ PM_SOUND_OFF,
+ PM_MUSIC_ON,
+ PM_MUSIC_OFF,
+ PM_CYBORG_OFF,
+ PM_CYBORG_NORMAL,
+ PM_CYBORG_DOUBLE,
+ PM_CYBORG_SUPER,
+ PM_CHANGE_CAPTAIN,
+ PM_CHANGE_SHIP,
+ PM_EXIT_SETTINGS,
+
+ PM_ALT_SCAN,
+ PM_ALT_STARMAP,
+ PM_ALT_MANIFEST,
+ PM_ALT_GAME_MENU,
+ PM_ALT_NAVIGATE,
+
+ PM_ALT_CARGO,
+ PM_ALT_DEVICES,
+ PM_ALT_ROSTER,
+ PM_ALT_EXIT_MANIFEST,
+
+ PM_ALT_MSCAN,
+ PM_ALT_ESCAN,
+ PM_ALT_BSCAN,
+ PM_ALT_ASCAN,
+ PM_ALT_DISPATCH,
+ PM_ALT_EXIT_SCAN,
+};
+
+extern BOOLEAN DoMenuChooser (MENU_STATE *pMS, BYTE BaseState);
+extern void DrawMenuStateStrings (BYTE beg_index, SWORD NewState);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_MENUSTAT_H_ */
diff --git a/src/uqm/misc.c b/src/uqm/misc.c
new file mode 100644
index 0000000..4bc728f
--- /dev/null
+++ b/src/uqm/misc.c
@@ -0,0 +1,407 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "element.h"
+#include "init.h"
+#include "races.h"
+#include "ship.h"
+#include "status.h"
+#include "setup.h"
+#include "sounds.h"
+#include "weapon.h"
+#include "libs/mathlib.h"
+
+
+void
+spawn_planet (void)
+{
+ HELEMENT hPlanetElement;
+
+ hPlanetElement = AllocElement ();
+ if (hPlanetElement)
+ {
+ ELEMENT *PlanetElementPtr;
+ extern FRAME planet[];
+
+ LockElement (hPlanetElement, &PlanetElementPtr);
+ PlanetElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ PlanetElementPtr->hit_points = 200;
+ PlanetElementPtr->state_flags = APPEARING;
+ PlanetElementPtr->life_span = NORMAL_LIFE + 1;
+ SetPrimType (&DisplayArray[PlanetElementPtr->PrimIndex], STAMP_PRIM);
+ PlanetElementPtr->current.image.farray = planet;
+ PlanetElementPtr->current.image.frame =
+ PlanetElementPtr->current.image.farray[0];
+ PlanetElementPtr->collision_func = collision;
+ PlanetElementPtr->postprocess_func =
+ (void (*) (struct element *ElementPtr))CalculateGravity;
+ ZeroVelocityComponents (&PlanetElementPtr->velocity);
+ do
+ {
+ PlanetElementPtr->current.location.x =
+ WRAP_X (DISPLAY_ALIGN_X (TFB_Random ()));
+ PlanetElementPtr->current.location.y =
+ WRAP_Y (DISPLAY_ALIGN_Y (TFB_Random ()));
+ } while (CalculateGravity (PlanetElementPtr)
+ || TimeSpaceMatterConflict (PlanetElementPtr));
+ PlanetElementPtr->mass_points = PlanetElementPtr->hit_points;
+ UnlockElement (hPlanetElement);
+
+ PutElement (hPlanetElement);
+ }
+}
+
+extern FRAME asteroid[];
+
+static void
+spawn_rubble (ELEMENT *AsteroidElementPtr)
+{
+ HELEMENT hRubbleElement;
+
+ hRubbleElement = AllocElement ();
+ if (hRubbleElement)
+ {
+ ELEMENT *RubbleElementPtr;
+
+ PutElement (hRubbleElement);
+ LockElement (hRubbleElement, &RubbleElementPtr);
+ RubbleElementPtr->playerNr = AsteroidElementPtr->playerNr;
+ RubbleElementPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ RubbleElementPtr->life_span = 5;
+ RubbleElementPtr->turn_wait = RubbleElementPtr->next_turn = 0;
+ SetPrimType (&DisplayArray[RubbleElementPtr->PrimIndex], STAMP_PRIM);
+ RubbleElementPtr->current.image.farray = asteroid;
+ RubbleElementPtr->current.image.frame =
+ SetAbsFrameIndex (asteroid[0], ANGLE_TO_FACING (FULL_CIRCLE));
+ RubbleElementPtr->current.location = AsteroidElementPtr->current.location;
+ RubbleElementPtr->preprocess_func = animation_preprocess;
+ RubbleElementPtr->death_func = spawn_asteroid;
+ UnlockElement (hRubbleElement);
+ }
+}
+
+static void
+asteroid_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ COUNT frame_index;
+
+ frame_index = GetFrameIndex (ElementPtr->current.image.frame);
+ if (ElementPtr->thrust_wait & (1 << 7))
+ --frame_index;
+ else
+ ++frame_index;
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame,
+ NORMALIZE_FACING (frame_index));
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = (unsigned char)(ElementPtr->thrust_wait & ((1 << 7) - 1));
+ }
+}
+
+void
+spawn_asteroid (ELEMENT *ElementPtr)
+{
+ HELEMENT hAsteroidElement;
+
+ if ((hAsteroidElement = AllocElement ()) == 0)
+ {
+ if (ElementPtr != 0)
+ {
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ ElementPtr->life_span = 1;
+ }
+ }
+ else
+ {
+ ELEMENT *AsteroidElementPtr;
+ COUNT val;
+
+ LockElement (hAsteroidElement, &AsteroidElementPtr);
+ AsteroidElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ AsteroidElementPtr->hit_points = 1;
+ AsteroidElementPtr->mass_points = 3;
+ AsteroidElementPtr->state_flags = APPEARING;
+ AsteroidElementPtr->life_span = NORMAL_LIFE;
+ SetPrimType (&DisplayArray[AsteroidElementPtr->PrimIndex], STAMP_PRIM);
+ if ((val = (COUNT)TFB_Random ()) & (1 << 0))
+ {
+ if (!(val & (1 << 1)))
+ AsteroidElementPtr->current.location.x = 0;
+ else
+ AsteroidElementPtr->current.location.x = LOG_SPACE_WIDTH;
+ AsteroidElementPtr->current.location.y =
+ WRAP_Y (DISPLAY_ALIGN_Y (TFB_Random ()));
+ }
+ else
+ {
+ AsteroidElementPtr->current.location.x =
+ WRAP_X (DISPLAY_ALIGN_X (TFB_Random ()));
+ if (!(val & (1 << 1)))
+ AsteroidElementPtr->current.location.y = 0;
+ else
+ AsteroidElementPtr->current.location.y = LOG_SPACE_HEIGHT;
+ }
+
+ {
+ // Using these temporary variables because the execution order
+ // of function arguments may vary per system, which may break
+ // synchronisation on network games.
+ SIZE magnitude =
+ DISPLAY_TO_WORLD (((SIZE)TFB_Random () & 7) + 4);
+ COUNT facing = (COUNT)TFB_Random ();
+ SetVelocityVector (&AsteroidElementPtr->velocity, magnitude,
+ facing);
+ }
+ AsteroidElementPtr->current.image.farray = asteroid;
+ AsteroidElementPtr->current.image.frame =
+ SetAbsFrameIndex (asteroid[0],
+ NORMALIZE_FACING (TFB_Random ()));
+ AsteroidElementPtr->turn_wait =
+ AsteroidElementPtr->thrust_wait =
+ (BYTE)TFB_Random () & (BYTE)((1 << 2) - 1);
+ AsteroidElementPtr->thrust_wait |=
+ (BYTE)TFB_Random () & (BYTE)(1 << 7);
+ AsteroidElementPtr->preprocess_func = asteroid_preprocess;
+ AsteroidElementPtr->death_func = spawn_rubble;
+ AsteroidElementPtr->collision_func = collision;
+ UnlockElement (hAsteroidElement);
+
+ PutElement (hAsteroidElement);
+ }
+}
+
+void
+do_damage (ELEMENT *ElementPtr, SIZE damage)
+{
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ {
+ if (!DeltaCrew (ElementPtr, -damage))
+ {
+ ElementPtr->life_span = 0;
+ ElementPtr->state_flags |= NONSOLID;
+ }
+ }
+ else if (!GRAVITY_MASS (ElementPtr->mass_points))
+ {
+ if ((BYTE)damage < ElementPtr->hit_points)
+ ElementPtr->hit_points -= (BYTE)damage;
+ else
+ {
+ ElementPtr->hit_points = 0;
+ ElementPtr->life_span = 0;
+ ElementPtr->state_flags |= NONSOLID;
+ }
+ }
+}
+
+#define CREW_COLOR_LOW_INTENSITY \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02)
+#define CREW_COLOR_HIGH_INTENSITY \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1E, 0x0A), 0x0A)
+void
+crew_preprocess (ELEMENT *ElementPtr)
+{
+ HELEMENT hTarget;
+
+ // Switch from dark to light or vice versa:
+ Color oldColor = GetPrimColor (&DisplayArray[ElementPtr->PrimIndex]);
+ Color newColor = sameColor (oldColor, CREW_COLOR_LOW_INTENSITY) ?
+ CREW_COLOR_HIGH_INTENSITY : CREW_COLOR_LOW_INTENSITY;
+ SetPrimColor (&DisplayArray[ElementPtr->PrimIndex], newColor);
+
+ ElementPtr->state_flags |= CHANGING;
+
+ hTarget = ElementPtr->hTarget;
+ if (hTarget == 0)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr && StarShipPtr->RaceDescPtr->ship_info.crew_level)
+ ElementPtr->hTarget = StarShipPtr->hShip;
+ else
+ {
+ COUNT facing;
+
+ facing = 0;
+ TrackShip (ElementPtr, &facing);
+ }
+ }
+
+ if (hTarget)
+ {
+#define CREW_DELTA SCALED_ONE
+ SIZE delta;
+ ELEMENT *ShipPtr;
+
+ LockElement (hTarget, &ShipPtr);
+ delta = ShipPtr->current.location.x
+ - ElementPtr->current.location.x;
+ delta = WRAP_DELTA_X (delta);
+ if (delta > 0)
+ ElementPtr->next.location.x += CREW_DELTA;
+ else if (delta < 0)
+ ElementPtr->next.location.x -= CREW_DELTA;
+
+ delta = ShipPtr->current.location.y -
+ ElementPtr->current.location.y;
+ delta = WRAP_DELTA_Y (delta);
+ if (delta > 0)
+ ElementPtr->next.location.y += CREW_DELTA;
+ else if (delta < 0)
+ ElementPtr->next.location.y -= CREW_DELTA;
+ UnlockElement (hTarget);
+ }
+}
+
+void
+crew_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if ((ElementPtr1->state_flags & PLAYER_SHIP)
+ && ElementPtr1->life_span >= NORMAL_LIFE
+ && ElementPtr0->hit_points > 0)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ if (!(StarShipPtr->RaceDescPtr->ship_info.ship_flags & CREW_IMMUNE))
+ {
+ ProcessSound (SetAbsSoundIndex (GameSounds, GRAB_CREW), ElementPtr1);
+ DeltaCrew (ElementPtr1, 1);
+ }
+ }
+
+ ElementPtr0->hit_points = 0;
+ ElementPtr0->life_span = 0;
+ ElementPtr0->state_flags |= COLLISION | DISAPPEARING | NONSOLID;
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+void
+AbandonShip (ELEMENT *ShipPtr, ELEMENT *TargetPtr,
+ COUNT crew_loss)
+{
+ SIZE dx, dy;
+ COUNT direction;
+ RECT r;
+ STARSHIP *StarShipPtr;
+ HELEMENT hCrew;
+ INTERSECT_CONTROL ShipIntersect;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->RaceDescPtr->ship_info.ship_flags & CREW_IMMUNE)
+ return;
+
+ ShipIntersect = ShipPtr->IntersectControl;
+ GetFrameRect (ShipIntersect.IntersectStamp.frame, &r);
+
+ if ((direction = GetVelocityTravelAngle (
+ &ShipPtr->velocity)) == FULL_CIRCLE)
+ dx = dy = 0;
+ else
+ {
+#define MORE_THAN_ENOUGH 100
+ direction += HALF_CIRCLE;
+ dx = COSINE (direction, MORE_THAN_ENOUGH);
+ dy = SINE (direction, MORE_THAN_ENOUGH);
+ }
+
+ while (crew_loss-- && (hCrew = AllocElement ()))
+ {
+#define CREW_LIFE 300
+ ELEMENT *CrewPtr;
+
+ DeltaCrew (ShipPtr, -1);
+
+ PutElement (hCrew);
+ LockElement (hCrew, &CrewPtr);
+ CrewPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ CrewPtr->hit_points = 1;
+ CrewPtr->state_flags = APPEARING | FINITE_LIFE | CREW_OBJECT;
+ CrewPtr->life_span = CREW_LIFE;
+ SetPrimType (&DisplayArray[CrewPtr->PrimIndex], POINT_PRIM);
+ SetPrimColor (&DisplayArray[CrewPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02));
+ CrewPtr->current.image.frame = DecFrameIndex (stars_in_space);
+ CrewPtr->current.image.farray = &stars_in_space;
+ CrewPtr->preprocess_func = crew_preprocess;
+ CrewPtr->collision_func = crew_collision;
+
+ SetElementStarShip (CrewPtr, StarShipPtr);
+
+ GetElementStarShip (TargetPtr, &StarShipPtr);
+ CrewPtr->hTarget = StarShipPtr->hShip;
+
+ {
+ SIZE w, h;
+ INTERSECT_CONTROL CrewIntersect;
+
+ ShipIntersect.IntersectStamp.origin =
+ ShipPtr->IntersectControl.EndPoint;
+
+ w = (SIZE)((COUNT)TFB_Random () % r.extent.width);
+ h = (SIZE)((COUNT)TFB_Random () % r.extent.height);
+ CrewIntersect.EndPoint = ShipIntersect.EndPoint;
+ CrewIntersect.IntersectStamp.frame = DecFrameIndex (stars_in_space);
+ if (dx == 0 && dy == 0)
+ {
+ CrewIntersect.EndPoint.x += w - (r.extent.width >> 1);
+ CrewIntersect.EndPoint.y += h - (r.extent.height >> 1);
+ CrewIntersect.IntersectStamp.origin =
+ TargetPtr->IntersectControl.EndPoint;
+ }
+ else
+ {
+ if (dx == 0)
+ CrewIntersect.EndPoint.x += w - (r.extent.width >> 1);
+ else if (dx > 0)
+ CrewIntersect.EndPoint.x += w;
+ else
+ CrewIntersect.EndPoint.x -= w;
+ if (dy == 0)
+ CrewIntersect.EndPoint.y += h - (r.extent.height >> 1);
+ else if (dy > 0)
+ CrewIntersect.EndPoint.y += h;
+ else
+ CrewIntersect.EndPoint.y -= h;
+ CrewIntersect.IntersectStamp.origin.x =
+ CrewIntersect.EndPoint.x + dx;
+ CrewIntersect.IntersectStamp.origin.y =
+ CrewIntersect.EndPoint.y + dy;
+ }
+
+ DrawablesIntersect (&CrewIntersect,
+ &ShipIntersect, MAX_TIME_VALUE);
+
+ CrewPtr->current.location.x =
+ DISPLAY_TO_WORLD (CrewIntersect.EndPoint.x);
+ CrewPtr->current.location.y =
+ DISPLAY_TO_WORLD (CrewIntersect.EndPoint.y);
+ }
+ UnlockElement (hCrew);
+ }
+}
+
diff --git a/src/uqm/nameref.h b/src/uqm/nameref.h
new file mode 100644
index 0000000..5192670
--- /dev/null
+++ b/src/uqm/nameref.h
@@ -0,0 +1,33 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_NAMEREF_H_
+#define UQM_NAMEREF_H_
+
+#include "libs/reslib.h"
+
+#define LoadCodeRes LoadCodeResInstance
+#define LoadGraphic (DRAWABLE)LoadGraphicInstance
+#define LoadFont (FONT)LoadGraphicInstance
+#define LoadColorMap LoadColorMapInstance
+#define LoadStringTable LoadStringTableInstance
+#define LoadSound LoadSoundInstance
+#define LoadMusic LoadMusicInstance
+
+#endif /* UQM_NAMEREF_H_ */
+
diff --git a/src/uqm/oscill.c b/src/uqm/oscill.c
new file mode 100644
index 0000000..7c37b64
--- /dev/null
+++ b/src/uqm/oscill.c
@@ -0,0 +1,191 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "oscill.h"
+
+#include "setup.h"
+ // for OffScreenContext
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/drawable.h"
+#include "libs/sound/sound.h"
+#include "libs/sound/trackplayer.h"
+
+
+static FRAME scope_frame;
+static int scope_init = 0;
+static FRAME scopeWork;
+static Color scopeColor;
+static EXTENT scopeSize;
+BOOLEAN oscillDisabled = FALSE;
+
+void
+InitOscilloscope (FRAME scopeBg)
+{
+ scope_frame = scopeBg;
+ if (!scope_init)
+ {
+ EXTENT size = GetFrameBounds (scope_frame);
+ POINT midPt = {size.width / 2, size.height / 2};
+
+ // mid-image pixel defines the color of scope lines
+ scopeColor = GetFramePixel (scope_frame, midPt);
+ // insist that scope lines be purely opaque
+ scopeColor.a = 0xff;
+
+ scopeWork = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP | MAPPED_TO_DISPLAY,
+ size.width, size.height, 1));
+
+ // assume and subtract the borders
+ scopeSize.width = size.width - 2;
+ scopeSize.height = size.height - 2;
+
+ scope_init = 1;
+ }
+}
+
+void
+UninitOscilloscope (void)
+{
+ // XXX: Is never called (BUG?)
+ DestroyDrawable (ReleaseDrawable (scopeWork));
+ scopeWork = NULL;
+ scope_init = 0;
+}
+
+// draws the oscilloscope
+void
+DrawOscilloscope (void)
+{
+ STAMP s;
+ BYTE scope_data[128];
+
+ if (oscillDisabled)
+ return;
+
+ assert ((size_t)scopeSize.width <= sizeof scope_data);
+ assert (scopeSize.height < 256);
+
+ if (GraphForegroundStream (scope_data, scopeSize.width, scopeSize.height,
+ usingSpeech))
+ {
+ int i;
+ CONTEXT oldContext;
+
+ oldContext = SetContext (OffScreenContext);
+ SetContextFGFrame (scopeWork);
+ SetContextClipRect (NULL);
+
+ // draw the background image
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = scope_frame;
+ DrawStamp (&s);
+
+ // draw the scope lines
+ SetContextForeGroundColor (scopeColor);
+ for (i = 0; i < scopeSize.width - 1; ++i)
+ {
+ LINE line;
+
+ line.first.x = i + 1;
+ line.first.y = scope_data[i] + 1;
+ line.second.x = i + 2;
+ line.second.y = scope_data[i + 1] + 1;
+ DrawLine (&line);
+ }
+
+ SetContext (oldContext);
+
+ s.frame = scopeWork;
+ }
+ else
+ { // no data -- draw blank scope background
+ s.frame = scope_frame;
+ }
+
+ // draw the final scope image to screen
+ s.origin.x = 0;
+ s.origin.y = 0;
+ DrawStamp (&s);
+}
+
+
+static STAMP sliderStamp;
+static STAMP buttonStamp;
+static BOOLEAN sliderChanged = FALSE;
+int sliderSpace; // slider width - button width
+BOOLEAN sliderDisabled = FALSE;
+
+/*
+ * Initialise the communication progress bar
+ * x - x location of slider
+ * y - y location of slider
+ * width - width of slider
+ * height - height of slider
+ * bwidth - width of button indicating current progress
+ * bheight - height of button indicating progress
+ * f - image for the slider
+ */
+
+void
+InitSlider (int x, int y, int width, FRAME sliderFrame, FRAME buttonFrame)
+{
+ EXTENT sliderSize = GetFrameBounds (sliderFrame);
+ EXTENT buttonSize = GetFrameBounds (buttonFrame);
+
+ sliderStamp.origin.x = x;
+ sliderStamp.origin.y = y;
+ sliderStamp.frame = sliderFrame;
+
+ buttonStamp.origin.x = x;
+ buttonStamp.origin.y = y - ((buttonSize.height - sliderSize.height) / 2);
+ buttonStamp.frame = buttonFrame;
+
+ sliderSpace = width - buttonSize.width;
+}
+
+void
+SetSliderImage (FRAME f)
+{
+ sliderChanged = TRUE;
+ buttonStamp.frame = f;
+}
+
+void
+DrawSlider (void)
+{
+ int offs;
+ static int last_offs = -1;
+
+ if (sliderDisabled)
+ return;
+
+ offs = GetTrackPosition (sliderSpace);
+ if (offs != last_offs || sliderChanged)
+ {
+ sliderChanged = FALSE;
+ last_offs = offs;
+ buttonStamp.origin.x = sliderStamp.origin.x + offs;
+ BatchGraphics ();
+ DrawStamp (&sliderStamp);
+ DrawStamp (&buttonStamp);
+ UnbatchGraphics ();
+ }
+}
+
diff --git a/src/uqm/oscill.h b/src/uqm/oscill.h
new file mode 100644
index 0000000..bd021a4
--- /dev/null
+++ b/src/uqm/oscill.h
@@ -0,0 +1,43 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_OSCILL_H_
+#define UQM_OSCILL_H_
+
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern BOOLEAN sliderDisabled;
+extern BOOLEAN oscillDisabled;
+
+extern void InitOscilloscope (FRAME scopeBg);
+extern void DrawOscilloscope (void);
+extern void UninitOscilloscope (void);
+
+extern void InitSlider (int x, int y, int width, FRAME sliderFrame,
+ FRAME buttonFrame);
+extern void SetSliderImage (FRAME f);
+void DrawSlider (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_OSCILL_H_ */
diff --git a/src/uqm/outfit.c b/src/uqm/outfit.c
new file mode 100644
index 0000000..458dfa0
--- /dev/null
+++ b/src/uqm/outfit.c
@@ -0,0 +1,795 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "options.h"
+#include "colors.h"
+#include "controls.h"
+#include "menustat.h"
+#include "gameopt.h"
+#include "gamestr.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "settings.h"
+#include "starbase.h"
+#include "setup.h"
+#include "sis.h"
+#include "units.h"
+#include "sounds.h"
+#include "planets/planets.h"
+ // for xxx_DISASTER
+#include "libs/graphics/gfx_common.h"
+
+
+enum
+{
+ OUTFIT_FUEL,
+ OUTFIT_MODULES,
+ OUTFIT_SAVELOAD,
+ OUTFIT_EXIT,
+ OUTFIT_DOFUEL
+};
+
+
+static void
+DrawModuleStrings (MENU_STATE *pMS, BYTE NewModule)
+{
+ RECT r;
+ STAMP s;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+ GetContextClipRect (&r);
+ s.origin.x = RADAR_X - r.corner.x;
+ s.origin.y = RADAR_Y - r.corner.y;
+ r.corner.x = s.origin.x - 1;
+ r.corner.y = s.origin.y - 11;
+ r.extent.width = RADAR_WIDTH + 2;
+ r.extent.height = 11;
+ BatchGraphics ();
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ DrawFilledRectangle (&r);
+ if (NewModule >= EMPTY_SLOT)
+ {
+ r.corner = s.origin;
+ r.extent.width = RADAR_WIDTH;
+ r.extent.height = RADAR_HEIGHT;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00));
+ DrawFilledRectangle (&r);
+ }
+ else if (pMS->CurFrame)
+ {
+ TEXT t;
+ UNICODE buf[40];
+
+ s.frame = SetAbsFrameIndex (pMS->CurFrame, NewModule);
+ DrawStamp (&s);
+ t.baseline.x = s.origin.x + RADAR_WIDTH - 2;
+ t.baseline.y = s.origin.y + RADAR_HEIGHT - 2;
+ t.align = ALIGN_RIGHT;
+ t.CharCount = (COUNT)~0;
+ t.pStr = buf;
+ sprintf (buf, "%u",
+ GLOBAL (ModuleCost[NewModule]) * MODULE_COST_SCALE);
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x1F, 0x00), 0x02));
+ font_DrawText (&t);
+ }
+ UnbatchGraphics ();
+ SetContext (OldContext);
+}
+
+static void
+RedistributeFuel (void)
+{
+ const DWORD FuelVolume = GLOBAL_SIS (FuelOnBoard);
+ const CONTEXT OldContext = SetContext (SpaceContext);
+ RECT r;
+ r.extent.height = 1;
+
+ // Loop through all the rows to draw
+ BatchGraphics ();
+ for (GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE;
+ GLOBAL_SIS (FuelOnBoard) < GetFTankCapacity (&r.corner);
+ GLOBAL_SIS (FuelOnBoard) += FUEL_VOLUME_PER_ROW)
+ {
+ // If we're less than the fuel level, draw fuel.
+ if (GLOBAL_SIS (FuelOnBoard) < FuelVolume)
+ {
+ r.extent.width = 3;
+ DrawPoint (&r.corner);
+ r.corner.x += r.extent.width + 1;
+ DrawPoint (&r.corner);
+ r.corner.x -= r.extent.width;
+ SetContextForeGroundColor (
+ SetContextBackGroundColor (BLACK_COLOR));
+ }
+ else // Otherwise, draw an empty bar.
+ {
+ r.extent.width = 5;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0B, 0x00, 0x00), 0x2E));
+ }
+ DrawFilledRectangle (&r);
+ }
+ UnbatchGraphics ();
+ SetContext (OldContext);
+
+ GLOBAL_SIS (FuelOnBoard) = FuelVolume;
+}
+
+#define LANDER_X 24
+#define LANDER_Y 67
+#define LANDER_WIDTH 15
+
+static void
+DisplayLanders (MENU_STATE *pMS)
+{
+ STAMP s;
+
+ s.frame = pMS->ModuleFrame;
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 3)
+ {
+ s.origin.x = s.origin.y = 0;
+ s.frame = DecFrameIndex (s.frame);
+ DrawStamp (&s);
+ }
+ else
+ {
+ COUNT i;
+
+ s.origin.x = LANDER_X;
+ s.origin.y = LANDER_Y;
+ for (i = 0; i < GLOBAL_SIS (NumLanders); ++i)
+ {
+ DrawStamp (&s);
+ s.origin.x += LANDER_WIDTH;
+ }
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ for (; i < MAX_LANDERS; ++i)
+ {
+ DrawFilledStamp (&s);
+ s.origin.x += LANDER_WIDTH;
+ }
+ }
+}
+
+static BOOLEAN
+DoInstallModule (MENU_STATE *pMS)
+{
+ BYTE NewState, new_slot_piece, old_slot_piece;
+ SIZE FirstItem, LastItem;
+ BOOLEAN select, cancel, motion;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ pMS->InputFunc = DoOutfit;
+ return (TRUE);
+ }
+
+ select = PulsedInputState.menu[KEY_MENU_SELECT];
+ cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
+ motion = PulsedInputState.menu[KEY_MENU_LEFT] ||
+ PulsedInputState.menu[KEY_MENU_RIGHT] ||
+ PulsedInputState.menu[KEY_MENU_UP] ||
+ PulsedInputState.menu[KEY_MENU_DOWN];
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ FirstItem = 0;
+ NewState = pMS->CurState;
+ switch (NewState)
+ {
+ case PLANET_LANDER:
+ case EMPTY_SLOT + 3:
+ old_slot_piece = pMS->delta_item < GLOBAL_SIS (NumLanders)
+ ? PLANET_LANDER : (EMPTY_SLOT + 3);
+ LastItem = MAX_LANDERS - 1;
+ break;
+ case FUSION_THRUSTER:
+ case EMPTY_SLOT + 0:
+ old_slot_piece = GLOBAL_SIS (DriveSlots[pMS->delta_item]);
+ LastItem = NUM_DRIVE_SLOTS - 1;
+ break;
+ case TURNING_JETS:
+ case EMPTY_SLOT + 1:
+ old_slot_piece = GLOBAL_SIS (JetSlots[pMS->delta_item]);
+ LastItem = NUM_JET_SLOTS - 1;
+ break;
+ default:
+ old_slot_piece = GLOBAL_SIS (ModuleSlots[pMS->delta_item]);
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 3)
+ FirstItem = NUM_BOMB_MODULES;
+ LastItem = NUM_MODULE_SLOTS - 1;
+ break;
+ }
+
+ if (NewState < CREW_POD)
+ FirstItem = LastItem = NewState;
+ else if (NewState < EMPTY_SLOT)
+ FirstItem = CREW_POD, LastItem = NUM_PURCHASE_MODULES - 1;
+
+ if (!pMS->Initialized)
+ {
+ new_slot_piece = old_slot_piece;
+ pMS->Initialized = TRUE;
+
+ pMS->InputFunc = DoInstallModule;
+
+
+ SetContext (SpaceContext);
+ ClearSISRect (CLEAR_SIS_RADAR);
+ SetFlashRect (NULL);
+ goto InitFlash;
+ }
+ else if (select || cancel)
+ {
+ new_slot_piece = pMS->CurState;
+ if (select)
+ {
+ if (new_slot_piece < EMPTY_SLOT)
+ {
+ if (GLOBAL_SIS (ResUnits) <
+ (DWORD)(GLOBAL (ModuleCost[new_slot_piece])
+ * MODULE_COST_SCALE))
+ { // not enough RUs to build
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return (TRUE);
+ }
+ }
+ else if (new_slot_piece == EMPTY_SLOT + 2)
+ {
+ if (old_slot_piece == CREW_POD)
+ {
+ if (GLOBAL_SIS (CrewEnlisted) > CREW_POD_CAPACITY
+ * (CountSISPieces (CREW_POD) - 1))
+ { // crew pod still needed for crew recruited
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return (TRUE);
+ }
+ }
+ else if (old_slot_piece == FUEL_TANK
+ || old_slot_piece == HIGHEFF_FUELSYS)
+ {
+ DWORD volume;
+
+ volume = (DWORD)CountSISPieces (FUEL_TANK)
+ * FUEL_TANK_CAPACITY
+ + (DWORD)CountSISPieces (HIGHEFF_FUELSYS)
+ * HEFUEL_TANK_CAPACITY;
+ volume -= (old_slot_piece == FUEL_TANK
+ ? FUEL_TANK_CAPACITY : HEFUEL_TANK_CAPACITY);
+ if (GLOBAL_SIS (FuelOnBoard) > volume + FUEL_RESERVE)
+ { // fuel tank still needed for the fuel on board
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return (TRUE);
+ }
+ }
+ else if (old_slot_piece == STORAGE_BAY)
+ {
+ if (GLOBAL_SIS (TotalElementMass) > STORAGE_BAY_CAPACITY
+ * (CountSISPieces (STORAGE_BAY) - 1))
+ { // storage bay still needed for the cargo
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return (TRUE);
+ }
+ }
+ }
+ }
+
+ SetContext (SpaceContext);
+
+ SetFlashRect (NULL);
+
+ if (select)
+ {
+ if (new_slot_piece >= EMPTY_SLOT && old_slot_piece >= EMPTY_SLOT)
+ {
+ new_slot_piece -= EMPTY_SLOT - 1;
+ if (new_slot_piece > CREW_POD)
+ new_slot_piece = PLANET_LANDER;
+ }
+ else
+ {
+ switch (pMS->CurState)
+ {
+ case PLANET_LANDER:
+ ++GLOBAL_SIS (NumLanders);
+ break;
+ case EMPTY_SLOT + 3:
+ --GLOBAL_SIS (NumLanders);
+ break;
+ case FUSION_THRUSTER:
+ case EMPTY_SLOT + 0:
+ GLOBAL_SIS (DriveSlots[pMS->delta_item]) =
+ new_slot_piece;
+ break;
+ case TURNING_JETS:
+ case EMPTY_SLOT + 1:
+ GLOBAL_SIS (JetSlots[pMS->delta_item]) =
+ new_slot_piece;
+ break;
+ default:
+ GLOBAL_SIS (ModuleSlots[pMS->delta_item]) =
+ new_slot_piece;
+ break;
+ }
+
+ if (new_slot_piece < EMPTY_SLOT)
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA,
+ -(GLOBAL (ModuleCost[new_slot_piece])
+ * MODULE_COST_SCALE));
+ else /* if (old_slot_piece < EMPTY_SLOT) */
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA,
+ GLOBAL (ModuleCost[old_slot_piece])
+ * MODULE_COST_SCALE);
+
+ if (pMS->CurState == PLANET_LANDER ||
+ pMS->CurState == EMPTY_SLOT + 3)
+ DisplayLanders (pMS);
+ else
+ {
+ DrawShipPiece (pMS->ModuleFrame, new_slot_piece,
+ pMS->delta_item, FALSE);
+
+ if (new_slot_piece > TURNING_JETS
+ && old_slot_piece > TURNING_JETS)
+ RedistributeFuel ();
+ if (optWhichFonts == OPT_PC)
+ DrawFlagshipStats ();
+ }
+ }
+
+ cancel = FALSE;
+ }
+
+ if (pMS->CurState < EMPTY_SLOT)
+ {
+ pMS->CurState += EMPTY_SLOT - 1;
+ if (pMS->CurState < EMPTY_SLOT)
+ pMS->CurState = EMPTY_SLOT + 3;
+ else if (pMS->CurState > EMPTY_SLOT + 2)
+ pMS->CurState = EMPTY_SLOT + 2;
+ if (cancel)
+ new_slot_piece = pMS->CurState;
+ goto InitFlash;
+ }
+ else if (!cancel)
+ {
+ pMS->CurState = new_slot_piece;
+ goto InitFlash;
+ }
+ else
+ {
+ SetContext (StatusContext);
+ DrawMenuStateStrings (PM_FUEL, pMS->CurState = OUTFIT_MODULES);
+ SetFlashRect (SFR_MENU_3DO);
+
+ pMS->InputFunc = DoOutfit;
+ ClearSISRect (DRAW_SIS_DISPLAY);
+ }
+ }
+ else if (motion)
+ {
+ SIZE NewItem;
+
+ NewItem = NewState < EMPTY_SLOT ? pMS->CurState : pMS->delta_item;
+ do
+ {
+ if (NewState >= EMPTY_SLOT && (PulsedInputState.menu[KEY_MENU_UP]
+ || PulsedInputState.menu[KEY_MENU_DOWN]))
+ {
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (NewState-- == EMPTY_SLOT)
+ NewState = EMPTY_SLOT + 3;
+ }
+ else
+ {
+ if (NewState++ == EMPTY_SLOT + 3)
+ NewState = EMPTY_SLOT;
+ }
+ NewItem = 0;
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 3)
+ {
+ if (NewState == EMPTY_SLOT + 3)
+ NewState = PulsedInputState.menu[KEY_MENU_UP] ?
+ EMPTY_SLOT + 2 : EMPTY_SLOT;
+ if (NewState == EMPTY_SLOT + 2)
+ NewItem = NUM_BOMB_MODULES;
+ }
+ pMS->delta_item = NewItem;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_LEFT] ||
+ PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (NewItem-- == FirstItem)
+ NewItem = LastItem;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT] ||
+ PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (NewItem++ == LastItem)
+ NewItem = FirstItem;
+ }
+ } while (NewState < EMPTY_SLOT
+ && (GLOBAL (ModuleCost[NewItem]) == 0
+ || (NewItem >= GUN_WEAPON && NewItem <= CANNON_WEAPON
+ && pMS->delta_item > 0 && pMS->delta_item < 13)));
+
+ if (NewState < EMPTY_SLOT)
+ {
+ if (NewItem != pMS->CurState)
+ {
+ pMS->CurState = NewItem;
+ PreUpdateFlashRect ();
+ DrawModuleStrings (pMS, NewItem);
+ PostUpdateFlashRect ();
+ }
+ }
+ else if (NewItem != pMS->delta_item || NewState != pMS->CurState)
+ {
+ SIZE w;
+
+ switch (NewState)
+ {
+ case PLANET_LANDER:
+ case EMPTY_SLOT + 3:
+ new_slot_piece = NewItem < GLOBAL_SIS (NumLanders)
+ ? PLANET_LANDER : (EMPTY_SLOT + 3);
+ break;
+ case FUSION_THRUSTER:
+ case EMPTY_SLOT + 0:
+ new_slot_piece = GLOBAL_SIS (DriveSlots[NewItem]);
+ break;
+ case TURNING_JETS:
+ case EMPTY_SLOT + 1:
+ new_slot_piece = GLOBAL_SIS (JetSlots[NewItem]);
+ break;
+ default:
+ new_slot_piece = GLOBAL_SIS (ModuleSlots[NewItem]);
+ break;
+ }
+
+ SetContext (SpaceContext);
+
+ if (NewState == pMS->CurState)
+ {
+ if (NewState == PLANET_LANDER || NewState == EMPTY_SLOT + 3)
+ w = LANDER_WIDTH;
+ else
+ w = SHIP_PIECE_OFFSET;
+
+ w *= (NewItem - pMS->delta_item);
+ pMS->flash_rect0.corner.x += w;
+ pMS->delta_item = NewItem;
+ }
+ else
+ {
+ pMS->CurState = NewState;
+InitFlash:
+ w = SHIP_PIECE_OFFSET;
+ switch (pMS->CurState)
+ {
+ case PLANET_LANDER:
+ case EMPTY_SLOT + 3:
+ pMS->flash_rect0.corner.x = LANDER_X - 1;
+ pMS->flash_rect0.corner.y = LANDER_Y - 1;
+ pMS->flash_rect0.extent.width = 11 + 2;
+ pMS->flash_rect0.extent.height = 13 + 2;
+
+ w = LANDER_WIDTH;
+ break;
+ case FUSION_THRUSTER:
+ case EMPTY_SLOT + 0:
+ pMS->flash_rect0.corner.x = DRIVE_TOP_X - 1;
+ pMS->flash_rect0.corner.y = DRIVE_TOP_Y - 1;
+ pMS->flash_rect0.extent.width = 8;
+ pMS->flash_rect0.extent.height = 6;
+
+ break;
+ case TURNING_JETS:
+ case EMPTY_SLOT + 1:
+ pMS->flash_rect0.corner.x = JET_TOP_X - 1;
+ pMS->flash_rect0.corner.y = JET_TOP_Y - 1;
+ pMS->flash_rect0.extent.width = 9;
+ pMS->flash_rect0.extent.height = 10;
+
+ break;
+ default:
+ pMS->flash_rect0.corner.x = MODULE_TOP_X - 1;
+ pMS->flash_rect0.corner.y = MODULE_TOP_Y - 1;
+ pMS->flash_rect0.extent.width = SHIP_PIECE_OFFSET + 2;
+ pMS->flash_rect0.extent.height = 34;
+
+ break;
+ }
+
+ w *= pMS->delta_item;
+ pMS->flash_rect0.corner.x += w;
+ }
+
+ DrawModuleStrings (pMS, new_slot_piece);
+ if (pMS->CurState < EMPTY_SLOT)
+ // flash with PC menus too
+ SetFlashRect (SFR_MENU_ANY);
+ else
+ SetFlashRect (&pMS->flash_rect0);
+ }
+ }
+
+ return (TRUE);
+}
+
+static void
+ChangeFuelQuantity (void)
+{
+ int incr = 0; // Fuel increment in fuel points (not units).
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ incr = FUEL_TANK_SCALE; // +1 Unit
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ incr = -FUEL_TANK_SCALE; // -1 Unit
+ else if (PulsedInputState.menu[KEY_MENU_PAGE_UP])
+ incr = FUEL_VOLUME_PER_ROW; // +1 Bar
+ else if (PulsedInputState.menu[KEY_MENU_PAGE_DOWN])
+ incr = -FUEL_VOLUME_PER_ROW; // -1 Bar
+ else
+ return;
+
+ // Clamp incr to what we can afford/hold/have.
+ {
+ const int maxFit = GetFuelTankCapacity () - GLOBAL_SIS (FuelOnBoard);
+ const int maxAfford = GLOBAL_SIS (ResUnits) / GLOBAL (FuelCost);
+ const int minFit = - (int) GLOBAL_SIS (FuelOnBoard);
+
+ if (incr > maxFit)
+ incr = maxFit; // All we can hold.
+
+ if (incr > maxAfford * FUEL_TANK_SCALE)
+ incr = maxAfford * FUEL_TANK_SCALE; // All we can afford.
+
+ if (incr < minFit)
+ incr = minFit; // All we have.
+ }
+
+ if (!incr)
+ {
+ // No more room, not enough RUs, or no fuel left to drain.
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ else
+ {
+ const int cost = (incr / FUEL_TANK_SCALE) * GLOBAL (FuelCost);
+ PreUpdateFlashRect ();
+ DeltaSISGauges (0, incr, -cost);
+ PostUpdateFlashRect ();
+ RedistributeFuel ();
+ }
+
+ { // Make fuel gauge flash.
+ RECT r;
+ CONTEXT oldContext = SetContext (StatusContext);
+ GetGaugeRect (&r, FALSE);
+ SetFlashRect (&r);
+ SetContext (oldContext);
+ }
+}
+
+static void
+onNamingDone (void)
+{
+ // In case player just named a ship, redraw it
+ DrawFlagshipName (FALSE);
+}
+
+BOOLEAN
+DoOutfit (MENU_STATE *pMS)
+{
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ goto ExitOutfit;
+
+ if (!pMS->Initialized)
+ {
+ pMS->InputFunc = DoOutfit;
+ pMS->Initialized = TRUE;
+
+ SetNamingCallback (onNamingDone);
+
+ {
+ COUNT num_frames;
+ STAMP s;
+
+ pMS->CurFrame = CaptureDrawable (
+ LoadGraphic (MODULES_PMAP_ANIM));
+ pMS->hMusic = LoadMusic (OUTFIT_MUSIC);
+ pMS->CurState = OUTFIT_FUEL;
+ pMS->ModuleFrame = CaptureDrawable (
+ LoadGraphic (SISMODS_MASK_PMAP_ANIM));
+ s.origin.x = s.origin.y = 0;
+ s.frame = CaptureDrawable (
+ LoadGraphic (OUTFIT_PMAP_ANIM));
+
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+ DrawSISFrame ();
+ DrawSISMessage (GAME_STRING (STARBASE_STRING_BASE + 2));
+ DrawSISTitle (GAME_STRING (STARBASE_STRING_BASE));
+
+ SetContext (SpaceContext);
+
+ DrawStamp (&s);
+ DestroyDrawable (ReleaseDrawable (s.frame));
+
+ for (num_frames = 0; num_frames < NUM_DRIVE_SLOTS; ++num_frames)
+ {
+ BYTE which_piece;
+
+ which_piece = GLOBAL_SIS (DriveSlots[num_frames]);
+ if (which_piece < EMPTY_SLOT)
+ DrawShipPiece (pMS->ModuleFrame, which_piece,
+ num_frames, FALSE);
+ }
+ for (num_frames = 0; num_frames < NUM_JET_SLOTS; ++num_frames)
+ {
+ BYTE which_piece;
+
+ which_piece = GLOBAL_SIS (JetSlots[num_frames]);
+ if (which_piece < EMPTY_SLOT)
+ DrawShipPiece (pMS->ModuleFrame, which_piece,
+ num_frames, FALSE);
+ }
+ for (num_frames = 0; num_frames < NUM_MODULE_SLOTS; ++num_frames)
+ {
+ BYTE which_piece;
+
+ which_piece = GLOBAL_SIS (ModuleSlots[num_frames]);
+ if (which_piece < EMPTY_SLOT)
+ DrawShipPiece (pMS->ModuleFrame, which_piece,
+ num_frames, FALSE);
+ }
+ RedistributeFuel ();
+ DisplayLanders (pMS);
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) < 3)
+ {
+ BYTE ShieldFlags;
+
+ ShieldFlags = GET_GAME_STATE (LANDER_SHIELDS);
+
+ s.frame = SetAbsFrameIndex (pMS->ModuleFrame,
+ GetFrameCount (pMS->ModuleFrame) - 5);
+ if (ShieldFlags & (1 << EARTHQUAKE_DISASTER))
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ if (ShieldFlags & (1 << BIOLOGICAL_DISASTER))
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ if (ShieldFlags & (1 << LIGHTNING_DISASTER))
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ if (ShieldFlags & (1 << LAVASPOT_DISASTER))
+ DrawStamp (&s);
+ }
+
+ DrawMenuStateStrings (PM_FUEL, pMS->CurState);
+ DrawFlagshipName (FALSE);
+ if (optWhichFonts == OPT_PC)
+ DrawFlagshipStats ();
+
+ ScreenTransition (3, NULL);
+ PlayMusic (pMS->hMusic, TRUE, 1);
+ UnbatchGraphics ();
+
+ SetFlashRect (SFR_MENU_3DO);
+
+ GLOBAL_SIS (FuelOnBoard) =
+ (GLOBAL_SIS (FuelOnBoard)
+ + (FUEL_TANK_SCALE >> 1)) / FUEL_TANK_SCALE;
+ GLOBAL_SIS (FuelOnBoard) *= FUEL_TANK_SCALE;
+ }
+
+ SetContext (StatusContext);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_CANCEL]
+ || (PulsedInputState.menu[KEY_MENU_SELECT]
+ && pMS->CurState == OUTFIT_EXIT))
+ {
+ if (pMS->CurState == OUTFIT_DOFUEL)
+ {
+ pMS->CurState = OUTFIT_FUEL;
+ SetFlashRect (SFR_MENU_3DO);
+ }
+ else
+ {
+ExitOutfit:
+ DestroyDrawable (ReleaseDrawable (pMS->CurFrame));
+ pMS->CurFrame = 0;
+ DestroyDrawable (ReleaseDrawable (pMS->ModuleFrame));
+ pMS->ModuleFrame = 0;
+
+ SetNamingCallback (NULL);
+
+ return (FALSE);
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ switch (pMS->CurState)
+ {
+ case OUTFIT_FUEL:
+ {
+ RECT r;
+
+ pMS->CurState = OUTFIT_DOFUEL;
+ SetContext (StatusContext);
+ GetGaugeRect (&r, FALSE);
+ SetFlashRect (&r);
+ break;
+ }
+ case OUTFIT_DOFUEL:
+ pMS->CurState = OUTFIT_FUEL;
+ SetFlashRect (SFR_MENU_3DO);
+ break;
+ case OUTFIT_MODULES:
+ pMS->CurState = EMPTY_SLOT + 2;
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) != 3)
+ pMS->delta_item = 0;
+ else
+ pMS->delta_item = NUM_BOMB_MODULES;
+ pMS->first_item.y = 0;
+ pMS->Initialized = 0;
+ DoInstallModule (pMS);
+ break;
+ case OUTFIT_SAVELOAD:
+ // Clearing FlashRect is not necessary
+ if (!GameOptions ())
+ goto ExitOutfit;
+ DrawMenuStateStrings (PM_FUEL, pMS->CurState);
+ SetFlashRect (SFR_MENU_3DO);
+ break;
+ }
+ }
+ else
+ {
+ switch (pMS->CurState)
+ {
+ case OUTFIT_DOFUEL:
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN |
+ MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN,
+ MENU_SOUND_SELECT | MENU_SOUND_CANCEL);
+ break;
+ default:
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ break;
+ }
+
+ if (pMS->CurState == OUTFIT_DOFUEL)
+ {
+ ChangeFuelQuantity ();
+ SleepThread (ONE_SECOND / 30);
+ }
+ else
+ DoMenuChooser (pMS, PM_FUEL);
+ }
+
+ return (TRUE);
+}
+
diff --git a/src/uqm/pickship.c b/src/uqm/pickship.c
new file mode 100644
index 0000000..3dcced0
--- /dev/null
+++ b/src/uqm/pickship.c
@@ -0,0 +1,501 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "pickship.h"
+
+#include "build.h"
+#include "colors.h"
+#include "controls.h"
+#include "menustat.h"
+#include "supermelee/pickmele.h"
+#include "encount.h"
+#include "battle.h"
+#include "races.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "libs/mathlib.h"
+
+
+#define NUM_PICK_SHIP_ROWS 2
+#define NUM_PICK_SHIP_COLUMNS 6
+
+#define ICON_WIDTH 16
+#define ICON_HEIGHT 16
+
+#define FLAGSHIP_X_OFFS 65
+#define FLAGSHIP_Y_OFFS 4
+#define FLAGSHIP_WIDTH 22
+#define FLAGSHIP_HEIGHT 48
+
+static BOOLEAN
+DoPickBattleShip (MENU_STATE *pMS)
+{
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ pMS->CurFrame = 0;
+ return (FALSE);
+ }
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+
+ if (!pMS->Initialized)
+ {
+ pMS->Initialized = TRUE;
+ pMS->InputFunc = DoPickBattleShip;
+
+
+ goto ChangeSelection;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ if ((HSTARSHIP)pMS->CurFrame)
+ {
+ PlayMenuSound (MENU_SOUND_SUCCESS);
+ return (FALSE);
+ }
+ }
+ else
+ {
+ COORD new_row, new_col;
+ int dx = 0, dy = 0;
+ if (PulsedInputState.menu[KEY_MENU_RIGHT]) dx = 1;
+ if (PulsedInputState.menu[KEY_MENU_LEFT]) dx = -1;
+ if (PulsedInputState.menu[KEY_MENU_UP]) dy = -1;
+ if (PulsedInputState.menu[KEY_MENU_DOWN]) dy = 1;
+
+ new_col = pMS->first_item.x + dx;
+ new_row = pMS->first_item.y + dy;
+ if (new_row != pMS->first_item.y
+ || new_col != pMS->first_item.x)
+ {
+ RECT r;
+ TEXT t;
+ COUNT crew_level, max_crew;
+ COUNT ship_index;
+ HSTARSHIP hBattleShip, hNextShip;
+ STARSHIP *StarShipPtr;
+
+ if (new_col < 0)
+ new_col = NUM_PICK_SHIP_COLUMNS;
+ else if (new_col > NUM_PICK_SHIP_COLUMNS)
+ new_col = 0;
+
+ if (new_row < 0)
+ new_row = NUM_PICK_SHIP_ROWS - 1;
+ else if (new_row == NUM_PICK_SHIP_ROWS)
+ new_row = 0;
+
+ PlayMenuSound (MENU_SOUND_MOVE);
+
+
+#ifdef NEVER
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x1D));
+ DrawRectangle (&pMS->flash_rect0);
+#endif /* NEVER */
+ pMS->first_item.y = new_row;
+ pMS->first_item.x = new_col;
+
+ChangeSelection:
+ if (pMS->first_item.x == (NUM_PICK_SHIP_COLUMNS >> 1))
+ {
+ pMS->flash_rect0.corner.x =
+ pMS->flash_rect1.corner.x - 2 + FLAGSHIP_X_OFFS;
+ pMS->flash_rect0.corner.y =
+ pMS->flash_rect1.corner.y - 2 + FLAGSHIP_Y_OFFS;
+ pMS->flash_rect0.extent.width = FLAGSHIP_WIDTH + 4;
+ pMS->flash_rect0.extent.height = FLAGSHIP_HEIGHT + 4;
+
+ hBattleShip = GetTailLink (&race_q[0]); /* Flagship */
+ }
+ else
+ {
+ new_col = pMS->first_item.x;
+ pMS->flash_rect0.corner.x = 5 + pMS->flash_rect1.corner.x - 2
+ + ((ICON_WIDTH + 4) * new_col);
+ if (new_col > (NUM_PICK_SHIP_COLUMNS >> 1))
+ {
+ --new_col;
+ pMS->flash_rect0.corner.x += FLAGSHIP_WIDTH - ICON_WIDTH;
+ }
+ pMS->flash_rect0.corner.y = 16 + pMS->flash_rect1.corner.y - 2
+ + ((ICON_HEIGHT + 4) * pMS->first_item.y);
+ pMS->flash_rect0.extent.width = ICON_WIDTH + 4;
+ pMS->flash_rect0.extent.height = ICON_HEIGHT + 4;
+
+ ship_index = (pMS->first_item.y * NUM_PICK_SHIP_COLUMNS)
+ + new_col;
+
+ for (hBattleShip = GetHeadLink (&race_q[0]);
+ hBattleShip != GetTailLink (&race_q[0]);
+ hBattleShip = hNextShip)
+ {
+ StarShipPtr = LockStarShip (&race_q[0], hBattleShip);
+ if (StarShipPtr->index == ship_index
+ && (StarShipPtr->SpeciesID != NO_ID))
+ {
+ UnlockStarShip (&race_q[0], hBattleShip);
+ break;
+ }
+
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockStarShip (&race_q[0], hBattleShip);
+ }
+
+ if (hBattleShip == GetTailLink (&race_q[0]))
+ hBattleShip = 0;
+ }
+
+ pMS->CurFrame = (FRAME)hBattleShip;
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ r.corner.x = pMS->flash_rect1.corner.x + 6;
+ r.corner.y = pMS->flash_rect1.corner.y + 5;
+ r.extent.width = ((ICON_WIDTH + 4) * 3) - 4;
+ r.extent.height = 7;
+ DrawFilledRectangle (&r);
+
+ if (hBattleShip == 0)
+ {
+ crew_level = 0;
+ max_crew = 0;
+ // Satisfy compiler.
+ }
+ else
+ {
+ SetContextFont (TinyFont);
+
+ t.baseline.x = r.corner.x + (r.extent.width >> 1);
+ t.baseline.y = r.corner.y + (r.extent.height - 1);
+ t.align = ALIGN_CENTER;
+
+ StarShipPtr = LockStarShip (&race_q[0], hBattleShip);
+ if (StarShipPtr->captains_name_index == 0)
+ {
+ t.pStr = GLOBAL_SIS (CommanderName);
+ t.CharCount = (COUNT)~0;
+ crew_level = GLOBAL_SIS (CrewEnlisted);
+ max_crew = GetCrewPodCapacity ();
+ }
+ else
+ {
+ STRING locString;
+
+ locString = SetAbsStringTableIndex (
+ StarShipPtr->race_strings,
+ StarShipPtr->captains_name_index);
+ t.pStr = (UNICODE *)GetStringAddress (locString);
+ t.CharCount = GetStringLength (locString);
+ crew_level = StarShipPtr->crew_level;
+ max_crew = StarShipPtr->max_crew;
+ }
+ UnlockStarShip (&race_q[0], hBattleShip);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x0A, 0x00), 0x0C));
+ font_DrawText (&t);
+ SetContextForeGroundColor (BLACK_COLOR);
+ }
+
+ r.corner.x += (ICON_WIDTH + 4)
+ * ((NUM_PICK_SHIP_COLUMNS >> 1) + 1)
+ + FLAGSHIP_WIDTH - ICON_WIDTH;
+ DrawFilledRectangle (&r);
+
+ if (crew_level)
+ {
+ char buf[80];
+
+ t.baseline.x = r.corner.x + (r.extent.width >> 1);
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ if (crew_level >= max_crew)
+ sprintf (buf, "%u", crew_level);
+ else
+ sprintf (buf, "%u/%u", crew_level, max_crew);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02));
+ font_DrawText (&t);
+ }
+
+ SetFlashRect (NULL);
+ SetFlashRect (&pMS->flash_rect0);
+ }
+ }
+
+ SleepThread (ONE_SECOND / 30);
+
+ return (TRUE);
+}
+
+static HSTARSHIP
+GetArmadaStarShip (void)
+{
+ RECT pick_r;
+ CONTEXT OldContext;
+ HSTARSHIP hBattleShip;
+
+ if (battle_counter[1] == 0)
+ {
+ // No opponents left.
+ return 0;
+ }
+
+// MenuSounds = CaptureSound (LoadSound (MENU_SOUNDS));
+
+OldContext = SetContext (SpaceContext);
+ DrawArmadaPickShip (FALSE, &pick_r);
+
+ {
+ MENU_STATE MenuState;
+
+ MenuState.InputFunc = DoPickBattleShip;
+ MenuState.Initialized = FALSE;
+ MenuState.first_item.x = NUM_PICK_SHIP_COLUMNS >> 1;
+ MenuState.first_item.y = 0;
+ MenuState.CurFrame = 0;
+ MenuState.flash_rect1.corner = pick_r.corner;
+ MenuState.flash_rect1.extent.width = 0;
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ DoInput (&MenuState, FALSE);
+
+ SetFlashRect (NULL);
+
+ hBattleShip = (HSTARSHIP)MenuState.CurFrame;
+ }
+
+ if (hBattleShip)
+ {
+ if (hBattleShip == GetTailLink (&race_q[0]))
+ { // Player chose SIS. There will be no more choices.
+ battle_counter[RPG_PLAYER_NUM] = 1;
+ }
+
+ WaitForSoundEnd (0);
+ }
+
+// DestroySound (ReleaseSound (MenuSounds));
+
+SetContext (OldContext);
+
+ return (hBattleShip);
+}
+
+// Get the next ship to use.
+HSTARSHIP
+GetEncounterStarShip (STARSHIP *LastStarShipPtr, COUNT which_player)
+{
+ if (inHQSpace ())
+ {
+ assert (which_player == RPG_PLAYER_NUM);
+ // SIS for the Hyperspace flight
+ return GetHeadLink (&race_q[which_player]);
+ }
+ else if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ {
+ // Let the player chose their own ship. (May be a computer player).
+ HSTARSHIP hBattleShip;
+
+ if (battle_counter[0] == 0 || battle_counter[1] == 0)
+ { // One side is out of ships. Game over.
+ return 0;
+ }
+
+ if (!GetNextMeleeStarShip (which_player, &hBattleShip))
+ return 0;
+
+ return hBattleShip;
+ }
+ else
+ {
+ // Full game.
+ if (which_player == RPG_PLAYER_NUM)
+ { // Human player in a full game.
+ if (LastStarShipPtr == 0 && battle_counter[which_player] == 1)
+ { // First time picking a ship and player has no escorts
+ // SIS is the last ship in queue (though there is only one)
+ return GetTailLink (&race_q[which_player]);
+ }
+ else if (battle_counter[which_player])
+ { // Player still has ships left
+ return GetArmadaStarShip ();
+ }
+ else if (LastStarShipPtr != 0)
+ { // last ship was the flagship
+#define RUN_AWAY_FUEL_COST (5 * FUEL_TANK_SCALE)
+ if (LastStarShipPtr->crew_level == 0)
+ { // Died in the line of duty
+ GLOBAL_SIS (CrewEnlisted) = (COUNT)~0;
+ }
+ else
+ { // Player ran away
+ if (GLOBAL_SIS (FuelOnBoard) > RUN_AWAY_FUEL_COST)
+ GLOBAL_SIS (FuelOnBoard) -= RUN_AWAY_FUEL_COST;
+ else
+ GLOBAL_SIS (FuelOnBoard) = 0;
+ }
+ }
+ return 0;
+ }
+ else
+ { // NPC player in a full game
+ if (FleetIsInfinite (which_player))
+ {
+ if (LastStarShipPtr != 0)
+ { // The current STARSHIP is reused for the next one;
+ // update with new info
+ // XXX: Note that if Syreen had a homeworld you could
+ // fight, all Syreen ships there would be crewed to
+ // the maximum, instead of the normal level
+ LastStarShipPtr->crew_level = LastStarShipPtr->max_crew;
+ LastStarShipPtr->playerNr = which_player;
+ LastStarShipPtr->captains_name_index = PickCaptainName ();
+ }
+ battle_counter[which_player]++;
+
+ return GetHeadLink (&race_q[which_player]);
+ }
+
+ // Get the next ship for the computer
+ if (LastStarShipPtr != 0)
+ return _GetSuccLink (LastStarShipPtr);
+
+ // Get the very first ship for the computer
+ return GetHeadLink (&race_q[which_player]);
+ }
+ }
+}
+
+void
+DrawArmadaPickShip (BOOLEAN draw_salvage_frame, RECT *pPickRect)
+{
+#define PICK_NAME_HEIGHT 6
+ //COUNT i;
+ HSTARSHIP hBattleShip, hNextShip;
+ STARSHIP *StarShipPtr;
+ RECT r, pick_r;
+ STAMP s;
+ TEXT t;
+ CONTEXT OldContext;
+ FRAME PickFrame;
+
+ OldContext = SetContext (SpaceContext);
+
+ PickFrame = CaptureDrawable (LoadGraphic (SC2_PICK_PMAP_ANIM));
+
+ BatchGraphics ();
+
+ s.frame = PickFrame;
+ SetFrameHot (s.frame, MAKE_HOT_SPOT (0, 0));
+ GetFrameRect (s.frame, &pick_r);
+ GetContextClipRect (&r);
+ pick_r.corner.x = (r.extent.width >> 1) - (pick_r.extent.width >> 1);
+ pick_r.corner.y = (r.extent.height >> 1) - (pick_r.extent.height >> 1);
+
+ if (!draw_salvage_frame)
+ *pPickRect = pick_r;
+ else
+ {
+ s.origin.x = r.extent.width >> 1;
+ s.frame = IncFrameIndex (s.frame);
+ SetFrameHot (s.frame, MAKE_HOT_SPOT (0, 0));
+ GetFrameRect (s.frame, &r);
+ s.origin.x -= r.extent.width >> 1;
+ s.origin.y = pick_r.corner.y - (r.extent.height >> 1);
+ DrawStamp (&s);
+ s.frame = DecFrameIndex (s.frame);
+ pick_r.corner.y = s.origin.y + r.extent.height;
+
+ r.corner.x = pick_r.corner.x;
+ r.corner.y = s.origin.y;
+ *pPickRect = r;
+ }
+ s.origin = pick_r.corner;
+ DrawStamp (&s);
+
+ t.baseline.x = pick_r.corner.x + (pick_r.extent.width >> 1);
+ t.baseline.y = pick_r.corner.y + pick_r.extent.height - 5;
+ t.align = ALIGN_CENTER;
+ t.pStr = GLOBAL_SIS (ShipName);
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x12, 0x12, 0x12), 0x17));
+ SetContextFont (StarConFont);
+ font_DrawText (&t);
+
+ r.extent.width = ICON_WIDTH;
+ r.extent.height = ICON_HEIGHT;
+ for (hBattleShip = GetHeadLink (&race_q[0]);
+ hBattleShip != 0; hBattleShip = hNextShip)
+ {
+ StarShipPtr = LockStarShip (&race_q[0], hBattleShip);
+
+ if (StarShipPtr->captains_name_index)
+ { // Escort ship, not SIS
+ COUNT ship_index;
+
+ ship_index = StarShipPtr->index;
+
+ s.origin.x = pick_r.corner.x
+ + (5 + ((ICON_WIDTH + 4)
+ * (ship_index % NUM_PICK_SHIP_COLUMNS)));
+ if ((ship_index % NUM_PICK_SHIP_COLUMNS) >=
+ (NUM_PICK_SHIP_COLUMNS >> 1))
+ s.origin.x += FLAGSHIP_WIDTH + 4;
+ s.origin.y = pick_r.corner.y
+ + (16 + ((ICON_HEIGHT + 4)
+ * (ship_index / NUM_PICK_SHIP_COLUMNS)));
+ s.frame = StarShipPtr->icons;
+ r.corner = s.origin;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ if ((StarShipPtr->SpeciesID != NO_ID) || (StarShipPtr->crew_level == 0))
+ {
+ DrawStamp (&s);
+ if (StarShipPtr->SpeciesID == NO_ID)
+ {
+ /* Dead ship - mark with an X. */
+ s.origin.x -= 1;
+ s.frame = SetAbsFrameIndex (StatusFrame, 3);
+ DrawStamp (&s);
+ }
+ }
+ else
+ {
+ /* Ship ran away */
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01));
+ DrawFilledStamp (&s);
+ }
+ }
+
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockStarShip (&race_q[0], hBattleShip);
+ }
+
+ UnbatchGraphics ();
+
+ DestroyDrawable (ReleaseDrawable (PickFrame));
+
+ SetContext (OldContext);
+}
+
diff --git a/src/uqm/pickship.h b/src/uqm/pickship.h
new file mode 100644
index 0000000..f5a55b3
--- /dev/null
+++ b/src/uqm/pickship.h
@@ -0,0 +1,35 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_PICKSHIP_H_INCL_
+#define UQM_PICKSHIP_H_INCL_
+
+#include "libs/compiler.h"
+#include "races.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern HSTARSHIP GetEncounterStarShip (STARSHIP *LastStarShipPtr,
+ COUNT which_player);
+extern void DrawArmadaPickShip (BOOLEAN draw_salvage_frame, RECT *pPickRect);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PICKSHIP_H_INCL_ */
diff --git a/src/uqm/plandata.c b/src/uqm/plandata.c
new file mode 100644
index 0000000..a79c2d5
--- /dev/null
+++ b/src/uqm/plandata.c
@@ -0,0 +1,1850 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gendef.h"
+#include "resinst.h"
+#include "planets/planets.h"
+#include "planets/elemdata.h"
+
+
+STAR_DESC starmap_array[] =
+{
+ // postfix name index (like 'Normae')
+ // prefix name index (like 'Alpha') |
+ // alien presence | |
+ // owner (unused) | | |
+ // x, y star type colour | | | |
+ {{5007, 35}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 6, 74},
+ {{ 708, 41}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 7, 91},
+ {{4714, 78}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 7, 74},
+ {{2187, 83}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 0, 126},
+ {{2814, 89}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 82},
+ {{4244, 91}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 0, 125},
+ {{5652, 98}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 124},
+ {{2939, 116}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 82},
+ {{2771, 146}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 82},
+ {{5313, 150}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 6, 73},
+ {{ 265, 156}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 92},
+ {{4529, 169}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 8, 74},
+ {{4911, 180}, MAKE_STAR (GIANT_STAR, ORANGE_BODY, -1), 0, 1, 74},
+ {{4747, 221}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 74},
+ {{9708, 250}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 0, 112},
+ {{4861, 262}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 74},
+ {{2908, 269}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), SHOFIXTI_DEFINED, 4, 82},
+ {{1855, 270}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 5, 81},
+ {{7958, 270}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 8},
+ {{5160, 280}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 73},
+ {{ 570, 289}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 92},
+ {{4923, 294}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), YEHAT_DEFINED, 3, 74},
+ {{2820, 301}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 5, 82},
+ {{7934, 318}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 8},
+ {{8062, 318}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 8},
+ {{1116, 334}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 6, 91},
+ {{ 803, 337}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 91},
+ {{1787, 338}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 6, 81},
+ {{ 877, 340}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 91},
+ {{5338, 355}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 5, 73},
+ {{5039, 373}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 73},
+ {{ 843, 380}, MAKE_STAR (GIANT_STAR, ORANGE_BODY, -1), 0, 1, 91},
+ {{4872, 408}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 74},
+ {{1740, 423}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 7, 81},
+ {{4596, 429}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 9, 74},
+ {{ 843, 431}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 91},
+ {{2156, 440}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 81},
+ {{2004, 441}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 81},
+ {{ 530, 442}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 92},
+ {{ 958, 468}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 5, 91},
+ {{2058, 475}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 81},
+ {{ 304, 477}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 1, 92},
+ {{ 522, 525}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), PKUNK_DEFINED, 3, 92},
+ {{2100, 554}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 81},
+ {{ 134, 565}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 6, 92},
+ {{6858, 577}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), MYCON_TRAP_DEFINED, 0, 123},
+ {{5014, 584}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 73},
+ {{5256, 608}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 73},
+ {{2411, 718}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 9},
+ {{2589, 741}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 9},
+ {{ 675, 742}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 8, 91},
+ {{9292, 750}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 5},
+ {{1463, 779}, MAKE_STAR (GIANT_STAR, RED_BODY, -1), 0, 6, 80},
+ {{3089, 782}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 9},
+ {{2854, 787}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 9},
+ {{3333, 801}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 9},
+ {{9237, 821}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 5, 5},
+ {{9339, 843}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 5},
+ {{ 242, 857}, MAKE_STAR (GIANT_STAR, ORANGE_BODY, -1), 0, 3, 90},
+ {{1515, 866}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 5, 80},
+ {{4770, 895}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 5, 75},
+ {{1412, 905}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 80},
+ {{4681, 916}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), RAINBOW_DEFINED, 6, 75},
+ {{9333, 937}, MAKE_STAR (SUPER_GIANT_STAR, YELLOW_BODY, -1), MELNORME0_DEFINED, 2, 5},
+ {{9419, 942}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 5},
+ {{ 230, 952}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 90},
+ {{ 146, 955}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 90},
+ {{4873, 968}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 75},
+ {{1559, 993}, MAKE_STAR (SUPER_GIANT_STAR, RED_BODY, -1), MELNORME1_DEFINED, 1, 80},
+ {{1895, 1041}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 93},
+ {{4337, 1066}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 75},
+ {{3732, 1067}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 0, 122},
+ {{1579, 1115}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 80},
+ {{4875, 1145}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 75},
+ {{4604, 1187}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 75},
+ {{5812, 1208}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 72},
+ {{1312, 1260}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 80},
+ {{1916, 1270}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 93},
+ {{6562, 1270}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 0, 121},
+ {{ 416, 1301}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 0, 120},
+ {{3958, 1354}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 89},
+ {{4000, 1363}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 89},
+ {{1752, 1450}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), SOL_DEFINED, 0, 129},
+ {{2187, 1500}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 0, 127},
+ {{1806, 1507}, MAKE_STAR (GIANT_STAR, WHITE_BODY, -1), 0, 0, 128},
+ {{5708, 1520}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 72},
+ {{9469, 1548}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 6},
+ {{4333, 1562}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 88},
+ {{6041, 1562}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 72},
+ {{9375, 1583}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 6},
+ {{2881, 1614}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 96},
+ {{6083, 1625}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 72},
+ {{4250, 1645}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 88},
+ {{ 650, 1646}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 7, 85},
+ {{9477, 1670}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 6},
+ {{2840, 1676}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 96},
+ {{9541, 1687}, MAKE_STAR (GIANT_STAR, RED_BODY, -1), 0, 4, 6},
+ {{7395, 1687}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 69},
+ {{4333, 1687}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), VUX_DEFINED, 2, 88},
+ {{9559, 1735}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 6},
+ {{ 736, 1737}, MAKE_STAR (GIANT_STAR, BLUE_BODY, -1), 0, 6, 85},
+ {{1601, 1746}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 94},
+ {{7395, 1750}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 69},
+ {{ 951, 1770}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 1, 85},
+ {{1666, 1812}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 94},
+ {{7187, 1833}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 69},
+ {{ 705, 1838}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 5, 85},
+ {{1140, 1847}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 85},
+ {{6467, 1878}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 71},
+ {{2791, 1895}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 96},
+ {{6500, 1916}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 71},
+ {{5458, 1916}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 0, 119},
+ {{1048, 1919}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 85},
+ {{3678, 1926}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 99},
+ {{3345, 1931}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), START_COLONY_DEFINED, 0, 98},
+ {{8187, 1937}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 4, 7},
+ {{3352, 1940}, MAKE_STAR (SUPER_GIANT_STAR, WHITE_BODY, -1), MELNORME2_DEFINED, 0, 97},
+ {{ 977, 1953}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 85},
+ {{4221, 1986}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), MAIDENS_DEFINED, 1, 100},
+ {{4500, 2000}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 100},
+ {{6833, 2000}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 70},
+ {{8163, 2009}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 3, 7},
+ {{8080, 2011}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 7},
+ {{6036, 2035}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 4, 71},
+ {{6479, 2062}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), EGG_CASE1_DEFINED, 3, 71},
+ {{2104, 2083}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), ZOQ_SCOUT_DEFINED, 0, 118},
+ {{8062, 2083}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 7},
+ {{ 270, 2187}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 79},
+ {{6500, 2208}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 6, 71},
+ {{6291, 2208}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), MYCON_DEFINED, 5, 71},
+ {{ 125, 2229}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 79},
+ {{ 312, 2250}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 79},
+ {{3884, 2262}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 99},
+ {{ 742, 2268}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), CHMMR_DEFINED, 0, 117},
+ {{2306, 2285}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 95},
+ {{2402, 2309}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 95},
+ {{6395, 2312}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), SUN_DEVICE_DEFINED, 2, 12},
+ {{8875, 2312}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 9, 61},
+ {{3551, 2320}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 99},
+ {{6208, 2333}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 12},
+ {{3354, 2354}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 99},
+ {{9909, 2359}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 0, 111},
+ {{2298, 2385}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 95},
+ {{7020, 2395}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 70},
+ {{9038, 2407}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 61},
+ {{9375, 2416}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 8, 61},
+ {{6500, 2458}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 6, 12},
+ {{ 217, 2509}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 78},
+ {{3641, 2512}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 86},
+ {{5625, 2520}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 14},
+ {{3713, 2537}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), ORZ_DEFINED, 3, 86},
+ {{3587, 2566}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), ANDROSYNTH_DEFINED, 7, 86},
+ {{9291, 2583}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 61},
+ {{3654, 2587}, MAKE_STAR (SUPER_GIANT_STAR, GREEN_BODY, -1), MELNORME3_DEFINED, 1, 86},
+ {{3721, 2619}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), TAALO_PROTECTOR_DEFINED, 4, 86},
+ {{5791, 2625}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 14},
+ {{6416, 2625}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 12},
+ {{6008, 2631}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), EGG_CASE0_DEFINED, 2, 14},
+ {{3608, 2637}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 6, 86},
+ {{3499, 2648}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 87},
+ {{9479, 2666}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 61},
+ {{3668, 2666}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 5, 86},
+ {{ 229, 2666}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 78},
+ {{8895, 2687}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 7, 61},
+ {{ 138, 2696}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 78},
+ {{5375, 2729}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 0, 116},
+ {{6354, 2729}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), EGG_CASE2_DEFINED, 3, 12},
+ {{6458, 2750}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 12},
+ {{2458, 2750}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 6, 106},
+ {{ 351, 2758}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 78},
+ {{7083, 2770}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 70},
+ {{3759, 2778}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 87},
+ {{9333, 2791}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 61},
+ {{3400, 2804}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 87},
+ {{9469, 2806}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), DRUUGE_DEFINED, 6, 61},
+ {{3619, 2830}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 87},
+ {{2208, 2854}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 7, 106},
+ {{9250, 2854}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 4, 61},
+ {{ 672, 2863}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 7, 78},
+ {{ 167, 2875}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 6, 78},
+ {{4030, 2887}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 105},
+ {{ 384, 2900}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 78},
+ {{2727, 2951}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 5, 106},
+ {{4645, 2958}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 105},
+ {{5625, 2958}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 13},
+ {{8270, 2958}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 66},
+ {{8291, 2979}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 66},
+ {{6020, 2979}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), RAINBOW_DEFINED, 3, 13},
+ {{6562, 3020}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 4, 70},
+ {{2011, 3043}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 8, 106},
+ {{8125, 3083}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 66},
+ {{2354, 3166}, MAKE_STAR (GIANT_STAR, YELLOW_BODY, -1), 0, 4, 106},
+ {{3833, 3187}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 105},
+ {{5812, 3208}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 1, 13},
+ {{9000, 3250}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 0, 113},
+ {{ 291, 3250}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 84},
+ {{ 501, 3259}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 84},
+ {{ 791, 3270}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 84},
+ {{2354, 3291}, MAKE_STAR (SUPER_GIANT_STAR, RED_BODY, -1), MELNORME4_DEFINED, 1, 106},
+ {{1104, 3333}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 4, 84},
+ {{2687, 3333}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 106},
+ {{3187, 3375}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 107},
+ {{1758, 3418}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 108},
+ {{2520, 3437}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 106},
+ {{8437, 3458}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 64},
+ {{8770, 3458}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 64},
+ {{3000, 3500}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 107},
+ {{ 149, 3519}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 76},
+ {{8791, 3541}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 64},
+ {{2148, 3551}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 109},
+ {{7375, 3562}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 0, 115},
+ {{9312, 3562}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 63},
+ {{9599, 3583}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 63},
+ {{9375, 3604}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 63},
+ {{ 90, 3614}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 6, 76},
+ {{2770, 3625}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 107},
+ {{8708, 3625}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 64},
+ {{ 267, 3645}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 76},
+ {{1604, 3645}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 108},
+ {{2274, 3663}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 109},
+ {{ 229, 3666}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), ILWRATH_DEFINED, 1, 76},
+ {{3083, 3674}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 107},
+ {{2416, 3687}, MAKE_STAR (GIANT_STAR, ORANGE_BODY, -1), SPATHI_DEFINED, 5, 109},
+ {{9333, 3708}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 63},
+ {{2250, 3708}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 3, 109},
+ {{ 288, 3735}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 76},
+ {{2354, 3741}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 109},
+ {{2583, 3750}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 6, 109},
+ {{4125, 3770}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), SYREEN_DEFINED, 0, 114},
+ {{ 166, 3770}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 4, 76},
+ {{6270, 3833}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 10},
+ {{2145, 3916}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 110},
+ {{6125, 3937}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 10},
+ {{6291, 3937}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 9, 10},
+ {{5937, 3937}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), SHIP_VAULT_DEFINED, 5, 10},
+ {{2479, 3958}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 7, 109},
+ {{ 926, 3972}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 83},
+ {{2062, 3991}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 110},
+ {{5895, 4020}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 8, 10},
+ {{ 285, 4020}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 77},
+ {{6062, 4041}, MAKE_STAR (GIANT_STAR, YELLOW_BODY, -1), 0, 1, 10},
+ {{2875, 4041}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 20},
+ {{8645, 4062}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 65},
+ {{ 860, 4065}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 83},
+ {{5958, 4083}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 10},
+ {{3038, 4083}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 20},
+ {{ 291, 4104}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 77},
+ {{6166, 4125}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 6, 10},
+ {{9812, 4145}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 62},
+ {{8520, 4166}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 65},
+ {{9573, 4182}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 62},
+ {{ 500, 4187}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 77},
+ {{2145, 4208}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 110},
+ {{6208, 4229}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 7, 10},
+ {{2812, 4250}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 7, 20},
+ {{2937, 4306}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 4, 20},
+ {{9416, 4395}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 62},
+ {{2875, 4479}, MAKE_STAR (GIANT_STAR, WHITE_BODY, -1), 0, 1, 20},
+ {{ 250, 4583}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 26},
+ {{7250, 4583}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 68},
+ {{ 479, 4583}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 26},
+ {{5708, 4604}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 0, 104},
+ {{ 479, 4645}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 26},
+ {{2895, 4687}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 6, 20},
+ {{2708, 4708}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 5, 20},
+ {{ 562, 4708}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 26},
+ {{ 416, 4717}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 26},
+ {{5094, 4931}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 11},
+ {{9000, 5000}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 67},
+ {{8958, 5000}, MAKE_STAR (GIANT_STAR, BLUE_BODY, -1), 0, 1, 67},
+ {{5006, 5011}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 11},
+ {{7312, 5062}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 68},
+ {{3679, 5068}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 17},
+ {{9062, 5083}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 67},
+ {{7416, 5083}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), RAINBOW_DEFINED, 3, 68},
+ {{5155, 5122}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 11},
+ {{3875, 5145}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 17},
+ {{4937, 5145}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 11},
+ {{2979, 5166}, MAKE_STAR (GIANT_STAR, ORANGE_BODY, -1), 0, 1, 15},
+ {{3035, 5178}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 15},
+ {{3994, 5185}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 17},
+ {{3541, 5187}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 17},
+ {{5977, 5246}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 102},
+ {{3770, 5250}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 17},
+ {{1520, 5261}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 55},
+ {{1613, 5279}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 55},
+ {{7020, 5291}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 68},
+ {{1416, 5315}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 55},
+ {{2993, 5318}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 15},
+ {{1425, 5404}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 55},
+ {{1854, 5416}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 5, 55},
+ {{3625, 5437}, MAKE_STAR (GIANT_STAR, GREEN_BODY, -1), 0, 1, 16},
+ {{3416, 5437}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 16},
+ {{4000, 5437}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), ZOQFOT_DEFINED, 1, 18},
+ {{6270, 5479}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 102},
+ {{3583, 5479}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 16},
+ {{4083, 5513}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 18},
+ {{2159, 5614}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 6, 55},
+ {{3937, 5625}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 18},
+ {{6014, 5632}, MAKE_STAR (GIANT_STAR, BLUE_BODY, -1), 0, 1, 21},
+ {{ 250, 5687}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 25},
+ {{3625, 5750}, MAKE_STAR (GIANT_STAR, RED_BODY, -1), 0, 2, 19},
+ {{ 371, 5772}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 25},
+ {{6107, 5785}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 21},
+ {{9645, 5791}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), BURVIXESE_DEFINED, 0, 130},
+ {{1545, 5818}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 54},
+ {{3750, 5833}, MAKE_STAR (GIANT_STAR, GREEN_BODY, -1), 0, 1, 19},
+ {{6301, 5875}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 5, 21},
+ {{1923, 5878}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 54},
+ {{4625, 5895}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 131},
+ {{ 152, 5900}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 25},
+ {{5437, 5916}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 33},
+ {{1714, 5926}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 54},
+ {{6200, 5935}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), SAMATRA_DEFINED, 4, 21},
+ {{6429, 5958}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 7, 21},
+ {{4729, 5958}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 131},
+ {{1978, 5968}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), TALKING_PET_DEFINED, 2, 54},
+ {{ 395, 5979}, MAKE_STAR (GIANT_STAR, GREEN_BODY, -1), 0, 1, 22},
+ {{ 563, 5980}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 22},
+ {{ 456, 5989}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 6, 22},
+ {{4625, 6000}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 131},
+ {{6166, 6000}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 3, 21},
+ {{6496, 6032}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 6, 21},
+ {{2228, 6038}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 12, 54},
+ {{4583, 6041}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 131},
+ {{1558, 6058}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 6, 54},
+ {{1902, 6065}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 54},
+ {{2159, 6073}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 11, 54},
+ {{ 365, 6093}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 22},
+ {{ 541, 6145}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 22},
+ {{2200, 6176}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 10, 54},
+ {{ 729, 6208}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 23},
+ {{5250, 6229}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 33},
+ {{8166, 6250}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 40},
+ {{6215, 6255}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 8, 21},
+ {{ 437, 6270}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 22},
+ {{5583, 6291}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 33},
+ {{1881, 6308}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 8, 54},
+ {{1795, 6329}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 7, 54},
+ {{2118, 6379}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 9, 54},
+ {{ 750, 6458}, MAKE_STAR (GIANT_STAR, WHITE_BODY, -1), 0, 1, 23},
+ {{3716, 6458}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 30},
+ {{1360, 6489}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 56},
+ {{7333, 6500}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 40},
+ {{3770, 6500}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 30},
+ {{4500, 6500}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 0, 37},
+ {{ 187, 6520}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 24},
+ {{ 125, 6541}, MAKE_STAR (GIANT_STAR, RED_BODY, -1), 0, 1, 24},
+ {{7812, 6562}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 40},
+ {{ 770, 6602}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 23},
+ {{5910, 6624}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 29},
+ {{ 208, 6625}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 24},
+ {{2604, 6645}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 101},
+ {{1578, 6668}, MAKE_STAR (SUPER_GIANT_STAR, GREEN_BODY, -1), MELNORME5_DEFINED, 1, 56},
+ {{5479, 6687}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 33},
+ {{ 375, 6716}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 24},
+ {{ 312, 6728}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 24},
+ {{6020, 6729}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 29},
+ {{5062, 6750}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 10, 28},
+ {{4208, 6854}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 31},
+ {{5145, 6875}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 9, 28},
+ {{4291, 6937}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 31},
+ {{5145, 6958}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 8, 28},
+ {{7208, 7000}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 39},
+ {{8625, 7000}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), RAINBOW_DEFINED, 1, 41},
+ {{4955, 7034}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 28},
+ {{4895, 7041}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 28},
+ {{4971, 7104}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 28},
+ {{8666, 7104}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 41},
+ {{4854, 7125}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 28},
+ {{5083, 7145}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 7, 28},
+ {{7360, 7184}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 39},
+ {{1020, 7187}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 58},
+ {{3875, 7187}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 32},
+ {{4879, 7201}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 5, 28},
+ {{4958, 7229}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 6, 28},
+ {{7125, 7250}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 39},
+ {{7532, 7258}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 39},
+ {{2416, 7291}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 101},
+ {{3854, 7291}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 2, 32},
+ {{9687, 7333}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 3, 44},
+ {{ 395, 7458}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), RAINBOW_DEFINED, 2, 60},
+ {{4895, 7458}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 36},
+ {{4645, 7479}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 36},
+ {{6940, 7514}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 11, 39},
+ {{7443, 7538}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 6, 39},
+ {{6479, 7541}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 0, 38},
+ {{7208, 7541}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 1, 39},
+ {{5791, 7583}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 3, 34},
+ {{ 333, 7625}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 60},
+ {{5958, 7645}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 34},
+ {{1041, 7708}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 58},
+ {{5875, 7729}, MAKE_STAR (SUPER_GIANT_STAR, YELLOW_BODY, -1), MELNORME6_DEFINED, 1, 34},
+ {{1125, 7791}, MAKE_STAR (GIANT_STAR, BLUE_BODY, -1), 0, 1, 58},
+ {{4979, 7791}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 2, 36},
+ {{4958, 7791}, MAKE_STAR (GIANT_STAR, WHITE_BODY, -1), 0, 1, 36},
+ {{6889, 7803}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 10, 39},
+ {{7200, 7849}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 8, 39},
+ {{7395, 7854}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 7, 39},
+ {{9437, 7854}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 44},
+ {{2836, 7857}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), RAINBOW_DEFINED, 5, 53},
+ {{5375, 7875}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 35},
+ {{6187, 7875}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 7, 35},
+ {{6041, 7916}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 35},
+ {{5979, 7979}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 35},
+ {{7083, 7993}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 9, 39},
+ {{3270, 8000}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 8, 53},
+ {{6104, 8000}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 6, 35},
+ {{ 687, 8000}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 59},
+ {{ 562, 8000}, MAKE_STAR (GIANT_STAR, GREEN_BODY, -1), URQUAN_WRECK_DEFINED, 1, 59},
+ {{5645, 8020}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 35},
+ {{1395, 8041}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 58},
+ {{8229, 8041}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 43},
+ {{2518, 8056}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 3, 53},
+ {{5875, 8062}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 35},
+ {{8416, 8083}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 43},
+ {{9000, 8229}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 44},
+ {{3562, 8250}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 9, 53},
+ {{5437, 8270}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), RAINBOW_DEFINED, 5, 48},
+ {{1520, 8333}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 58},
+ {{2771, 8351}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 53},
+ {{2535, 8358}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), THRADD_DEFINED, 4, 53},
+ {{3151, 8390}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 7, 53},
+ {{2362, 8395}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 11, 53},
+ {{2822, 8395}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 53},
+ {{5500, 8395}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 48},
+ {{2536, 8504}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 4, 2},
+ {{2582, 8507}, MAKE_STAR (SUPER_GIANT_STAR, YELLOW_BODY, -1), MELNORME7_DEFINED, 1, 2},
+ {{8625, 8562}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 6, 3},
+ {{4375, 8562}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 0, 50},
+ {{2593, 8569}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 2},
+ {{2562, 8572}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 2},
+ {{8492, 8578}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 7, 3},
+ {{1125, 8583}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 6, 58},
+ {{8073, 8588}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 46},
+ {{8560, 8638}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 3},
+ {{8750, 8645}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 5, 3},
+ {{5562, 8645}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 1, 48},
+ {{2588, 8653}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 14, 53},
+ {{2458, 8666}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 10, 53},
+ {{7666, 8666}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), RAINBOW_DEFINED, 2, 46},
+ {{2776, 8673}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), AQUA_HELIX_DEFINED, 6, 53},
+ {{8630, 8693}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), UTWIG_DEFINED, 2, 3},
+ {{2310, 8702}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 12, 53},
+ {{ 437, 8770}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 57},
+ {{8534, 8797}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), RAINBOW_DEFINED, 3, 3},
+ {{8588, 8812}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 4, 3},
+ {{7187, 8812}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 3, 46},
+ {{5475, 8823}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 48},
+ {{3050, 8833}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 4, 1},
+ {{2831, 8854}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 1},
+ {{2300, 8861}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 13, 53},
+ {{ 479, 8875}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 57},
+ {{2706, 8910}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 2, 1},
+ {{ 333, 8916}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 57},
+ {{2535, 8917}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 5, 1},
+ {{8322, 8934}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 1, 45},
+ {{8249, 8958}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 45},
+ {{8375, 8958}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 2, 45},
+ {{5645, 8979}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 4, 48},
+ {{2687, 9000}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 1},
+ {{8375, 9041}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 45},
+ {{9960, 9042}, MAKE_STAR (GIANT_STAR, WHITE_BODY, -1), RAINBOW_DEFINED, 0, 42},
+ {{7354, 9062}, MAKE_STAR (DWARF_STAR, BLUE_BODY, -1), 0, 1, 47},
+ {{7833, 9083}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 6, 47},
+ {{2581, 9105}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 6, 1},
+ {{7545, 9107}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 3, 47},
+ {{7414, 9124}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), SUPOX_DEFINED, 2, 47},
+ {{8500, 9125}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 45},
+ {{ 104, 9125}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 5, 27},
+ {{7889, 9181}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 7, 47},
+ {{7791, 9187}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 47},
+ {{7791, 9229}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 5, 47},
+ {{4812, 9270}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 0, 51},
+ {{8500, 9372}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), BOMB_DEFINED, 6, 45},
+ {{7255, 9374}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 11, 45},
+ {{8458, 9393}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 7, 45},
+ {{1000, 9395}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 4, 27},
+ {{5711, 9475}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 2, 49},
+ {{ 62, 9479}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 3, 27},
+ {{5989, 9496}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 7, 49},
+ {{8000, 9505}, MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 9, 45},
+ {{5329, 9538}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 5, 49},
+ {{2916, 9541}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 103},
+ {{8296, 9548}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 8, 45},
+ {{5600, 9552}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 1, 49},
+ {{7664, 9589}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 10, 45},
+ {{6125, 9604}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 8, 49},
+ {{9144, 9686}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 4, 4},
+ {{5781, 9711}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 3, 49},
+ {{5229, 9729}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 6, 49},
+ {{9120, 9741}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 3, 4},
+ {{9186, 9741}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 2, 4},
+ {{9159, 9745}, MAKE_STAR (SUPER_GIANT_STAR, BLUE_BODY, -1), MELNORME8_DEFINED, 1, 4},
+ {{ 333, 9750}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 0, 0},
+ {{9147, 9790}, MAKE_STAR (DWARF_STAR, ORANGE_BODY, -1), 0, 5, 4},
+ {{5704, 9795}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), VUX_BEAST_DEFINED, 4, 49},
+ {{ 333, 9812}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), SLYLANDRO_DEFINED, 2, 27},
+ {{1020, 9937}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 7, 27},
+ {{ 83, 9979}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 6, 27},
+ {{1937, 9979}, MAKE_STAR (DWARF_STAR, RED_BODY, -1), 0, 1, 103},
+ {{4395, 9979}, MAKE_STAR (DWARF_STAR, GREEN_BODY, -1), 0, 0, 52},
+
+ {{MAX_X_UNIVERSE << 1, MAX_Y_UNIVERSE << 1}, 0, 0, 0, 0},
+
+ // QuasiSpace locations
+#define VORTEX_SCALE 20
+ {{(-12* VORTEX_SCALE) + 5000, (-21 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{( 1 * VORTEX_SCALE) + 5000, (-20 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(-16 * VORTEX_SCALE) + 5000, (-18 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{( 8 * VORTEX_SCALE) + 5000, (-17 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{( 3 * VORTEX_SCALE) + 5000, (-13 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(-21 * VORTEX_SCALE) + 5000, (-4 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(-4 * VORTEX_SCALE) + 5000, (-4 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(-12 * VORTEX_SCALE) + 5000, (-2 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(-26 * VORTEX_SCALE) + 5000, (2 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(-17 * VORTEX_SCALE) + 5000, (7 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(10 * VORTEX_SCALE) + 5000, (7 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(15 * VORTEX_SCALE) + 5000, (14 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(22 * VORTEX_SCALE) + 5000, (16 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(-6 * VORTEX_SCALE) + 5000, (19 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+ {{(10 * VORTEX_SCALE) + 5000, (20 * VORTEX_SCALE) + 5000},
+ MAKE_STAR (DWARF_STAR, WHITE_BODY, -1), 0, 0, 132},
+
+ {{6134, 5900}, MAKE_STAR (DWARF_STAR, YELLOW_BODY, -1), 0, 0, 132},
+
+ {{MAX_X_UNIVERSE << 1, MAX_Y_UNIVERSE << 1}, 0, 0, 0, 0},
+};
+
+const BYTE element_array[NUMBER_OF_ELEMENTS] =
+{
+ COMMON, /* HYDROGEN */
+ COMMON, /* HELIUM */
+ COMMON, /* LITHIUM */
+ BASE_METAL, /* BERYLLIUM */
+ BASE_METAL, /* BORON */
+ COMMON, /* CARBON */
+ COMMON, /* NITROGEN */
+ CORROSIVE, /* OXYGEN */
+ CORROSIVE, /* FLUORINE */
+ NOBLE, /* NEON */
+ BASE_METAL, /* SODIUM */
+ BASE_METAL, /* MAGNESIUM */
+ BASE_METAL, /* ALUMINUM */
+ COMMON, /* SILICON */
+ COMMON, /* PHOSPHORUS */
+ CORROSIVE, /* SULFUR */
+ CORROSIVE, /* CHLORINE */
+ NOBLE, /* ARGON */
+ BASE_METAL, /* POTASSIUM */
+ BASE_METAL, /* CALCIUM */
+ BASE_METAL, /* SCANDIUM */
+ BASE_METAL, /* TITANIUM */
+ BASE_METAL, /* VANADIUM */
+ BASE_METAL, /* CHROMIUM */
+ BASE_METAL, /* MANGANESE */
+ BASE_METAL, /* IRON */
+ BASE_METAL, /* COBALT */
+ BASE_METAL, /* NICKEL */
+ BASE_METAL, /* COPPER */
+ BASE_METAL, /* ZINC */
+ BASE_METAL, /* GALLIUM */
+ BASE_METAL, /* GERMANIUM */
+ COMMON, /* ARSENIC */
+ COMMON, /* SELENIUM */
+ CORROSIVE, /* BROMINE */
+ NOBLE, /* KRYPTON */
+ BASE_METAL, /* RUBIDIUM */
+ BASE_METAL, /* STRONTIUM */
+ BASE_METAL, /* YTTRIUM */
+ BASE_METAL, /* ZIRCONIUM */
+ BASE_METAL, /* NIOBIUM */
+ BASE_METAL, /* MOLYBDENUM */
+ RADIOACTIVE, /* TECHNETIUM */
+ BASE_METAL, /* RUTHENIUM */
+ BASE_METAL, /* RHODIUM */
+ PRECIOUS, /* PALLADIUM */
+ PRECIOUS, /* SILVER */
+ BASE_METAL, /* CADMIUM */
+ BASE_METAL, /* INDIUM */
+ BASE_METAL, /* TIN */
+ BASE_METAL, /* ANTIMONY */
+ BASE_METAL, /* TELLURIUM */
+ CORROSIVE, /* IODINE */
+ NOBLE, /* XENON */
+ BASE_METAL, /* CESIUM */
+ BASE_METAL, /* BARIUM */
+ RARE_EARTH, /* LANTHANUM */
+ RARE_EARTH, /* CERIUM */
+ RARE_EARTH, /* PRASEODYMIUM */
+ RARE_EARTH, /* NEODYMIUM */
+ RARE_EARTH, /* PROMETHIUM */
+ RARE_EARTH, /* SAMARIUM */
+ RARE_EARTH, /* EUROPIUM */
+ RARE_EARTH, /* GADOLINIUM */
+ RARE_EARTH, /* TERBIUM */
+ RARE_EARTH, /* DYPROSIUM */
+ RARE_EARTH, /* HOLMIUM */
+ RARE_EARTH, /* ERBIUM */
+ RARE_EARTH, /* THULIUM */
+ RARE_EARTH, /* YTTERBIUM */
+ RARE_EARTH, /* LUTETIUM */
+ BASE_METAL, /* HAFNIUM */
+ BASE_METAL, /* TANTALUM */
+ BASE_METAL, /* TUNGSTEN */
+ BASE_METAL, /* RHENIUM */
+ BASE_METAL, /* OSMIUM */
+ PRECIOUS, /* IRIDIUM */
+ PRECIOUS, /* PLATINUM */
+ PRECIOUS, /* GOLD */
+ BASE_METAL, /* MERCURY */
+ BASE_METAL, /* THALLIUM */
+ BASE_METAL, /* LEAD */
+ BASE_METAL, /* BISMUTH */
+ RADIOACTIVE, /* POLONIUM */
+ RADIOACTIVE, /* ASTATINE */
+ NOBLE, /* RADON */
+ RADIOACTIVE, /* FRANCIUM */
+ RADIOACTIVE, /* RADIUM */
+ RADIOACTIVE, /* ACTINIUM */
+ RADIOACTIVE, /* THORIUM */
+ RADIOACTIVE, /* PROTACTINIUM */
+ RADIOACTIVE, /* URANIUM */
+ RADIOACTIVE, /* NEPTUNIUM */
+ RADIOACTIVE, /* PLUTONIUM */
+
+ COMMON, /* OZONE */
+ COMMON, /* FREE RADICALS */
+ COMMON, /* CARBON DIOXIDE */
+ COMMON, /* CARBON MONOXIDE */
+ COMMON, /* AMMONIA */
+ COMMON, /* METHANE */
+ COMMON, /* SULFURIC ACID */
+ COMMON, /* HYDROCHLORIC ACID */
+ COMMON, /* HYDROCYANIC ACID */
+ COMMON, /* FORMIC ACID */
+ COMMON, /* PHOSPHORIC ACID */
+ COMMON, /* FORMALDEHYDE */
+ COMMON, /* CYANOACETYLENE */
+ COMMON, /* METHANOL */
+ COMMON, /* ETHANOL */
+ COMMON, /* SILICON MONOXIDE */
+ COMMON, /* TITANIUM OXIDE */
+ COMMON, /* ZIRCONIUM OXIDE */
+ COMMON, /* WATER */
+ COMMON, /* SILICON COMPOUNDS */
+ COMMON, /* METAL OXIDES */
+ EXOTIC, /* QUANTUM BLACK HOLES */
+ EXOTIC, /* NEUTRONIUM */
+ EXOTIC, /* MAGNETIC MONOPOLES */
+ EXOTIC, /* DEGENERATE MATTER */
+ EXOTIC, /* SUPER FLUIDS */
+ EXOTIC, /* AGUUTI NODULES */
+ COMMON, /* IRON COMPOUNDS */
+ COMMON, /* ALUMINUM COMPOUNDS */
+ COMMON, /* NITROUS OXIDE */
+ COMMON, /* RADIOACTIVES */
+ COMMON, /* HYDROCARBONS */
+ COMMON, /* CARBON COMPOUNDS */
+ EXOTIC, /* ANTIMATTER */
+ EXOTIC, /* CHARON DUST */
+ EXOTIC, /* REISBURG HELICES */
+ EXOTIC, /* TZO CRYSTALS */
+ COMMON, /* CALCIUM COMPOUNDS */
+ COMMON, /* NITRIC ACID */
+};
+
+/*------------------------------ Global Data ------------------------------ */
+
+#define NO_DEPOSIT 0
+
+#define TRACE_USEFUL MINERAL_DEPOSIT (FEW, LIGHT)
+#define LIGHT_USEFUL MINERAL_DEPOSIT (MODERATE, LIGHT)
+#define MEDIUM_USEFUL MINERAL_DEPOSIT (MODERATE, MEDIUM)
+#define HEAVY_USEFUL MINERAL_DEPOSIT (MODERATE, HEAVY)
+#define HUGE_USEFUL MINERAL_DEPOSIT (NUMEROUS, HEAVY)
+
+const PlanetFrame planet_array[NUMBER_OF_PLANET_TYPES] =
+{
+ { /* OOLITE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ VIOLET_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {HOLMIUM, MEDIUM_USEFUL},
+ {ERBIUM, MEDIUM_USEFUL},
+ {THULIUM, MEDIUM_USEFUL},
+ {YTTERBIUM, MEDIUM_USEFUL},
+ {LUTETIUM, MEDIUM_USEFUL},
+ {PALLADIUM, MEDIUM_USEFUL},
+ {SILVER, MEDIUM_USEFUL},
+ {IRIDIUM, MEDIUM_USEFUL},
+ },
+ OOLITE_COLOR_TAB,
+ OOLITE_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* YTTRIC_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ VIOLET_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {YTTERBIUM, HUGE_USEFUL},
+ {YTTRIUM, HUGE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ YTTRIC_COLOR_TAB,
+ YTTRIC_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* QUASI_DEGENERATE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + TOPO_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ MED_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {DEGENERATE_MATTER, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ QUASI_DEGENERATE_COLOR_TAB,
+ QUASI_DEGENERATE_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* LANTHANIDE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ YELLOW_BODY), /* Color and type/size of planet */
+ HIGH_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {LANTHANUM, MEDIUM_USEFUL},
+ {CERIUM, MEDIUM_USEFUL},
+ {PRASEODYMIUM, MEDIUM_USEFUL},
+ {NEODYMIUM, MEDIUM_USEFUL},
+ {PROMETHIUM, MEDIUM_USEFUL},
+ {SAMARIUM, MEDIUM_USEFUL},
+ {GADOLINIUM, MEDIUM_USEFUL},
+ {TERBIUM, MEDIUM_USEFUL},
+ },
+ LANTHANIDE_COLOR_TAB,
+ LANTHANIDE_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* TREASURE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + TOPO_ALGO,
+ YELLOW_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (SUPER_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {PALLADIUM, HEAVY_USEFUL},
+ {SILVER, HEAVY_USEFUL},
+ {SILVER, HEAVY_USEFUL},
+ {IRIDIUM, HEAVY_USEFUL},
+ {GOLD, HEAVY_USEFUL},
+ {PLATINUM, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ TREASURE_COLOR_TAB,
+ TREASURE_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* UREA_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ YELLOW_BODY), /* Color and type/size of planet */
+ HIGH_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {AMMONIA, LIGHT_USEFUL},
+ {FORMALDEHYDE, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ UREA_COLOR_TAB,
+ UREA_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* METAL_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ ORANGE_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {IRON, HUGE_USEFUL},
+ {NICKEL, HEAVY_USEFUL},
+ {VANADIUM, MEDIUM_USEFUL},
+ {SILVER, MEDIUM_USEFUL},
+ {URANIUM, HEAVY_USEFUL},
+ {SULFUR, MEDIUM_USEFUL},
+ {COPPER, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ },
+ METAL_COLOR_TAB,
+ METAL_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* RADIOACTIVE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ ORANGE_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (SUPER_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {ASTATINE, MEDIUM_USEFUL},
+ {FRANCIUM, MEDIUM_USEFUL},
+ {RADIUM, MEDIUM_USEFUL},
+ {ACTINIUM, MEDIUM_USEFUL},
+ {THORIUM, MEDIUM_USEFUL},
+ {PROTACTINIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ RADIOACTIVE_COLOR_TAB,
+ RADIOACTIVE_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* OPALESCENT_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ CYAN_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {SAMARIUM, LIGHT_USEFUL},
+ {GADOLINIUM, LIGHT_USEFUL},
+ {ARGON, LIGHT_USEFUL},
+ {LITHIUM, LIGHT_USEFUL},
+ {SILICON, LIGHT_USEFUL},
+ {ARSENIC, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ OPALESCENT_COLOR_TAB,
+ OPALESCENT_XLAT_TAB,
+ 400, 1, 100, 190,
+ },
+ { /* CYANIC_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + TOPO_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LIGHT_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {CYANOACETYLENE, HUGE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ CYANIC_COLOR_TAB,
+ CYANIC_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* ACID_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {SULFURIC_ACID, HEAVY_USEFUL},
+ {HYDROCHLORIC_ACID, HEAVY_USEFUL},
+ {FORMIC_ACID, HEAVY_USEFUL},
+ {HYDROCYANIC_ACID, HEAVY_USEFUL},
+ {PHOSPHORIC_ACID, HEAVY_USEFUL},
+ {NITRIC_ACID, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ ACID_COLOR_TAB,
+ ACID_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* ALKALI_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {CALCIUM, MEDIUM_USEFUL},
+ {BARIUM, MEDIUM_USEFUL},
+ {STRONTIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ ALKALI_COLOR_TAB,
+ ALKALI_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* HALIDE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {FLUORINE, MEDIUM_USEFUL},
+ {BROMINE, MEDIUM_USEFUL},
+ {BROMINE, MEDIUM_USEFUL},
+ {ASTATINE, MEDIUM_USEFUL},
+ {IODINE, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ HALIDE_COLOR_TAB,
+ HALIDE_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* GREEN_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ MED_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {PRASEODYMIUM, HEAVY_USEFUL},
+ {NEODYMIUM, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ GREEN_COLOR_TAB,
+ GREEN_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* COPPER_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + TOPO_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {COPPER, HUGE_USEFUL},
+ {COPPER, HUGE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ COPPER_COLOR_TAB,
+ COPPER_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* CARBIDE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {CARBON, HEAVY_USEFUL},
+ {CARBON, HEAVY_USEFUL},
+ {CARBON, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ CARBIDE_COLOR_TAB,
+ CARBIDE_XLAT_TAB,
+ 400, 1, 100, 190,
+ },
+ { /* ULTRAMARINE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ BLUE_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LIGHT_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {KRYPTON, MEDIUM_USEFUL},
+ {COBALT, MEDIUM_USEFUL},
+ {HOLMIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ ULTRAMARINE_COLOR_TAB,
+ ULTRAMARINE_XLAT_TAB,
+ 200, 2, 100, 100,
+ },
+ { /* NOBLE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ BLUE_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LIGHT_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {NEON, LIGHT_USEFUL},
+ {RADON, LIGHT_USEFUL},
+ {ARGON, LIGHT_USEFUL},
+ {KRYPTON, LIGHT_USEFUL},
+ {XENON, LIGHT_USEFUL},
+ {HELIUM, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ NOBLE_COLOR_TAB,
+ NOBLE_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* AZURE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ BLUE_BODY), /* Color and type/size of planet */
+ MED_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {INDIUM, LIGHT_USEFUL},
+ {MOLYBDENUM, LIGHT_USEFUL},
+ {VANADIUM, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ AZURE_COLOR_TAB,
+ AZURE_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* CHONDRITE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ PURPLE_BODY), /* Color and type/size of planet */
+ HIGH_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {ETHANOL, HEAVY_USEFUL},
+ {FREE_RADICALS, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ CHONDRITE_COLOR_TAB,
+ CHONDRITE_XLAT_TAB,
+ 500, 1, 100, 190,
+ },
+ { /* PURPLE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ PURPLE_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {RHENIUM, MEDIUM_USEFUL},
+ {CADMIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ PURPLE_COLOR_TAB,
+ PURPLE_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* SUPER_DENSE_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + TOPO_ALGO,
+ PURPLE_BODY), /* Color and type/size of planet */
+ HIGH_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (SUPER_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {LEAD, MEDIUM_USEFUL},
+ {OSMIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ SUPER_DENSE_COLOR_TAB,
+ SUPER_DENSE_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* PELLUCID_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ PURPLE_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {TZO_CRYSTALS, TRACE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ PELLUCID_COLOR_TAB,
+ PELLUCID_XLAT_TAB,
+ 400, 1, 100, 190,
+ },
+ { /* DUST_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {BISMUTH, LIGHT_USEFUL},
+ {ALUMINUM, LIGHT_USEFUL},
+ {POTASSIUM, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ DUST_COLOR_TAB,
+ DUST_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* CRIMSON_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {BARIUM, LIGHT_USEFUL},
+ {BORON, LIGHT_USEFUL},
+ {BERYLLIUM, LIGHT_USEFUL},
+ {BISMUTH, LIGHT_USEFUL},
+ {BROMINE, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ CRIMSON_COLOR_TAB,
+ CRIMSON_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* CIMMERIAN_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + TOPO_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {METHANE, MEDIUM_USEFUL},
+ {AMMONIA, MEDIUM_USEFUL},
+ {METHANOL, MEDIUM_USEFUL},
+ {LITHIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ CIMMERIAN_COLOR_TAB,
+ CIMMERIAN_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* INFRARED_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {MERCURY, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ INFRARED_COLOR_TAB,
+ INFRARED_XLAT_TAB,
+ 400, 1, 100, 190,
+ },
+ { /* SELENIC_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + CRATERED_ALGO,
+ WHITE_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {IRON, LIGHT_USEFUL},
+ {ALUMINUM, LIGHT_USEFUL},
+ {CALCIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ SELENIC_COLOR_TAB,
+ SELENIC_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* AURIC_WORLD */
+ MAKE_BYTE (SMALL_ROCKY_WORLD + TOPO_ALGO,
+ YELLOW_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {GOLD, HUGE_USEFUL},
+ {GOLD, HUGE_USEFUL},
+ {GOLD, HUGE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ AURIC_COLOR_TAB,
+ AURIC_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+
+
+ { /* FLUORESCENT_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ VIOLET_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {TECHNETIUM, HUGE_USEFUL},
+ {NEON, HUGE_USEFUL},
+ {RADON, LIGHT_USEFUL},
+ {POTASSIUM, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ FLUORESCENT_COLOR_TAB,
+ FLUORESCENT_XLAT_TAB,
+ 400, 1, 100, 190,
+ },
+ { /* ULTRAVIOLET_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ VIOLET_BODY), /* Color and type/size of planet */
+ MED_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {POLONIUM, HUGE_USEFUL},
+ {GOLD, HUGE_USEFUL},
+ {PHOSPHORUS, HUGE_USEFUL},
+ {SCANDIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ ULTRAVIOLET_COLOR_TAB,
+ ULTRAVIOLET_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* PLUTONIC_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ YELLOW_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {BERYLLIUM, HUGE_USEFUL},
+ {BORON, HUGE_USEFUL},
+ {LANTHANUM, MEDIUM_USEFUL},
+ {ASTATINE, MEDIUM_USEFUL},
+ {FRANCIUM, MEDIUM_USEFUL},
+ {TITANIUM, LIGHT_USEFUL},
+ {CERIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ },
+ PLUTONIC_COLOR_TAB,
+ PLUTONIC_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* RAINBOW_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + TOPO_ALGO,
+ YELLOW_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {ACTINIUM, HEAVY_USEFUL},
+ {THORIUM, HEAVY_USEFUL},
+ {PROTACTINIUM, HEAVY_USEFUL},
+ {NEPTUNIUM, HEAVY_USEFUL},
+ {PLUTONIUM, HEAVY_USEFUL},
+ {OZONE, HUGE_USEFUL},
+ {OZONE, HUGE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ },
+ RAINBOW_COLOR_TAB,
+ RAINBOW_XLAT_TAB,
+ 500, 1, 20, 100,
+ },
+ { /* SHATTERED_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ ORANGE_BODY), /* Color and type/size of planet */
+ SUPER_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {PALLADIUM, HUGE_USEFUL},
+ {IRIDIUM, HUGE_USEFUL},
+ {TECHNETIUM, HUGE_USEFUL},
+ {POLONIUM, HUGE_USEFUL},
+ {SODIUM, HUGE_USEFUL},
+ {MANGANESE, HUGE_USEFUL},
+ {CHROMIUM, HUGE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ },
+ SHATTERED_COLOR_TAB,
+ SHATTERED_XLAT_TAB,
+ 500, 1, 0, 185,
+ },
+ { /* SAPPHIRE_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + TOPO_ALGO,
+ CYAN_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {REISBURG_HELICES, HUGE_USEFUL},
+ {RT_SUPER_FLUID, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ SAPPHIRE_COLOR_TAB,
+ SAPPHIRE_XLAT_TAB,
+ 80, 1, 0, 128,
+ },
+ { /* ORGANIC_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + TOPO_ALGO,
+ CYAN_BODY), /* Color and type/size of planet */
+ MED_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {FREE_RADICALS, HEAVY_USEFUL},
+ {FORMALDEHYDE, HEAVY_USEFUL},
+ {CARBON, HEAVY_USEFUL},
+ {CARBON_DIOXIDE, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ ORGANIC_COLOR_TAB,
+ ORGANIC_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* XENOLITHIC_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ CYAN_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {ALUMINUM, HUGE_USEFUL},
+ {PLATINUM, LIGHT_USEFUL},
+ {GERMANIUM, TRACE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ XENOLITHIC_COLOR_TAB,
+ XENOLITHIC_XLAT_TAB,
+ 400, 1, 100, 190,
+ },
+ { /* REDUX_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {BROMINE, MEDIUM_USEFUL},
+ {OXYGEN, MEDIUM_USEFUL},
+ {FLUORINE, MEDIUM_USEFUL},
+ {SULFUR, MEDIUM_USEFUL},
+ {CHLORINE, MEDIUM_USEFUL},
+ {IODINE, MEDIUM_USEFUL},
+ {ZIRCONIUM, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ },
+ REDUX_COLOR_TAB,
+ REDUX_XLAT_TAB,
+ 500, 1, 0, 190,
+ },
+ { /* PRIMORDIAL_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ SUPER_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {CESIUM, MEDIUM_USEFUL},
+ {BARIUM, MEDIUM_USEFUL},
+ {RUBIDIUM, MEDIUM_USEFUL},
+ {METHANE, HUGE_USEFUL},
+ {AMMONIA, HUGE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ PRIMORDIAL_COLOR_TAB,
+ PRIMORDIAL_XLAT_TAB,
+ 250, 2, 10, 200,
+ },
+ { /* EMERALD_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + TOPO_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {AGUUTI_NODULES, HUGE_USEFUL},
+ {ANTIMATTER, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ EMERALD_COLOR_TAB,
+ EMERALD_XLAT_TAB,
+ 80, 1, 0, 128,
+ },
+ { /* CHLORINE_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ HIGH_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {CHLORINE, HEAVY_USEFUL},
+ {CHLORINE, HEAVY_USEFUL},
+ {HYDROCHLORIC_ACID, HEAVY_USEFUL},
+ {HYDROCHLORIC_ACID, HEAVY_USEFUL},
+ {ZINC, LIGHT_USEFUL},
+ {GALLIUM, TRACE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ CHLORINE_COLOR_TAB,
+ CHLORINE_XLAT_TAB,
+ 500, 1, 0, 190,
+ },
+ { /* MAGNETIC_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ HIGH_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {ZINC, HEAVY_USEFUL},
+ {NICKEL, MEDIUM_USEFUL},
+ {MAGNETIC_MONOPOLES, TRACE_USEFUL},
+ {NIOBIUM, LIGHT_USEFUL},
+ {IRON, HEAVY_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ MAGNETIC_COLOR_TAB,
+ MAGNETIC_XLAT_TAB,
+ 400, 1, 100, 190,
+ },
+ { /* WATER_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ BLUE_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {IRON, LIGHT_USEFUL},
+ {ALUMINUM, LIGHT_USEFUL},
+ {TIN, LIGHT_USEFUL},
+ {LEAD, LIGHT_USEFUL},
+ {URANIUM, TRACE_USEFUL},
+ {MOLYBDENUM, TRACE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ WATER_COLOR_TAB,
+ WATER_XLAT_TAB,
+ 500, 1, 0, 190,
+ },
+ { /* TELLURIC_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ BLUE_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {IRIDIUM, MEDIUM_USEFUL},
+ {RUTHENIUM, MEDIUM_USEFUL},
+ {THALLIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ TELLURIC_COLOR_TAB,
+ TELLURIC_XLAT_TAB,
+ 250, 2, 80, 200,
+ },
+ { /* HYDROCARBON_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + TOPO_ALGO,
+ BLUE_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {HYDROCARBONS, HUGE_USEFUL},
+ {BISMUTH, LIGHT_USEFUL},
+ {TANTALUM, TRACE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ HYDROCARBON_COLOR_TAB,
+ HYDROCARBON_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* IODINE_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ MED_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {IODINE, HEAVY_USEFUL},
+ {MAGNESIUM, LIGHT_USEFUL},
+ {TUNGSTEN, TRACE_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ IODINE_COLOR_TAB,
+ IODINE_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+ { /* VINYLOGOUS_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ PURPLE_BODY), /* Color and type/size of planet */
+ LOW_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (NORMAL_DENSITY, NOTHING), /* Atmosphere and density */
+ {
+ {TITANIUM, LIGHT_USEFUL},
+ {ARSENIC, LIGHT_USEFUL},
+ {POTASSIUM, LIGHT_USEFUL},
+ {RHENIUM, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ VINYLOGOUS_COLOR_TAB,
+ VINYLOGOUS_XLAT_TAB,
+ 400, 1, 100, 190,
+ },
+ { /* RUBY_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + TOPO_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {TZO_CRYSTALS, HUGE_USEFUL},
+ {NEUTRONIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ RUBY_COLOR_TAB,
+ RUBY_XLAT_TAB,
+ 80, 1, 0, 128,
+ },
+ { /* MAGMA_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + TOPO_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ SUPER_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (HIGH_DENSITY, LIGHT), /* Atmosphere and density */
+ {
+ {LEAD, LIGHT_USEFUL},
+ {NICKEL, LIGHT_USEFUL},
+ {IRON, LIGHT_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ MAGMA_COLOR_TAB,
+ MAGMA_XLAT_TAB,
+ 500, 1, 0, 160,
+ },
+ { /* MAROON_WORLD */
+ MAKE_BYTE (LARGE_ROCKY_WORLD + CRATERED_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (LOW_DENSITY, MEDIUM), /* Atmosphere and density */
+ {
+ {CESIUM, MEDIUM_USEFUL},
+ {SILICON, MEDIUM_USEFUL},
+ {PHOSPHORUS, MEDIUM_USEFUL},
+ {RHODIUM, MEDIUM_USEFUL},
+ {CADMIUM, MEDIUM_USEFUL},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ MAROON_COLOR_TAB,
+ MAROON_XLAT_TAB,
+ 230, 2, 200, 150,
+ },
+
+ {
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ BLUE_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ BLU_GAS_COLOR_TAB,
+ BLU_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+ {
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ CYAN_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ CYA_GAS_COLOR_TAB,
+ CYA_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+ {
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ GREEN_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ GRN_GAS_COLOR_TAB,
+ GRN_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+ {
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ GRAY_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ GRY_GAS_COLOR_TAB,
+ GRY_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+ {
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ ORANGE_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ ORA_GAS_COLOR_TAB,
+ ORA_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+ {
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ PURPLE_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ PUR_GAS_COLOR_TAB,
+ PUR_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+ {
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ RED_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ RED_GAS_COLOR_TAB,
+ RED_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+ {
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ VIOLET_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ VIO_GAS_COLOR_TAB,
+ VIO_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+ { /* A Jupiter-like World */
+ MAKE_BYTE (GAS_GIANT + GAS_GIANT_ALGO,
+ YELLOW_BODY), /* Color and type/size of planet */
+ NO_TECTONICS, /* Tectonics - Scaled with Earth at 82 */
+ MAKE_BYTE (GAS_DENSITY, HEAVY), /* Atmosphere and density */
+ {
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ {NOTHING, NO_DEPOSIT},
+ },
+ YEL_GAS_COLOR_TAB,
+ YEL_GAS_XLAT_TAB,
+ 10, 2, 8, 29,
+ },
+};
+
diff --git a/src/uqm/planets/Makeinfo b/src/uqm/planets/Makeinfo
new file mode 100644
index 0000000..da4026f
--- /dev/null
+++ b/src/uqm/planets/Makeinfo
@@ -0,0 +1,7 @@
+uqm_SUBDIRS="generate"
+uqm_CFILES="calc.c cargo.c devices.c gentopo.c lander.c orbits.c
+ oval.c pl_stuff.c planets.c plangen.c pstarmap.c report.c
+ roster.c scan.c solarsys.c surface.c"
+uqm_HFILES="elemdata.h generate.h lander.h lifeform.h plandata.h planets.h
+ scan.h solarsys.h sundata.h"
+
diff --git a/src/uqm/planets/calc.c b/src/uqm/planets/calc.c
new file mode 100644
index 0000000..09bd461
--- /dev/null
+++ b/src/uqm/planets/calc.c
@@ -0,0 +1,530 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* ----------------------------- INCLUDES ---------------------------- */
+#include "planets.h"
+#include "uqm/starmap.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+/* -------------------------------- DATA -------------------------------- */
+
+/* -------------------------------- CODE -------------------------------- */
+
+//#define DEBUG_PLANET_CALC
+
+#define LOW_TEMP 0
+#define MED_TEMP 500
+#define HIGH_TEMP 1500
+#define LOW_TEMP_BONUS 10
+#define MED_TEMP_BONUS 25
+#define HIGH_TEMP_BONUS 50
+#define MAX_TECTONICS 255
+
+enum
+{
+ RED_SUN_INTENSITY = 0,
+ ORANGE_SUN_INTENSITY,
+ YELLOW_SUN_INTENSITY,
+ GREEN_SUN_INTENSITY,
+ BLUE_SUN_INTENSITY,
+ WHITE_SUN_INTENSITY
+};
+
+static UWORD
+CalcFromBase (UWORD base, UWORD variance)
+{
+ return base + LOWORD (RandomContext_Random (SysGenRNG)) % variance;
+}
+
+static inline UWORD
+CalcHalfBaseVariance (UWORD base)
+{
+ return CalcFromBase (base, (base >> 1) + 1);
+}
+
+static void
+CalcSysInfo (SYSTEM_INFO *SysInfoPtr)
+{
+ SysInfoPtr->StarSize = pSolarSysState->SunDesc[0].data_index;
+ switch (STAR_COLOR (CurStarDescPtr->Type))
+ {
+ case BLUE_BODY:
+ SysInfoPtr->StarIntensity = BLUE_SUN_INTENSITY;
+ break;
+ case GREEN_BODY:
+ SysInfoPtr->StarIntensity = GREEN_SUN_INTENSITY;
+ break;
+ case ORANGE_BODY:
+ SysInfoPtr->StarIntensity = ORANGE_SUN_INTENSITY;
+ break;
+ case RED_BODY:
+ SysInfoPtr->StarIntensity = RED_SUN_INTENSITY;
+ break;
+ case WHITE_BODY:
+ SysInfoPtr->StarIntensity = WHITE_SUN_INTENSITY;
+ break;
+ case YELLOW_BODY:
+ SysInfoPtr->StarIntensity = YELLOW_SUN_INTENSITY;
+ break;
+ }
+
+ switch (STAR_TYPE (CurStarDescPtr->Type))
+ {
+ case DWARF_STAR:
+ SysInfoPtr->StarEnergy =
+ (SysInfoPtr->StarIntensity + 1) * DWARF_ENERGY;
+ break;
+ case GIANT_STAR:
+ SysInfoPtr->StarEnergy =
+ (SysInfoPtr->StarIntensity + 1) * GIANT_ENERGY;
+ break;
+ case SUPER_GIANT_STAR:
+ SysInfoPtr->StarEnergy =
+ (SysInfoPtr->StarIntensity + 1) * SUPERGIANT_ENERGY;
+ break;
+ }
+}
+
+static UWORD
+GeneratePlanetComposition (PLANET_INFO *PlanetInfoPtr, SIZE SurfaceTemp,
+ SIZE radius)
+{
+ if (PLANSIZE (PlanetInfoPtr->PlanDataPtr->Type) == GAS_GIANT)
+ {
+ PlanetInfoPtr->Weather = 7 << 5;
+ return (GAS_GIANT_ATMOSPHERE);
+ }
+ else
+ {
+ BYTE range;
+ UWORD atmo;
+
+ PlanetInfoPtr->Weather = 0;
+ atmo = 0;
+ if ((range = HINIBBLE (PlanetInfoPtr->PlanDataPtr->AtmoAndDensity)) <= HEAVY)
+ {
+ if (SurfaceTemp < COLD_THRESHOLD)
+ --range;
+ else if (SurfaceTemp > HOT_THRESHOLD)
+ ++range;
+
+ if (range <= HEAVY + 1)
+ {
+ switch (range)
+ {
+ case LIGHT:
+ atmo = THIN_ATMOSPHERE;
+ PlanetInfoPtr->Weather = 1 << 5;
+ break;
+ case MEDIUM:
+ atmo = NORMAL_ATMOSPHERE;
+ PlanetInfoPtr->Weather = 2 << 5;
+ break;
+ case HEAVY:
+ atmo = THICK_ATMOSPHERE;
+ PlanetInfoPtr->Weather = 4 << 5;
+ break;
+ default:
+ atmo = SUPER_THICK_ATMOSPHERE;
+ PlanetInfoPtr->Weather = 6 << 5;
+ break;
+ }
+
+ radius /= EARTH_RADIUS;
+ if (radius < 2)
+ PlanetInfoPtr->Weather += 1 << 5;
+ else if (radius > 10)
+ PlanetInfoPtr->Weather -= 1 << 5;
+ atmo = CalcHalfBaseVariance (atmo);
+ }
+ }
+
+ return (atmo);
+ }
+}
+
+// This function is called both when the solar system is generated,
+// and when planetary orbit is entered.
+// In the former case, the if() block will not be executed,
+// which means that the temperature calculated in that case will be
+// slightly lower. The result is that the orbits may not always
+// have the colour you'd expect based on the true temperature.
+// (eg. Beta Corvi I). I don't know what the idea behind this is,
+// but the if statement must be there for a reason. -- SvdB
+// Update 2013-03-28: The contents of the if() block is probably there to
+// model a greenhouse effect. It seems that it is taken into account when
+// calculating the actual temperature (when landing or scanning), but not
+// when determining the colors of the drawn orbits. (Thanks to James Scott
+// for this insight.)
+static SIZE
+CalcTemp (SYSTEM_INFO *SysInfoPtr, SIZE radius)
+{
+#define GENERIC_ALBEDO 33 /* In %, 0=black, 100 is reflective */
+#define ADJUST_FOR_KELVIN 273
+#define PLANET_TEMP_CONSTANT 277L
+ DWORD alb;
+ SIZE centigrade, bonus;
+
+ alb = 100 - GENERIC_ALBEDO;
+ alb = square_root (square_root (alb * 100 * 10000))
+ * PLANET_TEMP_CONSTANT * SysInfoPtr->StarEnergy
+ / ((YELLOW_SUN_INTENSITY + 1) * DWARF_ENERGY);
+
+ centigrade = (SIZE)(alb / square_root (radius * 10000L / EARTH_RADIUS))
+ - ADJUST_FOR_KELVIN;
+
+ bonus = 0;
+ if (SysInfoPtr == &pSolarSysState->SysInfo
+ && HINIBBLE (SysInfoPtr->PlanetInfo.PlanDataPtr->AtmoAndDensity) <= HEAVY)
+ {
+#define COLD_BONUS 20
+#define HOT_BONUS 200
+ if (centigrade >= HOT_THRESHOLD)
+ bonus = HOT_BONUS;
+ else if (centigrade >= COLD_THRESHOLD)
+ bonus = COLD_BONUS;
+
+ bonus <<= HINIBBLE (SysInfoPtr->PlanetInfo.PlanDataPtr->AtmoAndDensity);
+ bonus = CalcHalfBaseVariance (bonus);
+ }
+
+ return (centigrade + bonus);
+}
+
+static COUNT
+CalcRotation (PLANET_INFO *PlanetInfoPtr)
+{
+ if (PLANSIZE (PlanetInfoPtr->PlanDataPtr->Type) == GAS_GIANT)
+ return CalcFromBase (80, 80);
+ else if (LOBYTE (RandomContext_Random (SysGenRNG)) % 10 == 0)
+ return CalcFromBase (50 * 240, 200 * 240);
+ else
+ return CalcFromBase (150, 150);
+}
+
+static SIZE
+CalcTilt (void)
+{ /* Calculate Axial Tilt */
+ SIZE tilt;
+ BYTE i;
+
+#define NUM_TOSSES 10
+#define TILT_RANGE 180
+ tilt = -(TILT_RANGE / 2);
+ i = NUM_TOSSES;
+ do /* Using added Randomom values to give bell curve */
+ {
+ tilt += LOWORD (RandomContext_Random (SysGenRNG))
+ % ((TILT_RANGE / NUM_TOSSES) + 1);
+ } while (--i);
+
+ return (tilt);
+}
+
+UWORD
+CalcGravity (const PLANET_INFO *PlanetInfoPtr)
+{
+ return (DWORD)PlanetInfoPtr->PlanetDensity * PlanetInfoPtr->PlanetRadius
+ / 100;
+}
+
+static UWORD
+CalcTectonics (UWORD base, UWORD temp)
+{
+ UWORD tect = CalcFromBase (base, 3 << 5);
+#ifdef OLD
+ if (temp >= HIGH_TEMP)
+ tect += HIGH_TEMP_BONUS;
+ else if (temp >= MED_TEMP)
+ tect += MED_TEMP_BONUS;
+ else if (temp >= LOW_TEMP)
+ tect += LOW_TEMP_BONUS;
+#else /* !OLD */
+ (void) temp; /* silence compiler whining */
+#endif /* OLD */
+ return tect;
+}
+
+// This code moved from planets/surface.c:CalcLifeForms()
+static int
+CalcLifeChance (const PLANET_INFO *PlanetInfoPtr)
+{
+ SIZE life_var = 0;
+
+ if (PLANSIZE (PlanetInfoPtr->PlanDataPtr->Type) == GAS_GIANT)
+ return -1;
+
+ if (PlanetInfoPtr->SurfaceTemperature < -151)
+ life_var -= 300;
+ else if (PlanetInfoPtr->SurfaceTemperature < -51)
+ life_var -= 100;
+ else if (PlanetInfoPtr->SurfaceTemperature < 0)
+ life_var += 100;
+ else if (PlanetInfoPtr->SurfaceTemperature < 50)
+ life_var += 300;
+ else if (PlanetInfoPtr->SurfaceTemperature < 150)
+ life_var += 50;
+ else if (PlanetInfoPtr->SurfaceTemperature < 250)
+ life_var -= 100;
+ else if (PlanetInfoPtr->SurfaceTemperature < 500)
+ life_var -= 400;
+ else
+ life_var -= 800;
+
+ if (PlanetInfoPtr->AtmoDensity == 0)
+ life_var -= 1000;
+ else if (PlanetInfoPtr->AtmoDensity < 15)
+ life_var += 100;
+ else if (PlanetInfoPtr->AtmoDensity < 30)
+ life_var += 200;
+ else if (PlanetInfoPtr->AtmoDensity < 100)
+ life_var += 300;
+ else if (PlanetInfoPtr->AtmoDensity < 1000)
+ life_var += 150;
+ else if (PlanetInfoPtr->AtmoDensity < 2500)
+ ;
+ else
+ life_var -= 100;
+
+#ifndef NOTYET
+ life_var += 200 + 80 + 80;
+#else /* NOTYET */
+ if (PlanetInfoPtr->SurfaceGravity < 10)
+ ;
+ else if (PlanetInfoPtr->SurfaceGravity < 35)
+ life_var += 50;
+ else if (PlanetInfoPtr->SurfaceGravity < 75)
+ life_var += 100;
+ else if (PlanetInfoPtr->SurfaceGravity < 150)
+ life_var += 200;
+ else if (PlanetInfoPtr->SurfaceGravity < 400)
+ life_var += 50;
+ else if (PlanetInfoPtr->SurfaceGravity < 800)
+ ;
+ else
+ life_var -= 100;
+
+ if (PlanetInfoPtr->Tectonics < 1)
+ life_var += 80;
+ else if (PlanetInfoPtr->Tectonics < 2)
+ life_var += 70;
+ else if (PlanetInfoPtr->Tectonics < 3)
+ life_var += 60;
+ else if (PlanetInfoPtr->Tectonics < 4)
+ life_var += 50;
+ else if (PlanetInfoPtr->Tectonics < 5)
+ life_var += 25;
+ else if (PlanetInfoPtr->Tectonics < 6)
+ ;
+ else
+ life_var -= 100;
+
+ if (PlanetInfoPtr->Weather < 1)
+ life_var += 80;
+ else if (PlanetInfoPtr->Weather < 2)
+ life_var += 70;
+ else if (PlanetInfoPtr->Weather < 3)
+ life_var += 60;
+ else if (PlanetInfoPtr->Weather < 4)
+ life_var += 50;
+ else if (PlanetInfoPtr->Weather < 5)
+ life_var += 25;
+ else if (PlanetInfoPtr->Weather < 6)
+ ;
+ else
+ life_var -= 100;
+#endif /* NOTYET */
+
+ return life_var;
+}
+
+// Sets the SysGenRNG to the required state first.
+void
+DoPlanetaryAnalysis (SYSTEM_INFO *SysInfoPtr, PLANET_DESC *pPlanetDesc)
+{
+ assert ((pPlanetDesc->data_index & ~WORLD_TYPE_SPECIAL)
+ < NUMBER_OF_PLANET_TYPES);
+
+ RandomContext_SeedRandom (SysGenRNG, pPlanetDesc->rand_seed);
+
+ CalcSysInfo (SysInfoPtr);
+
+#ifdef DEBUG_PLANET_CALC
+ {
+ BYTE ColorClass[6][8] = {
+ "Red",
+ "Orange",
+ "Yellow",
+ "Green",
+ "Blue",
+ "White",
+ };
+ BYTE SizeName[3][12] = {
+ "Dwarf",
+ "Giant",
+ "Supergiant",
+ };
+
+ log_add (log_Debug, "%s %s",
+ ColorClass[SysInfoPtr->StarIntensity],
+ SizeName[SysInfoPtr->StarSize]);
+ log_add (log_Debug, "Stellar Energy: %d (sol = 3)",
+ SysInfoPtr->StarEnergy);
+ }
+#endif /* DEBUG_PLANET_CALC */
+
+ {
+ SIZE radius;
+
+ SysInfoPtr->PlanetInfo.PlanDataPtr =
+ &PlanData[pPlanetDesc->data_index & ~PLANET_SHIELDED];
+
+ if (pPlanetDesc->pPrevDesc == pSolarSysState->SunDesc)
+ radius = pPlanetDesc->radius;
+ else
+ radius = pPlanetDesc->pPrevDesc->radius;
+ SysInfoPtr->PlanetInfo.PlanetToSunDist = radius;
+
+ SysInfoPtr->PlanetInfo.SurfaceTemperature =
+ CalcTemp (SysInfoPtr, radius);
+ switch (LONIBBLE (SysInfoPtr->PlanetInfo.PlanDataPtr->AtmoAndDensity))
+ {
+ case GAS_DENSITY:
+ SysInfoPtr->PlanetInfo.PlanetDensity = 20;
+ break;
+ case LIGHT_DENSITY:
+ SysInfoPtr->PlanetInfo.PlanetDensity = 33;
+ break;
+ case LOW_DENSITY:
+ SysInfoPtr->PlanetInfo.PlanetDensity = 60;
+ break;
+ case NORMAL_DENSITY:
+ SysInfoPtr->PlanetInfo.PlanetDensity = 100;
+ break;
+ case HIGH_DENSITY:
+ SysInfoPtr->PlanetInfo.PlanetDensity = 150;
+ break;
+ case SUPER_DENSITY:
+ SysInfoPtr->PlanetInfo.PlanetDensity = 200;
+ break;
+ }
+ SysInfoPtr->PlanetInfo.PlanetDensity +=
+ (SysInfoPtr->PlanetInfo.PlanetDensity / 20)
+ - (LOWORD (RandomContext_Random (SysGenRNG))
+ % (SysInfoPtr->PlanetInfo.PlanetDensity / 10));
+
+ switch (PLANSIZE (SysInfoPtr->PlanetInfo.PlanDataPtr->Type))
+ {
+ case SMALL_ROCKY_WORLD:
+#define SMALL_RADIUS 25
+ SysInfoPtr->PlanetInfo.PlanetRadius = CalcHalfBaseVariance (SMALL_RADIUS);
+ break;
+ case LARGE_ROCKY_WORLD:
+#define LARGE_RADIUS 75
+ SysInfoPtr->PlanetInfo.PlanetRadius = CalcHalfBaseVariance (LARGE_RADIUS);
+ break;
+ case GAS_GIANT:
+#define MIN_GAS_RADIUS 300
+#define MAX_GAS_RADIUS 1500
+ SysInfoPtr->PlanetInfo.PlanetRadius =
+ CalcFromBase (MIN_GAS_RADIUS, MAX_GAS_RADIUS - MIN_GAS_RADIUS);
+ break;
+ }
+
+ SysInfoPtr->PlanetInfo.RotationPeriod = CalcRotation (&SysInfoPtr->PlanetInfo);
+ SysInfoPtr->PlanetInfo.SurfaceGravity = CalcGravity (&SysInfoPtr->PlanetInfo);
+ SysInfoPtr->PlanetInfo.AxialTilt = CalcTilt ();
+ if ((SysInfoPtr->PlanetInfo.Tectonics =
+ CalcTectonics (SysInfoPtr->PlanetInfo.PlanDataPtr->BaseTectonics,
+ SysInfoPtr->PlanetInfo.SurfaceTemperature)) > MAX_TECTONICS)
+ SysInfoPtr->PlanetInfo.Tectonics = MAX_TECTONICS;
+
+ SysInfoPtr->PlanetInfo.AtmoDensity =
+ GeneratePlanetComposition (&SysInfoPtr->PlanetInfo,
+ SysInfoPtr->PlanetInfo.SurfaceTemperature, radius);
+
+ SysInfoPtr->PlanetInfo.Tectonics >>= 5;
+ SysInfoPtr->PlanetInfo.Weather >>= 5;
+
+ SysInfoPtr->PlanetInfo.LifeChance = CalcLifeChance (&SysInfoPtr->PlanetInfo);
+
+#ifdef DEBUG_PLANET_CALC
+ radius = (SIZE)((DWORD)UNSCALE_RADIUS (radius) * 100 / UNSCALE_RADIUS (EARTH_RADIUS));
+ log_add (log_Debug, "\tOrbital Distance : %d.%02d AU", radius / 100, radius % 100);
+ //log_add (log_Debug, "\tPlanetary Mass : %d.%02d Earth masses",
+ // SysInfoPtr->PlanetInfo.PlanetMass / 100,
+ // SysInfoPtr->PlanetInfo.PlanetMass % 100);
+ log_add (log_Debug, "\tPlanetary Radius : %d.%02d Earth radii",
+ SysInfoPtr->PlanetInfo.PlanetRadius / 100,
+ SysInfoPtr->PlanetInfo.PlanetRadius % 100);
+ log_add (log_Debug, "\tSurface Gravity: %d.%02d gravities",
+ SysInfoPtr->PlanetInfo.SurfaceGravity / 100,
+ SysInfoPtr->PlanetInfo.SurfaceGravity % 100);
+ log_add (log_Debug, "\tSurface Temperature: %d degrees C",
+ SysInfoPtr->PlanetInfo.SurfaceTemperature );
+ log_add (log_Debug, "\tAxial Tilt : %d degrees",
+ abs (SysInfoPtr->PlanetInfo.AxialTilt));
+ log_add (log_Debug, "\tTectonics : Class %u",
+ SysInfoPtr->PlanetInfo.Tectonics + 1);
+ log_add (log_Debug, "\tAtmospheric Density: %u.%02u",
+ SysInfoPtr->PlanetInfo.AtmoDensity / EARTH_ATMOSPHERE,
+ (SysInfoPtr->PlanetInfo.AtmoDensity * 100 / EARTH_ATMOSPHERE) % 100);
+ if (SysInfoPtr->PlanetInfo.AtmoDensity == 0)
+ {
+ log_add (log_Debug, "\tAtmosphere: (Vacuum)");
+ }
+ else if (SysInfoPtr->PlanetInfo.AtmoDensity < THIN_ATMOSPHERE)
+ {
+ log_add (log_Debug, "\tAtmosphere: (Thin)");
+ }
+ else if (SysInfoPtr->PlanetInfo.AtmoDensity < NORMAL_ATMOSPHERE)
+ {
+ log_add (log_Debug, "\tAtmosphere: (Normal)");
+ }
+ else if (SysInfoPtr->PlanetInfo.AtmoDensity < THICK_ATMOSPHERE)
+ {
+ log_add (log_Debug, "\tAtmosphere: (Thick)");
+ }
+ else if (SysInfoPtr->PlanetInfo.AtmoDensity < SUPER_THICK_ATMOSPHERE)
+ {
+ log_add (log_Debug, "\tAtmosphere: (Super thick)");
+ }
+ else
+ {
+ log_add (log_Debug, "\tAtmosphere: (Gas Giant)");
+ }
+
+ log_add (log_Debug, "\tWeather : Class %u",
+ SysInfoPtr->PlanetInfo.Weather + 1);
+
+ if (SysInfoPtr->PlanetInfo.RotationPeriod >= 480)
+ {
+ log_add (log_Debug, "\tLength of day : %d.%d Earth days",
+ SysInfoPtr->PlanetInfo.RotationPeriod / 240,
+ SysInfoPtr->PlanetInfo.RotationPeriod % 240);
+ }
+ else
+ {
+ log_add (log_Debug, "\tLength of day : %d.%d Earth hours",
+ SysInfoPtr->PlanetInfo.RotationPeriod / 10,
+ SysInfoPtr->PlanetInfo.RotationPeriod % 10);
+ }
+#endif /* DEBUG_PLANET_CALC */
+ }
+}
+
diff --git a/src/uqm/planets/cargo.c b/src/uqm/planets/cargo.c
new file mode 100644
index 0000000..147d95a
--- /dev/null
+++ b/src/uqm/planets/cargo.c
@@ -0,0 +1,356 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../colors.h"
+#include "../controls.h"
+#include "../gamestr.h"
+#include "../shipcont.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "../util.h"
+#include "../sis.h"
+ // for ClearSISRect(), DrawStatusMessage()
+#include "planets.h"
+#include "libs/graphics/drawable.h"
+ // for GetFrameBounds()
+
+
+#define ELEMENT_ORG_Y 35
+#define FREE_ORG_Y (ELEMENT_ORG_Y + (NUM_ELEMENT_CATEGORIES \
+ * ELEMENT_SPACING_Y))
+#define BIO_ORG_Y 119
+#define ELEMENT_SPACING_Y 9
+
+#define ELEMENT_COL_0 7
+#define ELEMENT_COL_1 32
+#define ELEMENT_COL_2 58
+
+#define ELEMENT_SEL_ORG_X (ELEMENT_COL_0 + 7 + 5)
+#define ELEMENT_SEL_WIDTH (ELEMENT_COL_2 - ELEMENT_SEL_ORG_X + 1)
+
+#define TEXT_BASELINE 6
+
+
+void
+ShowRemainingCapacity (void)
+{
+ RECT r;
+ TEXT t;
+ CONTEXT OldContext;
+ UNICODE buf[40];
+
+ OldContext = SetContext (StatusContext);
+ SetContextFont (TinyFont);
+
+ r.corner.x = 40;
+ r.corner.y = FREE_ORG_Y;
+
+ snprintf (buf, sizeof buf, "%u",
+ GetStorageBayCapacity () - GLOBAL_SIS (TotalElementMass));
+ t.baseline.x = ELEMENT_COL_2 + 1;
+ t.baseline.y = r.corner.y + TEXT_BASELINE;
+ t.align = ALIGN_RIGHT;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+
+ r.extent.width = t.baseline.x - r.corner.x + 1;
+ r.extent.height = ELEMENT_SPACING_Y - 2;
+
+ BatchGraphics ();
+ // erase previous free amount
+ SetContextForeGroundColor (CARGO_BACK_COLOR);
+ DrawFilledRectangle (&r);
+ // print the new free amount
+ SetContextForeGroundColor (CARGO_WORTH_COLOR);
+ font_DrawText (&t);
+ UnbatchGraphics ();
+
+ SetContext (OldContext);
+}
+
+static void
+DrawElementAmount (COUNT element, bool selected)
+{
+ RECT r;
+ TEXT t;
+ UNICODE buf[40];
+
+ r.corner.x = ELEMENT_SEL_ORG_X;
+ r.extent.width = ELEMENT_SEL_WIDTH;
+ r.extent.height = ELEMENT_SPACING_Y - 2;
+
+ if (element == NUM_ELEMENT_CATEGORIES)
+ r.corner.y = BIO_ORG_Y;
+ else
+ r.corner.y = ELEMENT_ORG_Y + (element * ELEMENT_SPACING_Y);
+
+ // draw line background
+ SetContextForeGroundColor (selected ?
+ CARGO_SELECTED_BACK_COLOR : CARGO_BACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ t.align = ALIGN_RIGHT;
+ t.pStr = buf;
+ t.baseline.y = r.corner.y + TEXT_BASELINE;
+
+ if (element == NUM_ELEMENT_CATEGORIES)
+ { // Bio
+ snprintf (buf, sizeof buf, "%u", GLOBAL_SIS (TotalBioMass));
+ }
+ else
+ { // Element
+ // print element's worth
+ SetContextForeGroundColor (selected ?
+ CARGO_SELECTED_WORTH_COLOR : CARGO_WORTH_COLOR);
+ t.baseline.x = ELEMENT_COL_1;
+ snprintf (buf, sizeof buf, "%u", GLOBAL (ElementWorth[element]));
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ snprintf (buf, sizeof buf, "%u", GLOBAL_SIS (ElementAmounts[element]));
+ }
+
+ // print the element/bio amount
+ SetContextForeGroundColor (selected ?
+ CARGO_SELECTED_AMOUNT_COLOR : CARGO_AMOUNT_COLOR);
+ t.baseline.x = ELEMENT_COL_2;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+}
+
+static void
+DrawCargoDisplay (void)
+{
+ STAMP s;
+ TEXT t;
+ RECT r;
+ COORD cy;
+ COUNT i;
+
+ r.corner.x = 2;
+ r.extent.width = FIELD_WIDTH + 1;
+ r.corner.y = 20;
+ // XXX: Shouldn't the height be 1 less? This draws the bottom border
+ // 1 pixel too low. Or if not, why do we need another box anyway?
+ r.extent.height = 129 - r.corner.y;
+ DrawStarConBox (&r, 1,
+ SHADOWBOX_MEDIUM_COLOR, SHADOWBOX_DARK_COLOR,
+ TRUE, CARGO_BACK_COLOR);
+
+ // draw the "CARGO" title
+ SetContextFont (StarConFont);
+ t.baseline.x = (STATUS_WIDTH >> 1) - 1;
+ t.baseline.y = 27;
+ t.align = ALIGN_CENTER;
+ t.pStr = GAME_STRING (CARGO_STRING_BASE);
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (CARGO_SELECTED_AMOUNT_COLOR);
+ font_DrawText (&t);
+
+ SetContextFont (TinyFont);
+
+ s.frame = SetAbsFrameIndex (MiscDataFrame,
+ (NUM_SCANDOT_TRANSITIONS * 2) + 3);
+ r.corner.x = ELEMENT_COL_0;
+ r.extent = GetFrameBounds (s.frame);
+ s.origin.x = r.corner.x + (r.extent.width >> 1);
+
+ cy = ELEMENT_ORG_Y;
+
+ // print element column headings
+ t.align = ALIGN_RIGHT;
+ t.baseline.y = cy - 1;
+ t.CharCount = (COUNT)~0;
+
+ SetContextForeGroundColor (CARGO_WORTH_COLOR);
+ t.baseline.x = ELEMENT_COL_1;
+ t.pStr = "$";
+ font_DrawText (&t);
+
+ t.baseline.x = ELEMENT_COL_2;
+ t.pStr = "#";
+ font_DrawText (&t);
+
+ // draw element icons and print amounts
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i, cy += ELEMENT_SPACING_Y)
+ {
+ // erase background under an element icon
+ SetContextForeGroundColor (BLACK_COLOR);
+ r.corner.y = cy;
+ DrawFilledRectangle (&r);
+
+ // draw an element icon
+ s.origin.y = r.corner.y + (r.extent.height >> 1);
+ DrawStamp (&s);
+ s.frame = SetRelFrameIndex (s.frame, 5);
+
+ DrawElementAmount (i, false);
+ }
+
+ // erase background under the Bio icon
+ SetContextForeGroundColor (BLACK_COLOR);
+ r.corner.y = BIO_ORG_Y;
+ DrawFilledRectangle (&r);
+
+ // draw the Bio icon
+ s.origin.y = r.corner.y + (r.extent.height >> 1);
+ s.frame = SetAbsFrameIndex (s.frame, 68);
+ DrawStamp (&s);
+
+ // print the Bio amount
+ DrawElementAmount (NUM_ELEMENT_CATEGORIES, false);
+
+ // draw the line over the Bio amount
+ r.corner.x = 4;
+ r.corner.y = BIO_ORG_Y - 2;
+ r.extent.width = FIELD_WIDTH - 3;
+ r.extent.height = 1;
+ SetContextForeGroundColor (CARGO_SELECTED_BACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ // print "Free"
+ t.baseline.x = 5;
+ t.baseline.y = FREE_ORG_Y + TEXT_BASELINE;
+ t.align = ALIGN_LEFT;
+ t.pStr = GAME_STRING (CARGO_STRING_BASE + 1);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ ShowRemainingCapacity ();
+}
+
+void
+DrawCargoStrings (BYTE OldElement, BYTE NewElement)
+{
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+ SetContextFont (TinyFont);
+
+ BatchGraphics ();
+
+ if (OldElement > NUM_ELEMENT_CATEGORIES)
+ { // Asked for the initial display
+ DrawCargoDisplay ();
+
+ // do not draw unselected again this time
+ OldElement = NewElement;
+ }
+
+ if (OldElement != NewElement)
+ { // unselect the previous element
+ DrawElementAmount (OldElement, false);
+ }
+
+ if (NewElement != (BYTE)~0)
+ { // select the new element
+ DrawElementAmount (NewElement, true);
+ }
+
+ UnbatchGraphics ();
+ SetContext (OldContext);
+}
+
+static void
+DrawElementDescription (COUNT element)
+{
+ DrawStatusMessage (GAME_STRING (element + (CARGO_STRING_BASE + 2)));
+}
+
+static BOOLEAN
+DoDiscardCargo (MENU_STATE *pMS)
+{
+ BYTE NewState;
+ BOOLEAN select, cancel, back, forward;
+
+ select = PulsedInputState.menu[KEY_MENU_SELECT];
+ cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
+ back = PulsedInputState.menu[KEY_MENU_UP] || PulsedInputState.menu[KEY_MENU_LEFT];
+ forward = PulsedInputState.menu[KEY_MENU_DOWN] || PulsedInputState.menu[KEY_MENU_RIGHT];
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (cancel)
+ {
+ return FALSE;
+ }
+ else if (select)
+ {
+ if (GLOBAL_SIS (ElementAmounts[pMS->CurState]))
+ {
+ --GLOBAL_SIS (ElementAmounts[pMS->CurState]);
+ DrawCargoStrings (pMS->CurState, pMS->CurState);
+
+ --GLOBAL_SIS (TotalElementMass);
+ ShowRemainingCapacity ();
+ }
+ else
+ { // no element left in cargo hold
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ }
+ else
+ {
+ NewState = pMS->CurState;
+ if (back)
+ {
+ if (NewState == 0)
+ NewState += NUM_ELEMENT_CATEGORIES;
+ --NewState;
+ }
+ else if (forward)
+ {
+ ++NewState;
+ if (NewState == NUM_ELEMENT_CATEGORIES)
+ NewState = 0;
+ }
+
+ if (NewState != pMS->CurState)
+ {
+ DrawCargoStrings (pMS->CurState, NewState);
+ DrawElementDescription (NewState);
+ pMS->CurState = NewState;
+ }
+ }
+
+ SleepThread (ONE_SECOND / 30);
+
+ return (TRUE);
+}
+
+void
+CargoMenu (void)
+{
+ MENU_STATE MenuState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+
+ // draw the initial cargo display
+ DrawCargoStrings ((BYTE)~0, MenuState.CurState);
+ DrawElementDescription (MenuState.CurState);
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ MenuState.InputFunc = DoDiscardCargo;
+ DoInput (&MenuState, TRUE);
+
+ // erase the cargo display
+ ClearSISRect (DRAW_SIS_DISPLAY);
+}
+
diff --git a/src/uqm/planets/devices.c b/src/uqm/planets/devices.c
new file mode 100644
index 0000000..e781d5b
--- /dev/null
+++ b/src/uqm/planets/devices.c
@@ -0,0 +1,690 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../build.h"
+#include "../colors.h"
+#include "../gendef.h"
+#include "../starmap.h"
+#include "../encount.h"
+#include "../gamestr.h"
+#include "../controls.h"
+#include "../save.h"
+#include "../settings.h"
+#include "../shipcont.h"
+#include "../setup.h"
+#include "../state.h"
+#include "../sis.h"
+ // for ClearSISRect()
+#include "../grpinfo.h"
+#include "../sounds.h"
+#include "../util.h"
+#include "../hyper.h"
+ // for SaveSisHyperState()
+#include "planets.h"
+ // for SaveSolarSysLocation() and tests
+#include "libs/strlib.h"
+
+
+// If DEBUG_DEVICES is defined, the device list shown in the game will
+// include the pictures of all devices defined, regardless of which
+// devices the player actually possesses.
+//#define DEBUG_DEVICES
+
+#define DEVICE_ICON_WIDTH 16
+#define DEVICE_ICON_HEIGHT 16
+
+#define DEVICE_ORG_Y 33
+#define DEVICE_SPACING_Y (DEVICE_ICON_HEIGHT + 2)
+
+#define DEVICE_COL_0 4
+#define DEVICE_COL_1 40
+
+#define DEVICE_SEL_ORG_X (DEVICE_COL_0 + DEVICE_ICON_WIDTH)
+#define DEVICE_SEL_WIDTH (FIELD_WIDTH + 1 - DEVICE_SEL_ORG_X + 1)
+
+#define ICON_OFS_Y 1
+#define NAME_OFS_Y 2
+#define TEXT_BASELINE 6
+#define TEXT_SPACING_Y 7
+
+#define MAX_VIS_DEVICES ((129 - DEVICE_ORG_Y) / DEVICE_SPACING_Y)
+
+
+typedef enum
+{
+ DEVICE_FAILURE = 0,
+ DEVICE_SUCCESS,
+ DEVICE_SUCCESS_NO_SOUND,
+} DeviceStatus;
+
+typedef struct
+{
+ BYTE list[NUM_DEVICES];
+ // List of all devices player has
+ COUNT count;
+ // Number of devices in the list
+ COUNT topIndex;
+ // Index of the top device displayed
+} DEVICES_STATE;
+
+
+#if 0
+static void
+EraseDevicesBackground (void)
+{
+ RECT r;
+
+ r.corner.x = 2 + 1;
+ r.extent.width = FIELD_WIDTH + 1 - 2;
+ r.corner.y = DEVICE_ORG_Y;
+ r.extent.height = MAX_VIS_DEVICES * DEVICE_SPACING_Y;
+ SetContextForeGroundColor (DEVICES_BACK_COLOR);
+ DrawFilledRectangle (&r);
+}
+#endif
+
+static void
+DrawDevice (COUNT device, COUNT pos, bool selected)
+{
+ RECT r;
+ TEXT t;
+
+ t.align = ALIGN_CENTER;
+ t.baseline.x = DEVICE_COL_1;
+
+ r.extent.width = DEVICE_SEL_WIDTH;
+ r.extent.height = TEXT_SPACING_Y * 2;
+ r.corner.x = DEVICE_SEL_ORG_X;
+
+ // draw line background
+ r.corner.y = DEVICE_ORG_Y + pos * DEVICE_SPACING_Y + NAME_OFS_Y;
+ SetContextForeGroundColor (selected ?
+ DEVICES_SELECTED_BACK_COLOR : DEVICES_BACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ SetContextFont (TinyFont);
+
+ // print device name
+ SetContextForeGroundColor (selected ?
+ DEVICES_SELECTED_NAME_COLOR : DEVICES_NAME_COLOR);
+ t.baseline.y = r.corner.y + TEXT_BASELINE;
+ t.pStr = GAME_STRING (device + DEVICE_STRING_BASE + 1);
+ t.CharCount = utf8StringPos (t.pStr, ' ');
+ font_DrawText (&t);
+ t.baseline.y += TEXT_SPACING_Y;
+ t.pStr = skipUTF8Chars (t.pStr, t.CharCount + 1);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+}
+
+static void
+DrawDevicesDisplay (DEVICES_STATE *devState)
+{
+ TEXT t;
+ RECT r;
+ STAMP s;
+ COORD cy;
+ COUNT i;
+
+ r.corner.x = 2;
+ r.corner.y = 20;
+ r.extent.width = FIELD_WIDTH + 1;
+ // XXX: Shouldn't the height be 1 less? This draws the bottom border
+ // 1 pixel too low. Or if not, why do we need another box anyway?
+ r.extent.height = 129 - r.corner.y;
+ DrawStarConBox (&r, 1,
+ SHADOWBOX_MEDIUM_COLOR, SHADOWBOX_DARK_COLOR,
+ TRUE, DEVICES_BACK_COLOR);
+
+ // print the "DEVICES" title
+ SetContextFont (StarConFont);
+ t.baseline.x = (STATUS_WIDTH >> 1) - 1;
+ t.baseline.y = r.corner.y + 7;
+ t.align = ALIGN_CENTER;
+ t.pStr = GAME_STRING (DEVICE_STRING_BASE);
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (DEVICES_SELECTED_NAME_COLOR);
+ font_DrawText (&t);
+
+ s.origin.x = DEVICE_COL_0;
+ cy = DEVICE_ORG_Y;
+
+ // draw device icons and print names
+ for (i = 0; i < MAX_VIS_DEVICES; ++i, cy += DEVICE_SPACING_Y)
+ {
+ COUNT devIndex = devState->topIndex + i;
+
+ if (devIndex >= devState->count)
+ break;
+
+ // draw device icon
+ s.origin.y = cy + ICON_OFS_Y;
+ s.frame = SetAbsFrameIndex (MiscDataFrame,
+ 77 + devState->list[devIndex]);
+ DrawStamp (&s);
+
+ DrawDevice (devState->list[devIndex], i, false);
+ }
+}
+
+static void
+DrawDevices (DEVICES_STATE *devState, COUNT OldDevice, COUNT NewDevice)
+{
+ BatchGraphics ();
+
+ SetContext (StatusContext);
+
+ if (OldDevice > NUM_DEVICES)
+ { // Asked for the initial display or refresh
+ DrawDevicesDisplay (devState);
+
+ // do not draw unselected again this time
+ OldDevice = NewDevice;
+ }
+
+ if (OldDevice != NewDevice)
+ { // unselect the previous element
+ DrawDevice (devState->list[OldDevice], OldDevice - devState->topIndex,
+ false);
+ }
+
+ if (NewDevice < NUM_DEVICES)
+ { // select the new element
+ DrawDevice (devState->list[NewDevice], NewDevice - devState->topIndex,
+ true);
+ }
+
+ UnbatchGraphics ();
+}
+
+// Returns TRUE if the broadcaster has been successfully activated,
+// and FALSE otherwise.
+static BOOLEAN
+UseCaster (void)
+{
+ if (inHQSpace ())
+ {
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ SET_GAME_STATE (USED_BROADCASTER, 1);
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) != IN_INTERPLANETARY
+ || !playerInSolarSystem ())
+ return FALSE;
+
+ if (playerInPlanetOrbit ()
+ && matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc,
+ 1, MATCH_PLANET)
+ && CurStarDescPtr->Index == CHMMR_DEFINED
+ && !GET_GAME_STATE (CHMMR_UNLEASHED))
+ {
+ // In orbit around the Chenjesu/Mmrnmhrm home planet.
+ NextActivity |= CHECK_LOAD; /* fake a load game */
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+
+ EncounterGroup = 0;
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ SaveSolarSysLocation ();
+ return TRUE;
+ }
+
+ {
+ BOOLEAN FoundIlwrath;
+ HIPGROUP hGroup;
+
+ FoundIlwrath = (CurStarDescPtr->Index == ILWRATH_DEFINED)
+ && StartSphereTracking (ILWRATH_SHIP);
+ // In the Ilwrath home system and they are alive?
+
+ if (!FoundIlwrath &&
+ (hGroup = GetHeadLink (&GLOBAL (ip_group_q))))
+ {
+ // Is an Ilwrath ship in the system?
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ FoundIlwrath = (GroupPtr->race_id == ILWRATH_SHIP);
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+
+ if (FoundIlwrath)
+ {
+ NextActivity |= CHECK_LOAD; /* fake a load game */
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+
+ EncounterGroup = 0;
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ if (CurStarDescPtr->Index == ILWRATH_DEFINED)
+ {
+ // Ilwrath home system.
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 4);
+ }
+ else
+ {
+ // Ilwrath ship.
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 5);
+ }
+
+ if (playerInPlanetOrbit ())
+ SaveSolarSysLocation ();
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static DeviceStatus
+InvokeDevice (BYTE which_device)
+{
+ BYTE val;
+
+ switch (which_device)
+ {
+ case ROSY_SPHERE_DEVICE:
+ val = GET_GAME_STATE (ULTRON_CONDITION);
+ if (val)
+ {
+ SET_GAME_STATE (ULTRON_CONDITION, val + 1);
+ SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 0);
+ SET_GAME_STATE (DISCUSSED_ULTRON, 0);
+ SET_GAME_STATE (SUPOX_ULTRON_HELP, 0);
+ return DEVICE_SUCCESS;
+ }
+ break;
+ case ARTIFACT_2_DEVICE:
+ break;
+ case ARTIFACT_3_DEVICE:
+ break;
+ case SUN_EFFICIENCY_DEVICE:
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && playerInPlanetOrbit ())
+ {
+ PlayMenuSound (MENU_SOUND_INVOKED);
+ SleepThreadUntil (FadeScreen (FadeAllToWhite, ONE_SECOND * 1)
+ + (ONE_SECOND * 2));
+ if (CurStarDescPtr->Index != CHMMR_DEFINED
+ || !matchWorld (pSolarSysState,
+ pSolarSysState->pOrbitalDesc,
+ 1, MATCH_PLANET))
+ {
+ FadeScreen (FadeAllToColor, ONE_SECOND * 2);
+ }
+ else
+ {
+ SET_GAME_STATE (CHMMR_EMERGING, 1);
+
+ EncounterGroup = 0;
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (CHMMR_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ }
+ return DEVICE_SUCCESS_NO_SOUND;
+ }
+ break;
+ case UTWIG_BOMB_DEVICE:
+ SET_GAME_STATE (UTWIG_BOMB, 0);
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ GLOBAL_SIS (CrewEnlisted) = (COUNT)~0;
+ return DEVICE_SUCCESS;
+ case ULTRON_0_DEVICE:
+ break;
+ case ULTRON_1_DEVICE:
+ break;
+ case ULTRON_2_DEVICE:
+ break;
+ case ULTRON_3_DEVICE:
+ break;
+ case MAIDENS_DEVICE:
+ break;
+ case TALKING_PET_DEVICE:
+ NextActivity |= CHECK_LOAD; /* fake a load game */
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 0);
+ if (inHQSpace ())
+ {
+ if (GetHeadEncounter ())
+ {
+ SET_GAME_STATE (SHIP_TO_COMPEL, 1);
+ }
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+
+ SaveSisHyperState ();
+ }
+ else
+ {
+ EncounterGroup = 0;
+ if (GetHeadLink (&GLOBAL (ip_group_q)))
+ {
+ SET_GAME_STATE (SHIP_TO_COMPEL, 1);
+
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+ }
+
+ if (CurStarDescPtr->Index == SAMATRA_DEFINED)
+ {
+ SET_GAME_STATE (READY_TO_CONFUSE_URQUAN, 1);
+ }
+ if (playerInPlanetOrbit ())
+ SaveSolarSysLocation ();
+ }
+ return DEVICE_SUCCESS;
+ case AQUA_HELIX_DEVICE:
+ val = GET_GAME_STATE (ULTRON_CONDITION);
+ if (val)
+ {
+ SET_GAME_STATE (ULTRON_CONDITION, val + 1);
+ SET_GAME_STATE (AQUA_HELIX_ON_SHIP, 0);
+ SET_GAME_STATE (DISCUSSED_ULTRON, 0);
+ SET_GAME_STATE (SUPOX_ULTRON_HELP, 0);
+ return DEVICE_SUCCESS;
+ }
+ break;
+ case CLEAR_SPINDLE_DEVICE:
+ val = GET_GAME_STATE (ULTRON_CONDITION);
+ if (val)
+ {
+ SET_GAME_STATE (ULTRON_CONDITION, val + 1);
+ SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 0);
+ SET_GAME_STATE (DISCUSSED_ULTRON, 0);
+ SET_GAME_STATE (SUPOX_ULTRON_HELP, 0);
+ return DEVICE_SUCCESS;
+ }
+ break;
+ case UMGAH_HYPERWAVE_DEVICE:
+ case BURVIX_HYPERWAVE_DEVICE:
+ if (UseCaster ())
+ return DEVICE_SUCCESS;
+ break;
+ case TAALO_PROTECTOR_DEVICE:
+ break;
+ case EGG_CASING0_DEVICE:
+ case EGG_CASING1_DEVICE:
+ case EGG_CASING2_DEVICE:
+ break;
+ case SYREEN_SHUTTLE_DEVICE:
+ break;
+ case VUX_BEAST_DEVICE:
+ break;
+ case DESTRUCT_CODE_DEVICE:
+ break;
+ case PORTAL_SPAWNER_DEVICE:
+#define PORTAL_FUEL_COST (10 * FUEL_TANK_SCALE)
+ if (inHyperSpace ()
+ && GLOBAL_SIS (FuelOnBoard) >= PORTAL_FUEL_COST)
+ {
+ /* No DeltaSISGauges because the flagship picture
+ * is currently obscured.
+ */
+ GLOBAL_SIS (FuelOnBoard) -= PORTAL_FUEL_COST;
+ SET_GAME_STATE (PORTAL_COUNTER, 1);
+ return DEVICE_SUCCESS;
+ }
+ break;
+ case URQUAN_WARP_DEVICE:
+ break;
+ case LUNAR_BASE_DEVICE:
+ break;
+ }
+
+ return DEVICE_FAILURE;
+}
+
+static BOOLEAN
+DoManipulateDevices (MENU_STATE *pMS)
+{
+ DEVICES_STATE *devState = pMS->privData;
+ BOOLEAN select, cancel, back, forward;
+ BOOLEAN pagefwd, pageback;
+
+ select = PulsedInputState.menu[KEY_MENU_SELECT];
+ cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
+ back = PulsedInputState.menu[KEY_MENU_UP] ||
+ PulsedInputState.menu[KEY_MENU_LEFT];
+ forward = PulsedInputState.menu[KEY_MENU_DOWN]
+ || PulsedInputState.menu[KEY_MENU_RIGHT];
+ pagefwd = PulsedInputState.menu[KEY_MENU_PAGE_DOWN];
+ pageback = PulsedInputState.menu[KEY_MENU_PAGE_UP];
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (cancel)
+ {
+ return FALSE;
+ }
+ else if (select)
+ {
+ DeviceStatus status;
+
+ status = InvokeDevice (devState->list[pMS->CurState]);
+ if (status == DEVICE_FAILURE)
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ else if (status == DEVICE_SUCCESS)
+ PlayMenuSound (MENU_SOUND_INVOKED);
+
+ return (status == DEVICE_FAILURE);
+ }
+ else
+ {
+ SIZE NewTop;
+ SIZE NewState;
+
+ NewTop = devState->topIndex;
+ NewState = pMS->CurState;
+
+ if (back)
+ --NewState;
+ else if (forward)
+ ++NewState;
+ else if (pagefwd)
+ NewState += MAX_VIS_DEVICES;
+ else if (pageback)
+ NewState -= MAX_VIS_DEVICES;
+
+ if (NewState < 0)
+ NewState = 0;
+ else if (NewState >= devState->count)
+ NewState = devState->count - 1;
+
+ if (NewState < NewTop || NewState >= NewTop + MAX_VIS_DEVICES)
+ NewTop = NewState - NewState % MAX_VIS_DEVICES;
+
+ if (NewState != pMS->CurState)
+ {
+ if (NewTop != devState->topIndex)
+ { // redraw the display
+ devState->topIndex = NewTop;
+ DrawDevices (devState, (COUNT)~0, NewState);
+ }
+ else
+ { // move selection to new device
+ DrawDevices (devState, pMS->CurState, NewState);
+ }
+ pMS->CurState = NewState;
+ }
+
+ SleepThread (ONE_SECOND / 30);
+ }
+
+ return TRUE;
+}
+
+SIZE
+InventoryDevices (BYTE *pDeviceMap, COUNT Size)
+{
+ BYTE i;
+ SIZE DevicesOnBoard;
+
+ DevicesOnBoard = 0;
+ for (i = 0; i < NUM_DEVICES && Size > 0; ++i)
+ {
+ BYTE DeviceState;
+
+ DeviceState = 0;
+ switch (i)
+ {
+ case ROSY_SPHERE_DEVICE:
+ DeviceState = GET_GAME_STATE (ROSY_SPHERE_ON_SHIP);
+ break;
+ case ARTIFACT_2_DEVICE:
+ DeviceState = GET_GAME_STATE (ARTIFACT_2_ON_SHIP);
+ break;
+ case ARTIFACT_3_DEVICE:
+ DeviceState = GET_GAME_STATE (ARTIFACT_3_ON_SHIP);
+ break;
+ case SUN_EFFICIENCY_DEVICE:
+ DeviceState = GET_GAME_STATE (SUN_DEVICE_ON_SHIP);
+ break;
+ case UTWIG_BOMB_DEVICE:
+ DeviceState = GET_GAME_STATE (UTWIG_BOMB_ON_SHIP);
+ break;
+ case ULTRON_0_DEVICE:
+ DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 1);
+ break;
+ case ULTRON_1_DEVICE:
+ DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 2);
+ break;
+ case ULTRON_2_DEVICE:
+ DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 3);
+ break;
+ case ULTRON_3_DEVICE:
+ DeviceState = (GET_GAME_STATE (ULTRON_CONDITION) == 4);
+ break;
+ case MAIDENS_DEVICE:
+ DeviceState = GET_GAME_STATE (MAIDENS_ON_SHIP);
+ break;
+ case TALKING_PET_DEVICE:
+ DeviceState = GET_GAME_STATE (TALKING_PET_ON_SHIP);
+ break;
+ case AQUA_HELIX_DEVICE:
+ DeviceState = GET_GAME_STATE (AQUA_HELIX_ON_SHIP);
+ break;
+ case CLEAR_SPINDLE_DEVICE:
+ DeviceState = GET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP);
+ break;
+ case UMGAH_HYPERWAVE_DEVICE:
+ DeviceState = GET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP);
+ break;
+ case TAALO_PROTECTOR_DEVICE:
+ DeviceState = GET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP);
+ break;
+ case EGG_CASING0_DEVICE:
+ DeviceState = GET_GAME_STATE (EGG_CASE0_ON_SHIP);
+ break;
+ case EGG_CASING1_DEVICE:
+ DeviceState = GET_GAME_STATE (EGG_CASE1_ON_SHIP);
+ break;
+ case EGG_CASING2_DEVICE:
+ DeviceState = GET_GAME_STATE (EGG_CASE2_ON_SHIP);
+ break;
+ case SYREEN_SHUTTLE_DEVICE:
+ DeviceState = GET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP);
+ break;
+ case VUX_BEAST_DEVICE:
+ DeviceState = GET_GAME_STATE (VUX_BEAST_ON_SHIP);
+ break;
+ case DESTRUCT_CODE_DEVICE:
+#ifdef NEVER
+ DeviceState = GET_GAME_STATE (DESTRUCT_CODE_ON_SHIP);
+#endif /* NEVER */
+ break;
+ case PORTAL_SPAWNER_DEVICE:
+ DeviceState = GET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP);
+ break;
+ case URQUAN_WARP_DEVICE:
+ DeviceState = GET_GAME_STATE (PORTAL_KEY_ON_SHIP);
+ break;
+ case BURVIX_HYPERWAVE_DEVICE:
+ DeviceState = GET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP);
+ break;
+ case LUNAR_BASE_DEVICE:
+ DeviceState = GET_GAME_STATE (MOONBASE_ON_SHIP);
+ break;
+ }
+
+#ifndef DEBUG_DEVICES
+ if (DeviceState)
+#endif /* DEBUG_DEVICES */
+ {
+ *pDeviceMap++ = i;
+ ++DevicesOnBoard;
+ --Size;
+ }
+ }
+
+ return DevicesOnBoard;
+}
+
+BOOLEAN
+DevicesMenu (void)
+{
+ MENU_STATE MenuState;
+ DEVICES_STATE DevicesState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+ MenuState.privData = &DevicesState;
+
+ memset (&DevicesState, 0, sizeof DevicesState);
+
+ DevicesState.count = InventoryDevices (DevicesState.list, NUM_DEVICES);
+ if (!DevicesState.count)
+ return FALSE;
+
+ DrawDevices (&DevicesState, (COUNT)~0, MenuState.CurState);
+
+ SetMenuSounds (MENU_SOUND_ARROWS | MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN,
+ MENU_SOUND_SELECT);
+
+ MenuState.InputFunc = DoManipulateDevices;
+ DoInput (&MenuState, TRUE);
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ if (GLOBAL_SIS (CrewEnlisted) != (COUNT)~0
+ && !(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ ClearSISRect (DRAW_SIS_DISPLAY);
+
+ if (!GET_GAME_STATE (PORTAL_COUNTER)
+ && !(GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
diff --git a/src/uqm/planets/elemdata.h b/src/uqm/planets/elemdata.h
new file mode 100644
index 0000000..53d228c
--- /dev/null
+++ b/src/uqm/planets/elemdata.h
@@ -0,0 +1,215 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_PLANETS_ELEMDATA_H_
+#define UQM_PLANETS_ELEMDATA_H_
+
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/*------------------------------ Type Defines ----------------------------- */
+enum
+{
+ COMMON = 0,
+ CORROSIVE,
+ BASE_METAL,
+ NOBLE,
+ RARE_EARTH,
+ PRECIOUS,
+ RADIOACTIVE,
+ EXOTIC,
+
+ NUM_ELEMENT_CATEGORIES
+};
+
+#define ElementCategory(et) (Elements[et] & 0x7)
+
+/*------------------------------ Global Data ------------------------------ */
+
+enum
+{
+ HYDROGEN,
+ HELIUM,
+ LITHIUM,
+ BERYLLIUM,
+ BORON,
+ CARBON,
+ NITROGEN,
+ OXYGEN,
+ FLUORINE,
+ NEON,
+ SODIUM,
+ MAGNESIUM,
+ ALUMINUM,
+ SILICON,
+ PHOSPHORUS,
+ SULFUR,
+ CHLORINE,
+ ARGON,
+ POTASSIUM,
+ CALCIUM,
+ SCANDIUM,
+ TITANIUM,
+ VANADIUM,
+ CHROMIUM,
+ MANGANESE,
+ IRON,
+ COBALT,
+ NICKEL,
+ COPPER,
+ ZINC,
+ GALLIUM,
+ GERMANIUM,
+ ARSENIC,
+ SELENIUM,
+ BROMINE,
+ KRYPTON,
+ RUBIDIUM,
+ STRONTIUM,
+ YTTRIUM,
+ ZIRCONIUM,
+ NIOBIUM,
+ MOLYBDENUM,
+ TECHNETIUM,
+ RUTHENIUM,
+ RHODIUM,
+ PALLADIUM,
+ SILVER,
+ CADMIUM,
+ INDIUM,
+ TIN,
+ ANTIMONY,
+ TELLURIUM,
+ IODINE,
+ XENON,
+ CESIUM,
+ BARIUM,
+ LANTHANUM,
+ CERIUM,
+ PRASEODYMIUM,
+ NEODYMIUM,
+ PROMETHIUM,
+ SAMARIUM,
+ EUROPIUM,
+ GADOLINIUM,
+ TERBIUM,
+ DYPROSIUM,
+ HOLMIUM,
+ ERBIUM,
+ THULIUM,
+ YTTERBIUM,
+ LUTETIUM,
+ HAFNIUM,
+ TANTALUM,
+ TUNGSTEN,
+ RHENIUM,
+ OSMIUM,
+ IRIDIUM,
+ PLATINUM,
+ GOLD,
+ MERCURY,
+ THALLIUM,
+ LEAD,
+ BISMUTH,
+ POLONIUM,
+ ASTATINE,
+ RADON,
+ FRANCIUM,
+ RADIUM,
+ ACTINIUM,
+ THORIUM,
+ PROTACTINIUM,
+ URANIUM,
+ NEPTUNIUM,
+ PLUTONIUM,
+ NUMBER_OF_NORMAL,
+
+ OZONE = NUMBER_OF_NORMAL,
+ FREE_RADICALS,
+ CARBON_DIOXIDE,
+ CARBON_MONOXIDE,
+ AMMONIA,
+ METHANE,
+ SULFURIC_ACID,
+ HYDROCHLORIC_ACID,
+ HYDROCYANIC_ACID,
+ FORMIC_ACID,
+ PHOSPHORIC_ACID,
+ FORMALDEHYDE,
+ CYANOACETYLENE,
+ METHANOL,
+ ETHANOL,
+ SILICON_MONOXIDE,
+ TITANIUM_OXIDE,
+ ZIRCONIUM_OXIDE,
+ WATER,
+ SILICON_COMPOUNDS,
+ METAL_OXIDES,
+ QUANTUM_BH,
+ NEUTRONIUM,
+ MAGNETIC_MONOPOLES,
+ DEGENERATE_MATTER,
+ RT_SUPER_FLUID,
+ AGUUTI_NODULES,
+ IRON_COMPOUNDS,
+ ALUMINUM_COMPOUNDS,
+ NITROUS_OXIDE,
+ RADIOACTIVE_COMPOUNDS,
+ HYDROCARBONS,
+ CARBON_COMPOUNDS,
+ ANTIMATTER,
+ CHARON_DUST,
+ REISBURG_HELICES,
+ TZO_CRYSTALS,
+ CALCIUM_COMPOUNDS,
+ NITRIC_ACID,
+
+ NUMBER_OF_ELEMENTS
+};
+#define NUMBER_OF_SPECIAL (NUMBER_OF_ELEMENTS - NUMBER_OF_NORMAL)
+
+#define CHANCE_MASK ((1 << 2) - 1)
+
+#define FEW 1
+#define MODERATE 4
+#define NUMEROUS 8
+
+enum
+{
+ LIGHT = 0,
+ MEDIUM,
+ HEAVY
+};
+#define NOTHING (BYTE)(~0)
+
+#define MINERAL_DEPOSIT(qn,ql) MAKE_BYTE (qn, ql)
+#define DEPOSIT_QUANTITY(md) LONIBBLE (md)
+#define DEPOSIT_QUALITY(md) HINIBBLE (md)
+
+#define MAX_ELEMENT_UNITS 0xF
+
+extern const BYTE *Elements;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PLANETS_ELEMDATA_H_ */
diff --git a/src/uqm/planets/generate.h b/src/uqm/planets/generate.h
new file mode 100644
index 0000000..9942e82
--- /dev/null
+++ b/src/uqm/planets/generate.h
@@ -0,0 +1,110 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GENERATE_H
+#define GENERATE_H
+
+typedef struct GenerateFunctions GenerateFunctions;
+
+#include "types.h"
+#include "planets.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/*
+ * To do (for further cleanups):
+ * - split off generateOrbital in a calculation and an activation
+ * (graphics and music) part.
+ * - make generateOrbital return a meaningful value, specifically, whether
+ * or not the player is going into orbit
+ * - for GenerateNameFunction, set the name in an argument, instead
+ * of in GLOBAL_SYS(PlanetName)
+ * - make generateName work for moons
+ * - add parameters to initNcs, reinitNpcs, and uninitNpcs, so that
+ * globals don't have to be used.
+ * - Add a reference from each world to the solar system, so that most
+ * of these functions can do with one less argument.
+ * - (maybe) don't directly call the generate functions via
+ * solarSys->genFuncs->..., but use a function for this, which first
+ * checks for solar system dependent handlers, and if this does not exist,
+ * or returns false, calls the default function.
+ */
+
+// Any of these functions returning true means that the action has been
+// handled, and that the default function should not be called.
+typedef bool (*InitNpcsFunction)(SOLARSYS_STATE *solarSys);
+typedef bool (*ReinitNpcsFunction)(SOLARSYS_STATE *solarSys);
+typedef bool (*UninitNpcsFunction)(SOLARSYS_STATE *solarSys);
+typedef bool (*GeneratePlanetsFunction)(SOLARSYS_STATE *solarSys);
+typedef bool (*GenerateMoonsFunction)(SOLARSYS_STATE *solarSys,
+ PLANET_DESC *planet);
+typedef bool (*GenerateOrbitalFunction)(SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+typedef bool (*GenerateNameFunction)(const SOLARSYS_STATE *,
+ const PLANET_DESC *world);
+// The following functions return the number of objects being generated
+// (or the index of the current object in some cases)
+typedef COUNT (*GenerateMineralsFunction)(const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+typedef COUNT (*GenerateEnergyFunction)(const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+typedef COUNT (*GenerateLifeFunction)(const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+// The following functions return true if the node should be removed
+// from the surface, i.e. picked up.
+typedef bool (*PickupMineralsFunction)(SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+typedef bool (*PickupEnergyFunction)(SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+typedef bool (*PickupLifeFunction)(SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+struct GenerateFunctions {
+ InitNpcsFunction initNpcs;
+ // Ships in the solar system, the first time it is accessed.
+ ReinitNpcsFunction reinitNpcs;
+ // Ships in the solar system, every next time it is accessed.
+ UninitNpcsFunction uninitNpcs;
+ // When leaving the solar system.
+ GeneratePlanetsFunction generatePlanets;
+ // Layout of planets within a solar system.
+ GenerateMoonsFunction generateMoons;
+ // Layout of moons around a planet.
+ GenerateNameFunction generateName;
+ // Name of a planet.
+ GenerateOrbitalFunction generateOrbital;
+ // Characteristics of words (planets and moons).
+ GenerateMineralsFunction generateMinerals;
+ // Minerals on the planet surface.
+ GenerateEnergyFunction generateEnergy;
+ // Energy sources on the planet surface.
+ GenerateLifeFunction generateLife;
+ // Bio on the planet surface.
+ PickupMineralsFunction pickupMinerals;
+ PickupEnergyFunction pickupEnergy;
+ PickupLifeFunction pickupLife;
+};
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* GENERATE_H */
+
diff --git a/src/uqm/planets/generate/Makeinfo b/src/uqm/planets/generate/Makeinfo
new file mode 100644
index 0000000..520af9d
--- /dev/null
+++ b/src/uqm/planets/generate/Makeinfo
@@ -0,0 +1,6 @@
+uqm_CFILES="gendefault.c genand.c genburv.c genchmmr.c gencol.c gendru.c
+ genilw.c genmel.c genmyc.c genorz.c genpet.c genpku.c genrain.c
+ gensam.c genshof.c gensly.c gensol.c genspa.c gensup.c gensyr.c
+ genthrad.c gentrap.c genutw.c genvault.c genvux.c genwreck.c
+ genyeh.c genzfpscout.c genzoq.c"
+uqm_HFILES="genall.h gendefault.h"
diff --git a/src/uqm/planets/generate/genall.h b/src/uqm/planets/generate/genall.h
new file mode 100644
index 0000000..3776cff
--- /dev/null
+++ b/src/uqm/planets/generate/genall.h
@@ -0,0 +1,27 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_PLANETS_GENERATE_GENALL_H_
+#define UQM_PLANETS_GENERATE_GENALL_H_
+
+#include "gendefault.h"
+#include "types.h"
+#include "../generate.h"
+#include "libs/compiler.h"
+
+
+#endif /* UQM_PLANETS_GENERATE_GENALL_H_ */
+
diff --git a/src/uqm/planets/generate/genand.c b/src/uqm/planets/generate/genand.c
new file mode 100644
index 0000000..457b5ff
--- /dev/null
+++ b/src/uqm/planets/generate/genand.c
@@ -0,0 +1,164 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../scan.h"
+#include "../../globdata.h"
+#include "../../nameref.h"
+#include "../../resinst.h"
+#include "../../sounds.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateAndrosynth_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateAndrosynth_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateAndrosynth_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateAndrosynth_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateAndrosynthFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateAndrosynth_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateAndrosynth_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateAndrosynth_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateAndrosynth_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateAndrosynth_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[1].data_index = TELLURIC_WORLD;
+ solarSys->PlanetDesc[1].radius = EARTH_RADIUS * 204L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[1].location.x,
+ solarSys->PlanetDesc[1].location.y);
+ solarSys->PlanetDesc[1].location.x =
+ COSINE (angle, solarSys->PlanetDesc[1].radius);
+ solarSys->PlanetDesc[1].location.y =
+ SINE (angle, solarSys->PlanetDesc[1].radius);
+
+ return true;
+}
+
+static bool
+GenerateAndrosynth_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 1, MATCH_PLANET))
+ {
+ COUNT i;
+ COUNT visits = 0;
+
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (ANDROSYNTH_RUINS_STRTAB));
+ // Androsynth ruins are a special case. The DiscoveryString contains
+ // several lander reports which form a story. Each report is given
+ // when the player collides with a new city ruin. Ruins previously
+ // visited are marked in the upper 16 bits of ScanRetrieveMask, and
+ // the lower bits are cleared to keep the ruin nodes on the map.
+ for (i = 16; i < 32; ++i)
+ {
+ if (isNodeRetrieved (&solarSys->SysInfo.PlanetInfo, ENERGY_SCAN, i))
+ ++visits;
+ }
+ if (visits >= GetStringTableCount (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString))
+ { // All the reports were already given
+ DestroyStringTable (ReleaseStringTable (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString = 0;
+ }
+ else
+ { // Advance the report sequence to the first unread
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ SetRelStringTableIndex (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString, visits);
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ if (matchWorld (solarSys, world, 1, MATCH_PLANET))
+ {
+ solarSys->SysInfo.PlanetInfo.AtmoDensity =
+ EARTH_ATMOSPHERE * 144 / 100;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 28;
+ solarSys->SysInfo.PlanetInfo.Weather = 1;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ }
+
+ return true;
+}
+
+static bool
+GenerateAndrosynth_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 1, MATCH_PLANET))
+ {
+ PLANET_INFO *planetInfo = &solarSys->SysInfo.PlanetInfo;
+
+ // Ruins previously visited are marked in the upper 16 bits
+ if (isNodeRetrieved (planetInfo, ENERGY_SCAN, whichNode + 16))
+ return false; // already visited this ruin, do not remove
+
+ setNodeRetrieved (planetInfo, ENERGY_SCAN, whichNode + 16);
+ // We set the retrieved bit manually here and need to indicate
+ // the change to the solar system state functions
+ SET_GAME_STATE (PLANETARY_CHANGE, 1);
+
+ // Androsynth ruins have several lander reports which form a story
+ GenerateDefault_landerReportCycle (solarSys);
+
+ return false; // do not remove the node from the surface
+ }
+
+ return false;
+}
+
+static COUNT
+GenerateAndrosynth_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 1, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
diff --git a/src/uqm/planets/generate/genburv.c b/src/uqm/planets/generate/genburv.c
new file mode 100644
index 0000000..aa5b6bd
--- /dev/null
+++ b/src/uqm/planets/generate/genburv.c
@@ -0,0 +1,192 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../globdata.h"
+#include "../../nameref.h"
+#include "../../resinst.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateBurvixese_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateBurvixese_generateMoons (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *planet);
+static bool GenerateBurvixese_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateBurvixese_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateBurvixese_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateBurvixeseFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateBurvixese_generatePlanets,
+ /* .generateMoons = */ GenerateBurvixese_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateBurvixese_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateBurvixese_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateBurvixese_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateBurvixese_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = REDUX_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 1;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 39L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+ return true;
+}
+
+static bool
+GenerateBurvixese_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet)
+{
+ GenerateDefault_generateMoons (solarSys, planet);
+
+ if (matchWorld (solarSys, planet, 0, MATCH_PLANET))
+ {
+ COUNT angle;
+ DWORD rand_val;
+
+ solarSys->MoonDesc[0].data_index = SELENIC_WORLD;
+ solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS
+ + (MAX_MOONS - 1) * MOON_DELTA;
+ rand_val = RandomContext_Random (SysGenRNG);
+ angle = NORMALIZE_ANGLE (LOWORD (rand_val));
+ solarSys->MoonDesc[0].location.x =
+ COSINE (angle, solarSys->MoonDesc[0].radius);
+ solarSys->MoonDesc[0].location.y =
+ SINE (angle, solarSys->MoonDesc[0].radius);
+ }
+ return true;
+}
+
+static bool
+GenerateBurvixese_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ DWORD rand_val;
+
+ DoPlanetaryAnalysis (&solarSys->SysInfo, world);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = rand_val;
+ GenerateLifeForms (&solarSys->SysInfo, GENERATE_ALL, NULL);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val;
+ GenerateMineralDeposits (&solarSys->SysInfo, GENERATE_ALL, NULL);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[ENERGY_SCAN] = rand_val;
+
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (
+ LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (BURV_RUINS_STRTAB));
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ }
+ else if (matchWorld (solarSys, world, 0, 0)
+ && !GET_GAME_STATE (BURVIXESE_BROADCASTERS))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] = CaptureDrawable (
+ LoadGraphic (BURV_BCS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (BURV_BCS_STRTAB));
+ }
+
+ LoadPlanet (NULL);
+
+ return true;
+}
+
+static COUNT
+GenerateBurvixese_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ if (GET_GAME_STATE (BURVIXESE_BROADCASTERS))
+ { // already picked up
+ return 0;
+ }
+
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateBurvixese_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ assert (!GET_GAME_STATE (BURVIXESE_BROADCASTERS) && whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (BURVIXESE_BROADCASTERS, 1);
+ SET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP, 1);
+
+ return true; // picked up
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/generate/genchmmr.c b/src/uqm/planets/generate/genchmmr.c
new file mode 100644
index 0000000..672d977
--- /dev/null
+++ b/src/uqm/planets/generate/genchmmr.c
@@ -0,0 +1,154 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../globdata.h"
+#include "../../nameref.h"
+#include "../../setup.h"
+#include "../../sounds.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+static bool GenerateChmmr_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateChmmr_generateMoons (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *planet);
+static bool GenerateChmmr_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+
+
+const GenerateFunctions generateChmmrFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateChmmr_generatePlanets,
+ /* .generateMoons = */ GenerateChmmr_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateChmmr_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateChmmr_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[1].data_index = SAPPHIRE_WORLD;
+ if (!GET_GAME_STATE (CHMMR_UNLEASHED))
+ solarSys->PlanetDesc[1].data_index |= PLANET_SHIELDED;
+ solarSys->PlanetDesc[1].NumPlanets = 1;
+
+ return true;
+}
+
+static bool
+GenerateChmmr_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet)
+{
+ GenerateDefault_generateMoons (solarSys, planet);
+
+ if (matchWorld (solarSys, planet, 1, MATCH_PLANET))
+ {
+ COUNT angle;
+ DWORD rand_val;
+
+ solarSys->MoonDesc[0].data_index = HIERARCHY_STARBASE;
+ solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS;
+ rand_val = RandomContext_Random (SysGenRNG);
+ angle = NORMALIZE_ANGLE (LOWORD (rand_val));
+ solarSys->MoonDesc[0].location.x =
+ COSINE (angle, solarSys->MoonDesc[0].radius);
+ solarSys->MoonDesc[0].location.y =
+ SINE (angle, solarSys->MoonDesc[0].radius);
+ }
+
+ return true;
+}
+
+static bool
+GenerateChmmr_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 1, MATCH_PLANET))
+ {
+ if (GET_GAME_STATE (CHMMR_UNLEASHED))
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ InitCommunication (CHMMR_CONVERSATION);
+
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
+ {
+ GLOBAL (CurrentActivity) |= END_INTERPLANETARY;
+ }
+
+ return true;
+ }
+ else if (GET_GAME_STATE (SUN_DEVICE_ON_SHIP)
+ && !GET_GAME_STATE (ILWRATH_DECEIVED)
+ && StartSphereTracking (ILWRATH_SHIP))
+ {
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (ILWRATH_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6);
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ InitCommunication (ILWRATH_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+
+ return true;
+ }
+ }
+ else if (matchWorld (solarSys, world, 1, 0))
+ {
+ /* Starbase */
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (CHMMR_BASE_STRTAB));
+
+ DoDiscoveryReport (MenuSounds);
+
+ DestroyStringTable (ReleaseStringTable (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString = 0;
+ FreeLanderFont (&solarSys->SysInfo.PlanetInfo);
+
+ return true;
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ return true;
+}
+
diff --git a/src/uqm/planets/generate/gencol.c b/src/uqm/planets/generate/gencol.c
new file mode 100644
index 0000000..27e4d5b
--- /dev/null
+++ b/src/uqm/planets/generate/gencol.c
@@ -0,0 +1,126 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../globdata.h"
+#include "../../grpinfo.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateColony_initNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateColony_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateColony_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+
+
+const GenerateFunctions generateColonyFunctions = {
+ /* .initNpcs = */ GenerateColony_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateColony_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateColony_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateColony_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ HIPGROUP hGroup;
+
+ GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (COLONY_GRPOFFS0);
+ if (GLOBAL (BattleGroupRef) == 0)
+ {
+ CloneShipFragment (URQUAN_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1);
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ SET_GAME_STATE_32 (COLONY_GRPOFFS0, GLOBAL (BattleGroupRef));
+ }
+
+ GenerateDefault_initNpcs (solarSys);
+
+ if (GLOBAL (BattleGroupRef)
+ && (hGroup = GetHeadLink (&GLOBAL (ip_group_q))))
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ GroupPtr->task = IN_ORBIT;
+ GroupPtr->sys_loc = 0 + 1; /* orbitting colony */
+ GroupPtr->dest_loc = 0 + 1; /* orbitting colony */
+ GroupPtr->loc.x = 0;
+ GroupPtr->loc.y = 0;
+ GroupPtr->group_counter = 0;
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+
+ return true;
+}
+
+static bool
+GenerateColony_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+ PLANET_DESC *pMinPlanet;
+
+ pMinPlanet = &solarSys->PlanetDesc[0];
+ FillOrbits (solarSys, (BYTE)~0, pMinPlanet, FALSE);
+
+ pMinPlanet->radius = EARTH_RADIUS * 115L / 100;
+ angle = ARCTAN (pMinPlanet->location.x, pMinPlanet->location.y);
+ pMinPlanet->location.x = COSINE (angle, pMinPlanet->radius);
+ pMinPlanet->location.y = SINE (angle, pMinPlanet->radius);
+ pMinPlanet->data_index = WATER_WORLD | PLANET_SHIELDED;
+
+ return true;
+}
+
+static bool
+GenerateColony_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ DoPlanetaryAnalysis (&solarSys->SysInfo, world);
+
+ solarSys->SysInfo.PlanetInfo.AtmoDensity =
+ EARTH_ATMOSPHERE * 98 / 100;
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 28;
+
+ LoadPlanet (NULL);
+
+ return true;
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ return true;
+}
+
diff --git a/src/uqm/planets/generate/gendefault.c b/src/uqm/planets/generate/gendefault.c
new file mode 100644
index 0000000..a88b89c
--- /dev/null
+++ b/src/uqm/planets/generate/gendefault.c
@@ -0,0 +1,373 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../lander.h"
+#include "../../encount.h"
+#include "../../gamestr.h"
+#include "../../globdata.h"
+#include "../../grpinfo.h"
+#include "../../races.h"
+#include "../../state.h"
+#include "../../sounds.h"
+#include "libs/mathlib.h"
+
+
+static void GeneratePlanets (SOLARSYS_STATE *system);
+static void check_yehat_rebellion (void);
+
+
+const GenerateFunctions generateDefaultFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateDefault_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateDefault_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+bool
+GenerateDefault_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ if (!GetGroupInfo (GLOBAL (BattleGroupRef), GROUP_INIT_IP))
+ {
+ GLOBAL (BattleGroupRef) = 0;
+ BuildGroups ();
+ }
+
+ (void) solarSys;
+ return true;
+}
+
+bool
+GenerateDefault_reinitNpcs (SOLARSYS_STATE *solarSys)
+{
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ // This is not a great place to do the Yehat rebellion check, but
+ // since you can start the rebellion in any star system (not just
+ // the Homeworld), I could not find a better place for it.
+ // At least it is better than where it was originally.
+ check_yehat_rebellion ();
+
+ (void) solarSys;
+ return true;
+}
+
+bool
+GenerateDefault_uninitNpcs (SOLARSYS_STATE *solarSys)
+{
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ ReinitQueue (&GLOBAL (ip_group_q));
+
+ (void) solarSys;
+ return true;
+}
+
+bool
+GenerateDefault_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ FillOrbits (solarSys, (BYTE)~0, solarSys->PlanetDesc, FALSE);
+ GeneratePlanets (solarSys);
+ return true;
+}
+
+bool
+GenerateDefault_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet)
+{
+ FillOrbits (solarSys, planet->NumPlanets, solarSys->MoonDesc, FALSE);
+ return true;
+}
+
+bool
+GenerateDefault_generateName (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world)
+{
+ COUNT i = planetIndex (solarSys, world);
+ utf8StringCopy (GLOBAL_SIS (PlanetName), sizeof (GLOBAL_SIS (PlanetName)),
+ GAME_STRING (PLANET_NUMBER_BASE + (9 + 7) + i));
+ SET_GAME_STATE (BATTLE_PLANET, world->data_index);
+
+ return true;
+}
+
+bool
+GenerateDefault_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ DWORD rand_val;
+ SYSTEM_INFO *sysInfo;
+
+#ifdef DEBUG_SOLARSYS
+ if (worldIsPlanet (solarSys, world))
+ {
+ log_add (log_Debug, "Planet index = %d",
+ planetIndex (solarSys, world));
+ }
+ else
+ {
+ log_add (log_Debug, "Planet index = %d, Moon index = %d",
+ planetIndex (solarSys, world),
+ moonIndex (solarSys, world));
+ }
+#endif /* DEBUG_SOLARSYS */
+
+ sysInfo = &solarSys->SysInfo;
+
+ DoPlanetaryAnalysis (sysInfo, world);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ sysInfo->PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = rand_val;
+ GenerateLifeForms (sysInfo, GENERATE_ALL, NULL);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ sysInfo->PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val;
+ GenerateMineralDeposits (sysInfo, GENERATE_ALL, NULL);
+
+ sysInfo->PlanetInfo.ScanSeed[ENERGY_SCAN] = rand_val;
+ LoadPlanet (NULL);
+
+ return true;
+}
+
+COUNT
+GenerateDefault_generateMinerals (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ return GenerateMineralDeposits (&solarSys->SysInfo, whichNode, info);
+ (void) world;
+}
+
+bool
+GenerateDefault_pickupMinerals (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ // Minerals do not need any extra handling as of now
+ (void) solarSys;
+ (void) world;
+ (void) whichNode;
+ return true;
+}
+
+COUNT
+GenerateDefault_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ (void) whichNode;
+ (void) solarSys;
+ (void) world;
+ (void) info;
+ return 0;
+}
+
+bool
+GenerateDefault_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ // This should never be called since every energy node needs
+ // special handling and the function should be overridden
+ assert (false);
+ (void) solarSys;
+ (void) world;
+ (void) whichNode;
+ return false;
+}
+
+COUNT
+GenerateDefault_generateLife (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ return GenerateLifeForms (&solarSys->SysInfo, whichNode, info);
+ (void) world;
+}
+
+bool
+GenerateDefault_pickupLife (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ // Bio does not need any extra handling as of now
+ (void) solarSys;
+ (void) world;
+ (void) whichNode;
+ return true;
+}
+
+COUNT
+GenerateDefault_generateArtifact (const SOLARSYS_STATE *solarSys,
+ COUNT whichNode, NODE_INFO *info)
+{
+ // Generate an energy node at a random location
+ return GenerateRandomNodes (&solarSys->SysInfo, ENERGY_SCAN, 1, 0,
+ whichNode, info);
+}
+
+COUNT
+GenerateDefault_generateRuins (const SOLARSYS_STATE *solarSys,
+ COUNT whichNode, NODE_INFO *info)
+{
+ // Generate a standard spread of city ruins of a destroyed civilization
+ return GenerateRandomNodes (&solarSys->SysInfo, ENERGY_SCAN, NUM_RACE_RUINS,
+ 0, whichNode, info);
+}
+
+static inline void
+runLanderReport (void)
+{
+ UnbatchGraphics ();
+ DoDiscoveryReport (MenuSounds);
+ BatchGraphics ();
+}
+
+bool
+GenerateDefault_landerReport (SOLARSYS_STATE *solarSys)
+{
+ PLANET_INFO *planetInfo = &solarSys->SysInfo.PlanetInfo;
+
+ if (!planetInfo->DiscoveryString)
+ return false;
+
+ runLanderReport ();
+
+ // XXX: A non-cycling report is given only once and has to be deleted
+ // in some circumstances (like the Syreen Vault). It does not
+ // hurt to simply delete it in all cases. Nothing should rely on
+ // the presence of DiscoveryString, but the Syreen Vault and the
+ // Mycon Egg Cases rely on its absence.
+ DestroyStringTable (ReleaseStringTable (planetInfo->DiscoveryString));
+ planetInfo->DiscoveryString = 0;
+
+ return true;
+}
+
+bool
+GenerateDefault_landerReportCycle (SOLARSYS_STATE *solarSys)
+{
+ PLANET_INFO *planetInfo = &solarSys->SysInfo.PlanetInfo;
+
+ if (!planetInfo->DiscoveryString)
+ return false;
+
+ runLanderReport ();
+ // Advance to the next report
+ planetInfo->DiscoveryString = SetRelStringTableIndex (
+ planetInfo->DiscoveryString, 1);
+
+ // If our discovery strings have cycled, we're done
+ if (GetStringTableIndex (planetInfo->DiscoveryString) == 0)
+ {
+ DestroyStringTable (ReleaseStringTable (planetInfo->DiscoveryString));
+ planetInfo->DiscoveryString = 0;
+ }
+
+ return true;
+}
+
+// NB. This function modifies the RNG state.
+static void
+GeneratePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT i;
+ PLANET_DESC *planet;
+
+ for (i = solarSys->SunDesc[0].NumPlanets,
+ planet = &solarSys->PlanetDesc[0]; i; --i, ++planet)
+ {
+ DWORD rand_val;
+ BYTE byte_val;
+ BYTE num_moons;
+ BYTE type;
+
+ rand_val = RandomContext_Random (SysGenRNG);
+ byte_val = LOBYTE (rand_val);
+
+ num_moons = 0;
+ type = PlanData[planet->data_index & ~PLANET_SHIELDED].Type;
+ switch (PLANSIZE (type))
+ {
+ case LARGE_ROCKY_WORLD:
+ if (byte_val < 0x00FF * 25 / 100)
+ {
+ if (byte_val < 0x00FF * 5 / 100)
+ ++num_moons;
+ ++num_moons;
+ }
+ break;
+ case GAS_GIANT:
+ if (byte_val < 0x00FF * 90 / 100)
+ {
+ if (byte_val < 0x00FF * 75 / 100)
+ {
+ if (byte_val < 0x00FF * 50 / 100)
+ {
+ if (byte_val < 0x00FF * 25 / 100)
+ ++num_moons;
+ ++num_moons;
+ }
+ ++num_moons;
+ }
+ ++num_moons;
+ }
+ break;
+ }
+ planet->NumPlanets = num_moons;
+ }
+}
+
+static void
+check_yehat_rebellion (void)
+{
+ HIPGROUP hGroup, hNextGroup;
+
+ // XXX: Is there a better way to do this? I could not find one.
+ // When you talk to a Yehat ship (YEHAT_SHIP) and start the rebellion,
+ // there is no battle following the comm. There is *never* a battle in
+ // an encounter with Rebels, but the group race_id (YEHAT_REBEL_SHIP)
+ // is different from Royalists (YEHAT_SHIP). There is *always* a battle
+ // in an encounter with Royalists.
+ // TRANSLATION: "If the civil war has not started yet, or the player
+ // battled a ship -- bail."
+ if (!GET_GAME_STATE (YEHAT_CIVIL_WAR) || EncounterRace >= 0)
+ return; // not this time
+
+ // Send Yehat groups to flee the system, but only if the player
+ // has actually talked to a ship.
+ for (hGroup = GetHeadLink (&GLOBAL (ip_group_q)); hGroup;
+ hGroup = hNextGroup)
+ {
+ IP_GROUP *GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+ // IGNORE_FLAGSHIP was set in ipdisp.c:ip_group_collision()
+ // during a collision with the flagship.
+ if (GroupPtr->race_id == YEHAT_SHIP
+ && (GroupPtr->task & IGNORE_FLAGSHIP))
+ {
+ GroupPtr->task &= REFORM_GROUP;
+ GroupPtr->task |= FLEE | IGNORE_FLAGSHIP;
+ GroupPtr->dest_loc = 0;
+ }
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+}
+
+
diff --git a/src/uqm/planets/generate/gendefault.h b/src/uqm/planets/generate/gendefault.h
new file mode 100644
index 0000000..a6d0e71
--- /dev/null
+++ b/src/uqm/planets/generate/gendefault.h
@@ -0,0 +1,66 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GENDEFAULT_H
+#define GENDEFAULT_H
+
+#include "types.h"
+#include "../planets.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+bool GenerateDefault_initNpcs (SOLARSYS_STATE *solarSys);
+bool GenerateDefault_reinitNpcs (SOLARSYS_STATE *solarSys);
+bool GenerateDefault_uninitNpcs (SOLARSYS_STATE *solarSys);
+bool GenerateDefault_generatePlanets (SOLARSYS_STATE *solarSys);
+bool GenerateDefault_generateMoons (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *planet);
+bool GenerateDefault_generateName (const SOLARSYS_STATE *,
+ const PLANET_DESC *world);
+bool GenerateDefault_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+COUNT GenerateDefault_generateMinerals (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+COUNT GenerateDefault_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+COUNT GenerateDefault_generateLife (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+bool GenerateDefault_pickupMinerals (SOLARSYS_STATE *, PLANET_DESC *world,
+ COUNT whichNode);
+bool GenerateDefault_pickupEnergy (SOLARSYS_STATE *, PLANET_DESC *world,
+ COUNT whichNode);
+bool GenerateDefault_pickupLife (SOLARSYS_STATE *, PLANET_DESC *world,
+ COUNT whichNode);
+
+COUNT GenerateDefault_generateArtifact (const SOLARSYS_STATE *,
+ COUNT whichNode, NODE_INFO *info);
+COUNT GenerateDefault_generateRuins (const SOLARSYS_STATE *,
+ COUNT whichNode, NODE_INFO *info);
+bool GenerateDefault_landerReport (SOLARSYS_STATE *);
+bool GenerateDefault_landerReportCycle (SOLARSYS_STATE *);
+
+
+extern const GenerateFunctions generateDefaultFunctions;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* GENDEFAULT_H */
+
diff --git a/src/uqm/planets/generate/gendru.c b/src/uqm/planets/generate/gendru.c
new file mode 100644
index 0000000..7202010
--- /dev/null
+++ b/src/uqm/planets/generate/gendru.c
@@ -0,0 +1,169 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+#include <string.h>
+
+
+static bool GenerateDruuge_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateDruuge_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateDruuge_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateDruuge_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateDruugeFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateDruuge_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateDruuge_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDruuge_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDruuge_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateDruuge_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ memmove (&solarSys->PlanetDesc[1], &solarSys->PlanetDesc[0],
+ sizeof (solarSys->PlanetDesc[0])
+ * solarSys->SunDesc[0].NumPlanets);
+ ++solarSys->SunDesc[0].NumPlanets;
+
+ solarSys->PlanetDesc[0].data_index = DUST_WORLD;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 50L / 100;
+ solarSys->PlanetDesc[0].NumPlanets = 0;
+ angle = HALF_CIRCLE - OCTANT;
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].rand_seed = MAKE_DWORD (
+ solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+
+ return true;
+}
+
+static bool
+GenerateDruuge_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (StartSphereTracking (DRUUGE_SHIP))
+ {
+ NotifyOthers (DRUUGE_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (DRUUGE_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ InitCommunication (DRUUGE_CONVERSATION);
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+ return true;
+ }
+ else
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (DRUUGE_RUINS_STRTAB));
+ if (GET_GAME_STATE (ROSY_SPHERE))
+ { // Already picked up Rosy Sphere, skip the report
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ SetAbsStringTableIndex (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString, 1);
+ }
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ return true;
+}
+
+static bool
+GenerateDruuge_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ GenerateDefault_landerReportCycle (solarSys);
+
+ // The artifact can be picked up from any ruin
+ if (!GET_GAME_STATE (ROSY_SPHERE))
+ { // Just picked up the Rosy Sphere from a ruin
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (ROSY_SPHERE, 1);
+ SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 1);
+ }
+
+ return false; // do not remove the node
+ }
+
+ (void) whichNode;
+ return false;
+}
+
+static COUNT
+GenerateDruuge_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
diff --git a/src/uqm/planets/generate/genilw.c b/src/uqm/planets/generate/genilw.c
new file mode 100644
index 0000000..31a8fc4
--- /dev/null
+++ b/src/uqm/planets/generate/genilw.c
@@ -0,0 +1,150 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateIlwrath_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateIlwrath_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateIlwrath_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateIlwrath_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateIlwrathFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateIlwrath_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateIlwrath_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateIlwrath_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateIlwrath_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateIlwrath_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = PRIMORDIAL_WORLD;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 204L / 100;
+ angle = ARCTAN (
+ solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GenerateIlwrath_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (StartSphereTracking (ILWRATH_SHIP))
+ {
+ NotifyOthers (ILWRATH_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (ILWRATH_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ InitCommunication (ILWRATH_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+ return true;
+ }
+ else
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (
+ LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (RUINS_STRTAB));
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ solarSys->SysInfo.PlanetInfo.Weather = 2;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 3;
+ }
+
+ return true;
+}
+
+static COUNT
+GenerateIlwrath_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateIlwrath_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/generate/genmel.c b/src/uqm/planets/generate/genmel.c
new file mode 100644
index 0000000..27f31b6
--- /dev/null
+++ b/src/uqm/planets/generate/genmel.c
@@ -0,0 +1,114 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../../build.h"
+#include "../../gendef.h"
+#include "../../starmap.h"
+#include "../../globdata.h"
+#include "../../state.h"
+#include "libs/log.h"
+
+
+static bool GenerateMelnorme_initNpcs (SOLARSYS_STATE *solarSys);
+
+static int SelectMelnormeRefVar (void);
+static DWORD GetMelnormeRef (void);
+static void SetMelnormeRef (DWORD Ref);
+
+
+const GenerateFunctions generateMelnormeFunctions = {
+ /* .initNpcs = */ GenerateMelnorme_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateDefault_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateDefault_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateMelnorme_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ GLOBAL (BattleGroupRef) = GetMelnormeRef ();
+ if (GLOBAL (BattleGroupRef) == 0)
+ {
+ CloneShipFragment (MELNORME_SHIP, &GLOBAL (npc_built_ship_q), 0);
+ GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1);
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ SetMelnormeRef (GLOBAL (BattleGroupRef));
+ }
+
+ GenerateDefault_initNpcs (solarSys);
+
+ return true;
+}
+
+
+static int
+SelectMelnormeRefVar (void)
+{
+ switch (CurStarDescPtr->Index)
+ {
+ case MELNORME0_DEFINED: return MELNORME0_GRPOFFS0;
+ case MELNORME1_DEFINED: return MELNORME1_GRPOFFS0;
+ case MELNORME2_DEFINED: return MELNORME2_GRPOFFS0;
+ case MELNORME3_DEFINED: return MELNORME3_GRPOFFS0;
+ case MELNORME4_DEFINED: return MELNORME4_GRPOFFS0;
+ case MELNORME5_DEFINED: return MELNORME5_GRPOFFS0;
+ case MELNORME6_DEFINED: return MELNORME6_GRPOFFS0;
+ case MELNORME7_DEFINED: return MELNORME7_GRPOFFS0;
+ case MELNORME8_DEFINED: return MELNORME8_GRPOFFS0;
+ default:
+ return -1;
+ }
+}
+
+static DWORD
+GetMelnormeRef (void)
+{
+ int RefVar = SelectMelnormeRefVar ();
+ if (RefVar < 0)
+ {
+ log_add (log_Warning, "GetMelnormeRef(): reference unknown");
+ return 0;
+ }
+
+ return GET_GAME_STATE_32 (RefVar);
+}
+
+static void
+SetMelnormeRef (DWORD Ref)
+{
+ int RefVar = SelectMelnormeRefVar ();
+ if (RefVar < 0)
+ {
+ log_add (log_Warning, "SetMelnormeRef(): reference unknown");
+ return;
+ }
+
+ SET_GAME_STATE_32 (RefVar, Ref);
+}
+
diff --git a/src/uqm/planets/generate/genmyc.c b/src/uqm/planets/generate/genmyc.c
new file mode 100644
index 0000000..ead32c7
--- /dev/null
+++ b/src/uqm/planets/generate/genmyc.c
@@ -0,0 +1,286 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../scan.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../gendef.h"
+#include "../../starmap.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../setup.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateMycon_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateMycon_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateMycon_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static COUNT GenerateMycon_generateLife (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateMycon_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateMyconFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateMycon_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateMycon_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateMycon_generateEnergy,
+ /* .generateLife = */ GenerateMycon_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateMycon_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateMycon_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = SHATTERED_WORLD;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 80L / 100;
+ if (solarSys->PlanetDesc[0].NumPlanets > 2)
+ solarSys->PlanetDesc[0].NumPlanets = 2;
+ angle = ARCTAN (
+ solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GenerateMycon_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if ((CurStarDescPtr->Index == MYCON_DEFINED
+ || CurStarDescPtr->Index == SUN_DEVICE_DEFINED)
+ && StartSphereTracking (MYCON_SHIP))
+ {
+ if (CurStarDescPtr->Index == MYCON_DEFINED
+ || !GET_GAME_STATE (SUN_DEVICE_UNGUARDED))
+ {
+ NotifyOthers (MYCON_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ if (CurStarDescPtr->Index == MYCON_DEFINED
+ || !GET_GAME_STATE (MYCON_FELL_FOR_AMBUSH))
+ {
+ CloneShipFragment (MYCON_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+ }
+ else
+ {
+ COUNT i;
+
+ for (i = 0; i < 5; ++i)
+ CloneShipFragment (MYCON_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ }
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ if (CurStarDescPtr->Index == MYCON_DEFINED)
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ }
+ else
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6);
+ }
+ InitCommunication (MYCON_CONVERSATION);
+
+ if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ return true;
+
+ {
+ BOOLEAN MyconSurvivors;
+
+ MyconSurvivors =
+ GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0;
+
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+
+ if (MyconSurvivors)
+ return true;
+
+ SET_GAME_STATE (SUN_DEVICE_UNGUARDED, 1);
+ RepairSISBorder ();
+ }
+ }
+ }
+
+ switch (CurStarDescPtr->Index)
+ {
+ case SUN_DEVICE_DEFINED:
+ if (!GET_GAME_STATE (SUN_DEVICE))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (
+ LoadGraphic (SUN_DEVICE_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (SUN_DEVICE_STRTAB));
+ }
+ break;
+ case EGG_CASE0_DEFINED:
+ case EGG_CASE1_DEFINED:
+ case EGG_CASE2_DEFINED:
+ if (GET_GAME_STATE (KNOW_ABOUT_SHATTERED) == 0)
+ SET_GAME_STATE (KNOW_ABOUT_SHATTERED, 1);
+
+ if (!isNodeRetrieved (&solarSys->SysInfo.PlanetInfo,
+ ENERGY_SCAN, 0))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (
+ LoadGraphic (EGG_CASE_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (EGG_CASE_STRTAB));
+ }
+ break;
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
+static COUNT
+GenerateMycon_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (CurStarDescPtr->Index == SUN_DEVICE_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ if (GET_GAME_STATE (SUN_DEVICE))
+ { // already picked up
+ return 0;
+ }
+
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ if ((CurStarDescPtr->Index == EGG_CASE0_DEFINED
+ || CurStarDescPtr->Index == EGG_CASE1_DEFINED
+ || CurStarDescPtr->Index == EGG_CASE2_DEFINED)
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ // XXX: DiscoveryString is set by generateOrbital() only when the
+ // node has not been picked up yet
+ if (!solarSys->SysInfo.PlanetInfo.DiscoveryString)
+ { // already picked up
+ return 0;
+ }
+
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateMycon_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (CurStarDescPtr->Index == SUN_DEVICE_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ assert (!GET_GAME_STATE (SUN_DEVICE) && whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (SUN_DEVICE, 1);
+ SET_GAME_STATE (SUN_DEVICE_ON_SHIP, 1);
+ SET_GAME_STATE (MYCON_VISITS, 0);
+
+ return true; // picked up
+ }
+
+ if ((CurStarDescPtr->Index == EGG_CASE0_DEFINED
+ || CurStarDescPtr->Index == EGG_CASE1_DEFINED
+ || CurStarDescPtr->Index == EGG_CASE2_DEFINED)
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ assert (whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ switch (CurStarDescPtr->Index)
+ {
+ case EGG_CASE0_DEFINED:
+ SET_GAME_STATE (EGG_CASE0_ON_SHIP, 1);
+ break;
+ case EGG_CASE1_DEFINED:
+ SET_GAME_STATE (EGG_CASE1_ON_SHIP, 1);
+ break;
+ case EGG_CASE2_DEFINED:
+ SET_GAME_STATE (EGG_CASE2_ON_SHIP, 1);
+ break;
+ }
+
+ return true; // picked up
+ }
+
+ (void) whichNode;
+ return false;
+}
+
+static COUNT
+GenerateMycon_generateLife (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ // Gee, I wonder why there isn't any life in Mycon systems...
+ (void) whichNode;
+ (void) solarSys;
+ (void) world;
+ (void) info;
+ return 0;
+}
+
diff --git a/src/uqm/planets/generate/genorz.c b/src/uqm/planets/generate/genorz.c
new file mode 100644
index 0000000..a50f318
--- /dev/null
+++ b/src/uqm/planets/generate/genorz.c
@@ -0,0 +1,222 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../gendef.h"
+#include "../../starmap.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../setup.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateOrz_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateOrz_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateOrz_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateOrz_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateOrzFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateOrz_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateOrz_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateOrz_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateOrz_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateOrz_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ if (CurStarDescPtr->Index == ORZ_DEFINED)
+ {
+ solarSys->PlanetDesc[0].data_index = WATER_WORLD;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 156L / 100;
+ solarSys->PlanetDesc[0].NumPlanets = 0;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+ }
+
+ return true;
+}
+
+static bool
+GenerateOrz_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if ((CurStarDescPtr->Index == ORZ_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ || (CurStarDescPtr->Index == TAALO_PROTECTOR_DEFINED
+ && matchWorld (solarSys, world, 1, 2)
+ && !GET_GAME_STATE (TAALO_PROTECTOR)))
+ {
+ COUNT i;
+
+ if ((CurStarDescPtr->Index == ORZ_DEFINED
+ || !GET_GAME_STATE (TAALO_UNPROTECTED))
+ && StartSphereTracking (ORZ_SHIP))
+ {
+ NotifyOthers (ORZ_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ if (CurStarDescPtr->Index == ORZ_DEFINED)
+ {
+ CloneShipFragment (ORZ_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ }
+ else
+ {
+ for (i = 0; i < 14; ++i)
+ {
+ CloneShipFragment (ORZ_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ }
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6);
+ }
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ InitCommunication (ORZ_CONVERSATION);
+
+ if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ return true;
+
+ {
+ BOOLEAN OrzSurvivors;
+
+ OrzSurvivors = GetHeadLink (&GLOBAL (npc_built_ship_q))
+ && (CurStarDescPtr->Index == ORZ_DEFINED
+ || !GET_GAME_STATE (TAALO_UNPROTECTED));
+
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+
+ if (OrzSurvivors)
+ return true;
+
+ RepairSISBorder ();
+ }
+ }
+
+ SET_GAME_STATE (TAALO_UNPROTECTED, 1);
+ if (CurStarDescPtr->Index == TAALO_PROTECTOR_DEFINED)
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (
+ LoadGraphic (TAALO_DEVICE_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (TAALO_DEVICE_STRTAB));
+ }
+ else
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (RUINS_STRTAB));
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ return true;
+}
+
+static COUNT
+GenerateOrz_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (CurStarDescPtr->Index == TAALO_PROTECTOR_DEFINED
+ && matchWorld (solarSys, world, 1, 2))
+ {
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ if (GET_GAME_STATE (TAALO_PROTECTOR))
+ { // already picked up
+ return 0;
+ }
+
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ if (CurStarDescPtr->Index == ORZ_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateOrz_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (CurStarDescPtr->Index == TAALO_PROTECTOR_DEFINED
+ && matchWorld (solarSys, world, 1, 2))
+ {
+ assert (!GET_GAME_STATE (TAALO_PROTECTOR) && whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (TAALO_PROTECTOR, 1);
+ SET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP, 1);
+
+ return true; // picked up
+ }
+
+ if (CurStarDescPtr->Index == ORZ_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/generate/genpet.c b/src/uqm/planets/generate/genpet.c
new file mode 100644
index 0000000..4c5515c
--- /dev/null
+++ b/src/uqm/planets/generate/genpet.c
@@ -0,0 +1,257 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../encount.h"
+#include "../../starmap.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../setup.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateTalkingPet_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateTalkingPet_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateTalkingPet_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateTalkingPet_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+static void ZapToUrquanEncounter (void);
+
+
+const GenerateFunctions generateTalkingPetFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateTalkingPet_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateTalkingPet_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateTalkingPet_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateTalkingPet_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateTalkingPet_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = TELLURIC_WORLD;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 204L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GenerateTalkingPet_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET)
+ && (GET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES)
+ || !GET_GAME_STATE (TALKING_PET)
+ || StartSphereTracking (UMGAH_SHIP)))
+ {
+ NotifyOthers (UMGAH_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ if (StartSphereTracking (UMGAH_SHIP))
+ {
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ if (!GET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES))
+ {
+ CloneShipFragment (UMGAH_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+ InitCommunication (UMGAH_CONVERSATION);
+ }
+ else
+ {
+ COUNT i;
+
+ for (i = 0; i < 10; ++i)
+ {
+ CloneShipFragment (UMGAH_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ }
+ InitCommunication (TALKING_PET_CONVERSATION);
+ }
+ }
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ BOOLEAN UmgahSurvivors;
+
+ UmgahSurvivors = GetHeadLink (
+ &GLOBAL (npc_built_ship_q)) != 0;
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+
+ if (GET_GAME_STATE (PLAYER_HYPNOTIZED))
+ ZapToUrquanEncounter ();
+ else if (GET_GAME_STATE (UMGAH_ZOMBIE_BLOBBIES)
+ && !UmgahSurvivors)
+ {
+ // Defeated the zombie fleet.
+ InitCommunication (TALKING_PET_CONVERSATION);
+ }
+ else if (!(StartSphereTracking (UMGAH_SHIP)))
+ {
+ // The Kohr-Ah have destroyed the Umgah, but the
+ // talking pet survived.
+ InitCommunication (TALKING_PET_CONVERSATION);
+ }
+
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+
+ return true;
+ }
+
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (RUINS_STRTAB));
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+
+ return true;
+}
+
+static COUNT
+GenerateTalkingPet_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateTalkingPet_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ (void) whichNode;
+ return false;
+}
+
+static void
+ZapToUrquanEncounter (void)
+{
+ HENCOUNTER hEncounter;
+
+ if ((hEncounter = AllocEncounter ()) || (hEncounter = GetHeadEncounter ()))
+ {
+ SIZE dx, dy;
+ ENCOUNTER *EncounterPtr;
+ HFLEETINFO hStarShip;
+ FLEET_INFO *TemplatePtr;
+ BRIEF_SHIP_INFO *BSIPtr;
+
+ LockEncounter (hEncounter, &EncounterPtr);
+
+ if (hEncounter == GetHeadEncounter ())
+ RemoveEncounter (hEncounter);
+ memset (EncounterPtr, 0, sizeof (*EncounterPtr));
+
+ InsertEncounter (hEncounter, GetHeadEncounter ());
+
+ hStarShip = GetStarShipFromIndex (&GLOBAL (avail_race_q), URQUAN_SHIP);
+ TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ EncounterPtr->origin = TemplatePtr->loc;
+ EncounterPtr->radius = TemplatePtr->actual_strength;
+ EncounterPtr->race_id = URQUAN_SHIP;
+ EncounterPtr->num_ships = 1;
+ EncounterPtr->flags = ONE_SHOT_ENCOUNTER;
+ BSIPtr = &EncounterPtr->ShipList[0];
+ BSIPtr->race_id = URQUAN_SHIP;
+ BSIPtr->crew_level = TemplatePtr->crew_level;
+ BSIPtr->max_crew = TemplatePtr->max_crew;
+ BSIPtr->max_energy = TemplatePtr->max_energy;
+ EncounterPtr->loc_pt.x = 5288;
+ EncounterPtr->loc_pt.y = 4892;
+ EncounterPtr->log_x = UNIVERSE_TO_LOGX (EncounterPtr->loc_pt.x);
+ EncounterPtr->log_y = UNIVERSE_TO_LOGY (EncounterPtr->loc_pt.y);
+ GLOBAL_SIS (log_x) = EncounterPtr->log_x;
+ GLOBAL_SIS (log_y) = EncounterPtr->log_y;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+
+ {
+#define LOST_DAYS 15
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND * 2));
+ MoveGameClockDays (LOST_DAYS);
+ }
+
+ GLOBAL (CurrentActivity) = MAKE_WORD (IN_HYPERSPACE, 0) | START_ENCOUNTER;
+
+ dx = CurStarDescPtr->star_pt.x - EncounterPtr->loc_pt.x;
+ dy = CurStarDescPtr->star_pt.y - EncounterPtr->loc_pt.y;
+ dx = (SIZE)square_root ((long)dx * dx + (long)dy * dy)
+ + (FUEL_TANK_SCALE >> 1);
+
+ DeltaSISGauges (0, -dx, 0);
+ if (GLOBAL_SIS (FuelOnBoard) < 5 * FUEL_TANK_SCALE)
+ {
+ dx = ((5 + ((COUNT)TFB_Random () % 5)) * FUEL_TANK_SCALE)
+ - (SIZE)GLOBAL_SIS (FuelOnBoard);
+ DeltaSISGauges (0, dx, 0);
+ }
+ DrawSISMessage (NULL);
+ DrawHyperCoords (EncounterPtr->loc_pt);
+
+ UnlockEncounter (hEncounter);
+ }
+}
+
diff --git a/src/uqm/planets/generate/genpku.c b/src/uqm/planets/generate/genpku.c
new file mode 100644
index 0000000..64b9965
--- /dev/null
+++ b/src/uqm/planets/generate/genpku.c
@@ -0,0 +1,159 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GeneratePkunk_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GeneratePkunk_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GeneratePkunk_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GeneratePkunk_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generatePkunkFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GeneratePkunk_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GeneratePkunk_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GeneratePkunk_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GeneratePkunk_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GeneratePkunk_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = WATER_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 1;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 104L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GeneratePkunk_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (StartSphereTracking (PKUNK_SHIP))
+ {
+ NotifyOthers (PKUNK_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (PKUNK_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ InitCommunication (PKUNK_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+ return true;
+ }
+ else
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (PKUNK_RUINS_STRTAB));
+ if (GET_GAME_STATE (CLEAR_SPINDLE))
+ { // Already picked up the Clear Spindle, skip the report
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ SetAbsStringTableIndex (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString, 1);
+ }
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
+static bool
+GeneratePkunk_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ GenerateDefault_landerReportCycle (solarSys);
+
+ // The artifact can be picked up from any ruin
+ if (!GET_GAME_STATE (CLEAR_SPINDLE))
+ { // Just picked up the Clear Spindle from a ruin
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (CLEAR_SPINDLE, 1);
+ SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 1);
+ }
+
+ return false; // do not remove the node
+ }
+
+ (void) whichNode;
+ return false;
+}
+
+static COUNT
+GeneratePkunk_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
diff --git a/src/uqm/planets/generate/genrain.c b/src/uqm/planets/generate/genrain.c
new file mode 100644
index 0000000..c149b29
--- /dev/null
+++ b/src/uqm/planets/generate/genrain.c
@@ -0,0 +1,102 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../gendef.h"
+#include "../../starmap.h"
+#include "../../globdata.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateRainbowWorld_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateRainbowWorld_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+
+
+const GenerateFunctions generateRainbowWorldFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateRainbowWorld_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateRainbowWorld_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateRainbowWorld_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = RAINBOW_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 0;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 50L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ if (angle <= QUADRANT)
+ angle += QUADRANT;
+ else if (angle >= FULL_CIRCLE - QUADRANT)
+ angle -= QUADRANT;
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GenerateRainbowWorld_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ BYTE which_rainbow;
+ UWORD rainbow_mask;
+ STAR_DESC *SDPtr;
+
+ rainbow_mask = MAKE_WORD (
+ GET_GAME_STATE (RAINBOW_WORLD0),
+ GET_GAME_STATE (RAINBOW_WORLD1));
+
+ which_rainbow = 0;
+ SDPtr = &star_array[0];
+ while (SDPtr != CurStarDescPtr)
+ {
+ if (SDPtr->Index == RAINBOW_DEFINED)
+ ++which_rainbow;
+ ++SDPtr;
+ }
+ rainbow_mask |= 1 << which_rainbow;
+ SET_GAME_STATE (RAINBOW_WORLD0, LOBYTE (rainbow_mask));
+ SET_GAME_STATE (RAINBOW_WORLD1, HIBYTE (rainbow_mask));
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
diff --git a/src/uqm/planets/generate/gensam.c b/src/uqm/planets/generate/gensam.c
new file mode 100644
index 0000000..b2398b3
--- /dev/null
+++ b/src/uqm/planets/generate/gensam.c
@@ -0,0 +1,324 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../encount.h"
+#include "../../globdata.h"
+#include "../../grpinfo.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateSaMatra_initNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateSaMatra_reinitNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateSaMatra_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateSaMatra_generateMoons (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *planet);
+static bool GenerateSaMatra_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+
+static void BuildUrquanGuard (SOLARSYS_STATE *solarSys);
+
+
+const GenerateFunctions generateSaMatraFunctions = {
+ /* .initNpcs = */ GenerateSaMatra_initNpcs,
+ /* .reinitNpcs = */ GenerateSaMatra_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateSaMatra_generatePlanets,
+ /* .generateMoons = */ GenerateSaMatra_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateSaMatra_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateSaMatra_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ if (!GET_GAME_STATE (URQUAN_MESSED_UP))
+ {
+ BuildUrquanGuard (solarSys);
+ }
+ else
+ { // Exorcise Ur-Quan ghosts upon system reentry
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ // wipe out the group
+ }
+
+ (void) solarSys;
+ return true;
+}
+
+static bool
+GenerateSaMatra_reinitNpcs (SOLARSYS_STATE *solarSys)
+{
+ BOOLEAN GuardEngaged;
+ HIPGROUP hGroup;
+ HIPGROUP hNextGroup;
+
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ EncounterGroup = 0;
+ EncounterRace = -1;
+ // Do not want guards to chase the player
+
+ GuardEngaged = FALSE;
+ for (hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ hGroup; hGroup = hNextGroup)
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+
+ if (GET_GAME_STATE (URQUAN_MESSED_UP))
+ {
+ GroupPtr->task &= REFORM_GROUP;
+ GroupPtr->task |= FLEE | IGNORE_FLAGSHIP;
+ GroupPtr->dest_loc = 0;
+ }
+ else if (GroupPtr->task & REFORM_GROUP)
+ {
+ // REFORM_GROUP was set in ipdisp.c:ip_group_collision
+ // during a collision with the flagship.
+ GroupPtr->task &= ~REFORM_GROUP;
+ GroupPtr->group_counter = 0;
+
+ GuardEngaged = TRUE;
+ }
+
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ }
+
+ if (GuardEngaged)
+ {
+ COUNT angle;
+ POINT org;
+
+ org = planetOuterLocation (4);
+ angle = ARCTAN (GLOBAL (ip_location.x) - org.x,
+ GLOBAL (ip_location.y) - org.y);
+ GLOBAL (ip_location.x) = org.x + COSINE (angle, 3000);
+ GLOBAL (ip_location.y) = org.y + SINE (angle, 3000);
+ XFormIPLoc (&GLOBAL (ip_location),
+ &GLOBAL (ShipStamp.origin), TRUE);
+ }
+
+ (void) solarSys;
+ return true;
+}
+
+static bool
+GenerateSaMatra_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ GenerateDefault_generatePlanets (solarSys);
+ solarSys->PlanetDesc[4].NumPlanets = 1;
+ return true;
+}
+
+static bool
+GenerateSaMatra_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet)
+{
+ GenerateDefault_generateMoons (solarSys, planet);
+
+ if (matchWorld (solarSys, planet, 4, MATCH_PLANET))
+ {
+ COUNT angle;
+ DWORD rand_val;
+
+ solarSys->MoonDesc[0].data_index = SA_MATRA;
+ solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS + (2 * MOON_DELTA);
+ rand_val = RandomContext_Random (SysGenRNG);
+ angle = NORMALIZE_ANGLE (LOWORD (rand_val));
+ solarSys->MoonDesc[0].location.x =
+ COSINE (angle, solarSys->MoonDesc[0].radius);
+ solarSys->MoonDesc[0].location.y =
+ SINE (angle, solarSys->MoonDesc[0].radius);
+ }
+
+ return true;
+}
+
+static bool
+GenerateSaMatra_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ /* Samatra */
+ if (matchWorld (solarSys, world, 4, 0))
+ {
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ if (!GET_GAME_STATE (URQUAN_MESSED_UP))
+ {
+ CloneShipFragment (!GET_GAME_STATE (KOHR_AH_FRENZY) ?
+ URQUAN_SHIP : BLACK_URQUAN_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+ }
+ else
+ {
+#define URQUAN_REMNANTS 3
+ BYTE i;
+
+ for (i = 0; i < URQUAN_REMNANTS; ++i)
+ {
+ CloneShipFragment (URQUAN_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ CloneShipFragment (BLACK_URQUAN_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ }
+ }
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ SET_GAME_STATE (URQUAN_PROTECTING_SAMATRA, 1);
+ InitCommunication (URQUAN_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ BOOLEAN UrquanSurvivors;
+
+ UrquanSurvivors = GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0;
+
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ if (UrquanSurvivors)
+ {
+ SET_GAME_STATE (URQUAN_PROTECTING_SAMATRA, 0);
+ }
+ else
+ {
+ EncounterGroup = 0;
+ EncounterRace = -1;
+ GLOBAL (CurrentActivity) = IN_LAST_BATTLE | START_ENCOUNTER;
+ if (GET_GAME_STATE (YEHAT_CIVIL_WAR)
+ && StartSphereTracking (YEHAT_SHIP)
+ && EscortFeasibilityStudy (YEHAT_REBEL_SHIP))
+ InitCommunication (YEHAT_REBEL_CONVERSATION);
+ }
+ }
+ return true;
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
+static void
+BuildUrquanGuard (SOLARSYS_STATE *solarSys)
+{
+ BYTE ship1, ship2;
+ BYTE b0, b1;
+ POINT org;
+ HIPGROUP hGroup, hNextGroup;
+
+ GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (SAMATRA_GRPOFFS0);
+
+ if (!GET_GAME_STATE (KOHR_AH_FRENZY))
+ {
+ ship1 = URQUAN_SHIP;
+ ship2 = BLACK_URQUAN_SHIP;
+ }
+ else
+ {
+ ship1 = BLACK_URQUAN_SHIP;
+ ship2 = URQUAN_SHIP;
+ }
+
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ for (b0 = 0; b0 < MAX_SHIPS_PER_SIDE; ++b0)
+ CloneShipFragment (ship1, &GLOBAL (npc_built_ship_q), 0);
+
+ if (GLOBAL (BattleGroupRef) == 0)
+ {
+ GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1);
+ SET_GAME_STATE_32 (SAMATRA_GRPOFFS0, GLOBAL (BattleGroupRef));
+ }
+
+#define NUM_URQUAN_GUARDS0 12
+ for (b0 = 1; b0 <= NUM_URQUAN_GUARDS0; ++b0)
+ PutGroupInfo (GLOBAL (BattleGroupRef), b0);
+
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ for (b0 = 0; b0 < MAX_SHIPS_PER_SIDE; ++b0)
+ CloneShipFragment (ship2, &GLOBAL (npc_built_ship_q), 0);
+
+#define NUM_URQUAN_GUARDS1 4
+ for (b0 = 1; b0 <= NUM_URQUAN_GUARDS1; ++b0)
+ PutGroupInfo (GLOBAL (BattleGroupRef),
+ (BYTE)(NUM_URQUAN_GUARDS0 + b0));
+
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+
+ GetGroupInfo (GLOBAL (BattleGroupRef), GROUP_INIT_IP);
+
+ org = planetOuterLocation (4);
+ hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ for (b0 = 0, b1 = 0;
+ b0 < NUM_URQUAN_GUARDS0;
+ ++b0, b1 += FULL_CIRCLE / (NUM_URQUAN_GUARDS0 + NUM_URQUAN_GUARDS1))
+ {
+ IP_GROUP *GroupPtr;
+
+ if (b1 % (FULL_CIRCLE / NUM_URQUAN_GUARDS1) == 0)
+ b1 += FULL_CIRCLE / (NUM_URQUAN_GUARDS0 + NUM_URQUAN_GUARDS1);
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+ GroupPtr->task = ON_STATION | IGNORE_FLAGSHIP;
+ GroupPtr->sys_loc = 0;
+ GroupPtr->dest_loc = 4 + 1;
+ GroupPtr->orbit_pos = NORMALIZE_FACING (ANGLE_TO_FACING (b1));
+ GroupPtr->group_counter = 0;
+ GroupPtr->loc.x = org.x + COSINE (b1, STATION_RADIUS);
+ GroupPtr->loc.y = org.y + SINE (b1, STATION_RADIUS);
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hGroup = hNextGroup;
+ }
+
+ for (b0 = 0, b1 = 0;
+ b0 < NUM_URQUAN_GUARDS1;
+ ++b0, b1 += FULL_CIRCLE / NUM_URQUAN_GUARDS1)
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+ GroupPtr->task = ON_STATION | IGNORE_FLAGSHIP;
+ GroupPtr->sys_loc = 0;
+ GroupPtr->dest_loc = 4 + 1;
+ GroupPtr->orbit_pos = NORMALIZE_FACING (ANGLE_TO_FACING (b1));
+ GroupPtr->group_counter = 0;
+ GroupPtr->loc.x = org.x + COSINE (b1, STATION_RADIUS);
+ GroupPtr->loc.y = org.y + SINE (b1, STATION_RADIUS);
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ hGroup = hNextGroup;
+ }
+
+ (void) solarSys;
+}
+
diff --git a/src/uqm/planets/generate/genshof.c b/src/uqm/planets/generate/genshof.c
new file mode 100644
index 0000000..7025f6a
--- /dev/null
+++ b/src/uqm/planets/generate/genshof.c
@@ -0,0 +1,178 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../../build.h"
+#include "../../globdata.h"
+#include "../../grpinfo.h"
+#include "../../state.h"
+#include "../planets.h"
+
+
+static bool GenerateShofixti_initNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateShofixti_reinitNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateShofixti_uninitNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateShofixti_generatePlanets (SOLARSYS_STATE *solarSys);
+
+static void check_old_shofixti (void);
+
+
+const GenerateFunctions generateShofixtiFunctions = {
+ /* .initNpcs = */ GenerateShofixti_initNpcs,
+ /* .reinitNpcs = */ GenerateShofixti_reinitNpcs,
+ /* .uninitNpcs = */ GenerateShofixti_uninitNpcs,
+ /* .generatePlanets = */ GenerateShofixti_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateDefault_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateShofixti_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ if (!GET_GAME_STATE (SHOFIXTI_RECRUITED)
+ && (!GET_GAME_STATE (SHOFIXTI_KIA)
+ || (!GET_GAME_STATE (SHOFIXTI_BRO_KIA)
+ && GET_GAME_STATE (MAIDENS_ON_SHIP))))
+ {
+ GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (SHOFIXTI_GRPOFFS0);
+ if (GLOBAL (BattleGroupRef) == 0
+ || !GetGroupInfo (GLOBAL (BattleGroupRef), GROUP_INIT_IP))
+ {
+ HSHIPFRAG hStarShip;
+
+ if (GLOBAL (BattleGroupRef) == 0)
+ GLOBAL (BattleGroupRef) = ~0L;
+
+ hStarShip = CloneShipFragment (SHOFIXTI_SHIP,
+ &GLOBAL (npc_built_ship_q), 1);
+ if (hStarShip)
+ { /* Set old Shofixti name; his brother if Tanaka died */
+ SHIP_FRAGMENT *FragPtr = LockShipFrag (
+ &GLOBAL (npc_built_ship_q), hStarShip);
+ /* Name Tanaka or Katana (+1) */
+ FragPtr->captains_name_index =
+ NAME_OFFSET + NUM_CAPTAINS_NAMES +
+ (GET_GAME_STATE (SHOFIXTI_KIA) & 1);
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ }
+
+ GLOBAL (BattleGroupRef) = PutGroupInfo (
+ GLOBAL (BattleGroupRef), 1);
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ SET_GAME_STATE_32 (SHOFIXTI_GRPOFFS0, GLOBAL (BattleGroupRef));
+ }
+ }
+
+ // This was originally a fallthrough to REINIT_NPCS.
+ // XXX: is the call to check_old_shofixti() needed?
+ GenerateDefault_initNpcs (solarSys);
+ check_old_shofixti ();
+
+ return true;
+}
+
+static bool
+GenerateShofixti_reinitNpcs (SOLARSYS_STATE *solarSys)
+{
+ GenerateDefault_reinitNpcs (solarSys);
+ check_old_shofixti ();
+
+ (void) solarSys;
+ return true;
+}
+
+static bool
+GenerateShofixti_uninitNpcs (SOLARSYS_STATE *solarSys)
+{
+ if (GLOBAL (BattleGroupRef)
+ && !GET_GAME_STATE (SHOFIXTI_RECRUITED)
+ && GetHeadLink (&GLOBAL (ip_group_q)) == 0)
+ {
+ if (!GET_GAME_STATE (SHOFIXTI_KIA))
+ {
+ SET_GAME_STATE (SHOFIXTI_KIA, 1);
+ SET_GAME_STATE (SHOFIXTI_VISITS, 0);
+ }
+ else if (GET_GAME_STATE (MAIDENS_ON_SHIP))
+ {
+ SET_GAME_STATE (SHOFIXTI_BRO_KIA, 1);
+ }
+ }
+
+ GenerateDefault_uninitNpcs (solarSys);
+ return true;
+}
+
+static bool
+GenerateShofixti_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT i;
+
+#define NUM_PLANETS 6
+ solarSys->SunDesc[0].NumPlanets = NUM_PLANETS;
+ for (i = 0; i < NUM_PLANETS; ++i)
+ {
+ PLANET_DESC *pCurDesc = &solarSys->PlanetDesc[i];
+
+ pCurDesc->NumPlanets = 0;
+ if (i < (NUM_PLANETS >> 1))
+ pCurDesc->data_index = SELENIC_WORLD;
+ else
+ pCurDesc->data_index = METAL_WORLD;
+ }
+
+ FillOrbits (solarSys, NUM_PLANETS, solarSys->PlanetDesc, TRUE);
+
+ return true;
+}
+
+
+static void
+check_old_shofixti (void)
+{
+ HIPGROUP hGroup;
+ IP_GROUP *GroupPtr;
+
+ if (!GLOBAL (BattleGroupRef))
+ return; // nothing to check
+
+ hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ if (!hGroup)
+ return; // still nothing to check
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ // REFORM_GROUP was set in ipdisp.c:ip_group_collision()
+ // during a collision with the flagship.
+ if (GroupPtr->race_id == SHOFIXTI_SHIP
+ && (GroupPtr->task & REFORM_GROUP)
+ && GET_GAME_STATE (SHOFIXTI_RECRUITED))
+ {
+ GroupPtr->task = FLEE | IGNORE_FLAGSHIP | REFORM_GROUP;
+ GroupPtr->dest_loc = 0;
+ }
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+}
+
diff --git a/src/uqm/planets/generate/gensly.c b/src/uqm/planets/generate/gensly.c
new file mode 100644
index 0000000..48ed100
--- /dev/null
+++ b/src/uqm/planets/generate/gensly.c
@@ -0,0 +1,70 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../comm.h"
+
+
+static bool GenerateSlylandro_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateSlylandro_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+
+
+const GenerateFunctions generateSlylandroFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateSlylandro_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateSlylandro_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateSlylandro_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[3].data_index = RED_GAS_GIANT;
+ solarSys->PlanetDesc[3].NumPlanets = 1;
+
+ return true;
+}
+
+static bool
+GenerateSlylandro_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 3, MATCH_PLANET))
+ {
+ InitCommunication (SLYLANDRO_HOME_CONVERSATION);
+ return true;
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
diff --git a/src/uqm/planets/generate/gensol.c b/src/uqm/planets/generate/gensol.c
new file mode 100644
index 0000000..d6041f3
--- /dev/null
+++ b/src/uqm/planets/generate/gensol.c
@@ -0,0 +1,671 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../lifeform.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../encount.h"
+#include "../../globdata.h"
+#include "../../gamestr.h"
+#include "../../grpinfo.h"
+#include "../../nameref.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateSol_initNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateSol_reinitNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateSol_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateSol_generateMoons (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *planet);
+static bool GenerateSol_generateName (const SOLARSYS_STATE *,
+ const PLANET_DESC *world);
+static bool GenerateSol_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateSol_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static COUNT GenerateSol_generateLife (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateSol_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+static int init_probe (void);
+static void check_probe (void);
+
+
+const GenerateFunctions generateSolFunctions = {
+ /* .initNpcs = */ GenerateSol_initNpcs,
+ /* .reinitNpcs = */ GenerateSol_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateSol_generatePlanets,
+ /* .generateMoons = */ GenerateSol_generateMoons,
+ /* .generateName = */ GenerateSol_generateName,
+ /* .generateOrbital = */ GenerateSol_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateSol_generateEnergy,
+ /* .generateLife = */ GenerateSol_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateSol_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateSol_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (URQUAN_PROBE_GRPOFFS0);
+ if (GLOBAL (BattleGroupRef) == 0)
+ {
+ CloneShipFragment (URQUAN_DRONE_SHIP, &GLOBAL (npc_built_ship_q), 0);
+ GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1);
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ SET_GAME_STATE_32 (URQUAN_PROBE_GRPOFFS0, GLOBAL (BattleGroupRef));
+ }
+
+ if (!init_probe ())
+ GenerateDefault_initNpcs (solarSys);
+
+ return true;
+}
+
+static bool
+GenerateSol_reinitNpcs (SOLARSYS_STATE *solarSys)
+{
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) != 3)
+ {
+ GenerateDefault_reinitNpcs (solarSys);
+ check_probe ();
+ }
+ else
+ {
+ GLOBAL (BattleGroupRef) = 0;
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+ }
+ return true;
+}
+
+static bool
+GenerateSol_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT planetI;
+
+#define SOL_SEED 334241042L
+ RandomContext_SeedRandom (SysGenRNG, SOL_SEED);
+
+ solarSys->SunDesc[0].NumPlanets = 9;
+ for (planetI = 0; planetI < 9; ++planetI)
+ {
+ COUNT angle;
+ DWORD rand_val;
+ UWORD word_val;
+ PLANET_DESC *pCurDesc = &solarSys->PlanetDesc[planetI];
+
+ pCurDesc->rand_seed = RandomContext_Random (SysGenRNG);
+ rand_val = pCurDesc->rand_seed;
+ word_val = LOWORD (rand_val);
+ angle = NORMALIZE_ANGLE ((COUNT)HIBYTE (word_val));
+
+ switch (planetI)
+ {
+ case 0: /* MERCURY */
+ pCurDesc->data_index = METAL_WORLD;
+ pCurDesc->radius = EARTH_RADIUS * 39L / 100;
+ pCurDesc->NumPlanets = 0;
+ break;
+ case 1: /* VENUS */
+ pCurDesc->data_index = PRIMORDIAL_WORLD;
+ pCurDesc->radius = EARTH_RADIUS * 72L / 100;
+ pCurDesc->NumPlanets = 0;
+ angle = NORMALIZE_ANGLE (FULL_CIRCLE - angle);
+ break;
+ case 2: /* EARTH */
+ pCurDesc->data_index = WATER_WORLD | PLANET_SHIELDED;
+ pCurDesc->radius = EARTH_RADIUS;
+ pCurDesc->NumPlanets = 2;
+ break;
+ case 3: /* MARS */
+ pCurDesc->data_index = DUST_WORLD;
+ pCurDesc->radius = EARTH_RADIUS * 152L / 100;
+ pCurDesc->NumPlanets = 0;
+ break;
+ case 4: /* JUPITER */
+ pCurDesc->data_index = RED_GAS_GIANT;
+ pCurDesc->radius = EARTH_RADIUS * 500L /* 520L */ / 100;
+ pCurDesc->NumPlanets = 4;
+ break;
+ case 5: /* SATURN */
+ pCurDesc->data_index = ORA_GAS_GIANT;
+ pCurDesc->radius = EARTH_RADIUS * 750L /* 952L */ / 100;
+ pCurDesc->NumPlanets = 1;
+ break;
+ case 6: /* URANUS */
+ pCurDesc->data_index = GRN_GAS_GIANT;
+ pCurDesc->radius = EARTH_RADIUS * 1000L /* 1916L */ / 100;
+ pCurDesc->NumPlanets = 0;
+ break;
+ case 7: /* NEPTUNE */
+ pCurDesc->data_index = BLU_GAS_GIANT;
+ pCurDesc->radius = EARTH_RADIUS * 1250L /* 2999L */ / 100;
+ pCurDesc->NumPlanets = 1;
+ break;
+ case 8: /* PLUTO */
+ pCurDesc->data_index = PELLUCID_WORLD;
+ pCurDesc->radius = EARTH_RADIUS * 1550L /* 3937L */ / 100;
+ pCurDesc->NumPlanets = 0;
+ angle = FULL_CIRCLE - OCTANT;
+ break;
+ }
+
+ pCurDesc->location.x = COSINE (angle, pCurDesc->radius);
+ pCurDesc->location.y = SINE (angle, pCurDesc->radius);
+ }
+
+ return true;
+}
+
+static bool
+GenerateSol_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet)
+{
+ COUNT planetNr;
+ DWORD rand_val;
+
+ GenerateDefault_generateMoons (solarSys, planet);
+
+ planetNr = planetIndex (solarSys, planet);
+ switch (planetNr)
+ {
+ case 2: /* moons of EARTH */
+ {
+ COUNT angle;
+
+ /* Starbase: */
+ solarSys->MoonDesc[0].data_index = HIERARCHY_STARBASE;
+ solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS;
+ angle = HALF_CIRCLE + QUADRANT;
+ solarSys->MoonDesc[0].location.x =
+ COSINE (angle, solarSys->MoonDesc[0].radius);
+ solarSys->MoonDesc[0].location.y =
+ SINE (angle, solarSys->MoonDesc[0].radius);
+
+ /* Luna: */
+ solarSys->MoonDesc[1].data_index = SELENIC_WORLD;
+ solarSys->MoonDesc[1].radius = MIN_MOON_RADIUS
+ + (MAX_MOONS - 1) * MOON_DELTA;
+ rand_val = RandomContext_Random (SysGenRNG);
+ angle = NORMALIZE_ANGLE (LOWORD (rand_val));
+ solarSys->MoonDesc[1].location.x =
+ COSINE (angle, solarSys->MoonDesc[1].radius);
+ solarSys->MoonDesc[1].location.y =
+ SINE (angle, solarSys->MoonDesc[1].radius);
+ break;
+ }
+ case 4: /* moons of JUPITER */
+ solarSys->MoonDesc[0].data_index = RADIOACTIVE_WORLD;
+ /* Io */
+ solarSys->MoonDesc[1].data_index = HALIDE_WORLD;
+ /* Europa */
+ solarSys->MoonDesc[2].data_index = CYANIC_WORLD;
+ /* Ganymede */
+ solarSys->MoonDesc[3].data_index = PELLUCID_WORLD;
+ /* Callisto */
+ break;
+ case 5: /* moons of SATURN */
+ solarSys->MoonDesc[0].data_index = ALKALI_WORLD;
+ /* Titan */
+ break;
+ case 7: /* moons of NEPTUNE */
+ solarSys->MoonDesc[0].data_index = VINYLOGOUS_WORLD;
+ /* Triton */
+ break;
+ }
+
+ return true;
+}
+
+static bool
+GenerateSol_generateName (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world)
+{
+ COUNT planetNr = planetIndex (solarSys, world);
+ utf8StringCopy (GLOBAL_SIS (PlanetName), sizeof (GLOBAL_SIS (PlanetName)),
+ GAME_STRING (PLANET_NUMBER_BASE + planetNr));
+ SET_GAME_STATE (BATTLE_PLANET, solarSys->PlanetDesc[planetNr].data_index);
+
+ return true;
+}
+
+static bool
+GenerateSol_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ DWORD rand_val;
+ COUNT planetNr;
+
+ if (matchWorld (solarSys, world, 2, 0))
+ {
+ /* Starbase */
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ EncounterGroup = 0;
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, (BYTE)~0);
+ return true;
+ }
+
+ DoPlanetaryAnalysis (&solarSys->SysInfo, world);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val;
+ GenerateMineralDeposits (&solarSys->SysInfo, GENERATE_ALL, NULL);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ planetNr = planetIndex (solarSys, world);
+ if (worldIsPlanet (solarSys, world))
+ {
+ switch (planetNr)
+ {
+ case 0: /* MERCURY */
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = 0;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 98;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 38;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 3;
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 2;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 59 * 240;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 165;
+ break;
+ case 1: /* VENUS */
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = 90 *
+ EARTH_ATMOSPHERE;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 95;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 95;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 177;
+ solarSys->SysInfo.PlanetInfo.Weather = 7;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 243 * 240;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 457;
+ break;
+ case 2: /* EARTH */
+ solarSys->SysInfo.PlanetInfo.AtmoDensity =
+ EARTH_ATMOSPHERE;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 100;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 100;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 23;
+ solarSys->SysInfo.PlanetInfo.Weather = 1;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 240;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 22;
+ break;
+ case 3: /* MARS */
+ // XXX: Mars atmo should actually be 1/2 in current units
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = 1;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 72;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 53;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 24;
+ solarSys->SysInfo.PlanetInfo.Weather = 1;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 246;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -53;
+ break;
+ case 4: /* JUPITER */
+ solarSys->SysInfo.PlanetInfo.AtmoDensity =
+ GAS_GIANT_ATMOSPHERE;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 24;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 1120;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 3;
+ solarSys->SysInfo.PlanetInfo.Weather = 7;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 98;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -143;
+ solarSys->SysInfo.PlanetInfo.PlanetToSunDist =
+ EARTH_RADIUS * 520L / 100;
+ break;
+ case 5: /* SATURN */
+ solarSys->SysInfo.PlanetInfo.AtmoDensity =
+ GAS_GIANT_ATMOSPHERE;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 13;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 945;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 27;
+ solarSys->SysInfo.PlanetInfo.Weather = 7;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 102;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -197;
+ solarSys->SysInfo.PlanetInfo.PlanetToSunDist =
+ EARTH_RADIUS * 952L / 100;
+ break;
+ case 6: /* URANUS */
+ solarSys->SysInfo.PlanetInfo.AtmoDensity =
+ GAS_GIANT_ATMOSPHERE;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 21;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 411;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 98;
+ solarSys->SysInfo.PlanetInfo.Weather = 7;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 172;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -217;
+ solarSys->SysInfo.PlanetInfo.PlanetToSunDist =
+ EARTH_RADIUS * 1916L / 100;
+ break;
+ case 7: /* NEPTUNE */
+ solarSys->SysInfo.PlanetInfo.AtmoDensity =
+ GAS_GIANT_ATMOSPHERE;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 28;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 396;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 30;
+ solarSys->SysInfo.PlanetInfo.Weather = 7;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 182;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -229;
+ solarSys->SysInfo.PlanetInfo.PlanetToSunDist =
+ EARTH_RADIUS * 2999L / 100;
+ break;
+ case 8: /* PLUTO */
+ if (!GET_GAME_STATE (FOUND_PLUTO_SPATHI))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (
+ LoadGraphic (SPAPLUTO_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (SPAPLUTO_STRTAB));
+ }
+
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = 0;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 33;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 18;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 119;
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 1533;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -235;
+ solarSys->SysInfo.PlanetInfo.PlanetToSunDist =
+ EARTH_RADIUS * 3937L / 100;
+ break;
+ }
+
+ solarSys->SysInfo.PlanetInfo.SurfaceGravity =
+ CalcGravity (&solarSys->SysInfo.PlanetInfo);
+ LoadPlanet (planetNr == 2 ?
+ CaptureDrawable (LoadGraphic (EARTH_MASK_ANIM)) : NULL);
+ }
+ else
+ {
+ // World is a moon.
+ COUNT moonNr = moonIndex (solarSys, world);
+
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 0;
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = 0;
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+ switch (planetNr)
+ {
+ case 2: /* moons of EARTH */
+ // NOTE: Even though we save the seed here, it is irrelevant.
+ // The seed will be used to randomly place the tractors, but
+ // since they are mobile, they will be moved to different
+ // locations not governed by this seed.
+ solarSys->SysInfo.PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] =
+ rand_val;
+
+ if (!GET_GAME_STATE (MOONBASE_DESTROYED))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (
+ LoadGraphic (MOONBASE_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (MOONBASE_STRTAB));
+ }
+
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 60;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 25;
+ solarSys->SysInfo.PlanetInfo.AxialTilt = 0;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 240 * 29;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -18;
+ break;
+
+ case 4: /* moons of JUPITER */
+ solarSys->SysInfo.PlanetInfo.PlanetToSunDist =
+ EARTH_RADIUS * 520L / 100;
+ switch (moonNr)
+ {
+ case 0: /* Io */
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 69;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 25;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 3;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 390;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -163;
+ break;
+ case 1: /* Europa */
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 54;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 25;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 840;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -161;
+ break;
+ case 2: /* Ganymede */
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 35;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 41;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 1728;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -164;
+ break;
+ case 3: /* Callisto */
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 35;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 38;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 4008;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -167;
+ break;
+ }
+ break;
+
+ case 5: /* moon of SATURN: Titan */
+ solarSys->SysInfo.PlanetInfo.PlanetToSunDist =
+ EARTH_RADIUS * 952L / 100;
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = 160;
+ solarSys->SysInfo.PlanetInfo.Weather = 2;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 34;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 40;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 3816;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -178;
+ break;
+
+ case 7: /* moon of NEPTUNE: Triton */
+ solarSys->SysInfo.PlanetInfo.PlanetToSunDist =
+ EARTH_RADIUS * 2999L / 100;
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = 10;
+ solarSys->SysInfo.PlanetInfo.Weather = 1;
+ solarSys->SysInfo.PlanetInfo.PlanetDensity = 95;
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 27;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.RotationPeriod = 4300;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = -216;
+ break;
+ }
+
+ solarSys->SysInfo.PlanetInfo.SurfaceGravity =
+ CalcGravity (&solarSys->SysInfo.PlanetInfo);
+ LoadPlanet (NULL);
+ }
+
+ return true;
+}
+
+static COUNT
+GenerateSol_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 8, MATCH_PLANET))
+ {
+ /* Pluto */
+ // This check is needed because the retrieval bit is not set for
+ // this node to keep it on the surface while the lander is taking off
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI))
+ { // already picked up
+ return 0;
+ }
+
+ if (info)
+ {
+ info->loc_pt.x = 20;
+ info->loc_pt.y = MAP_HEIGHT - 8;
+ }
+
+ return 1; // only matters when count is requested
+ }
+
+ if (matchWorld (solarSys, world, 2, 1))
+ {
+ /* Earth Moon */
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ if (GET_GAME_STATE (MOONBASE_DESTROYED))
+ { // already picked up
+ return 0;
+ }
+
+ if (info)
+ {
+ info->loc_pt.x = MAP_WIDTH * 3 / 4;
+ info->loc_pt.y = MAP_HEIGHT * 1 / 4;
+ }
+
+ return 1; // only matters when count is requested
+ }
+
+ (void) whichNode;
+ return 0;
+}
+
+static bool
+GenerateSol_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 8, MATCH_PLANET))
+ { // Pluto
+ assert (!GET_GAME_STATE (FOUND_PLUTO_SPATHI) && whichNode == 0);
+
+ // Ran into Fwiffo on Pluto
+ #define FWIFFO_FRAGS 8
+ if (!KillLanderCrewSeq (FWIFFO_FRAGS, ONE_SECOND / 20))
+ return false; // lander probably died
+
+ SET_GAME_STATE (FOUND_PLUTO_SPATHI, 1);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ // Do not remove the node from the surface while the lander is
+ // taking off. FOUND_PLUTO_SPATHI bit will keep the node from
+ // showing up on subsequent visits.
+ return false;
+ }
+
+ if (matchWorld (solarSys, world, 2, 1))
+ { // Earth Moon
+ assert (!GET_GAME_STATE (MOONBASE_DESTROYED) && whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (MOONBASE_DESTROYED, 1);
+ SET_GAME_STATE (MOONBASE_ON_SHIP, 1);
+
+ return true; // picked up
+ }
+
+ (void) whichNode;
+ return false;
+}
+
+static COUNT
+GenerateSol_generateLife (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 2, 1))
+ {
+ /* Earth Moon */
+ return GenerateRandomNodes (&solarSys->SysInfo, BIOLOGICAL_SCAN, 10,
+ NUM_CREATURE_TYPES + 1, whichNode, info);
+ }
+
+ return 0;
+}
+
+
+static int
+init_probe (void)
+{
+ HIPGROUP hGroup;
+
+ if (!GET_GAME_STATE (PROBE_MESSAGE_DELIVERED)
+ && GetGroupInfo (GLOBAL (BattleGroupRef), GROUP_INIT_IP)
+ && (hGroup = GetHeadLink (&GLOBAL (ip_group_q))))
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ GroupPtr->task = IN_ORBIT;
+ GroupPtr->sys_loc = 2 + 1; /* orbitting earth */
+ GroupPtr->dest_loc = 2 + 1; /* orbitting earth */
+ GroupPtr->loc.x = 0;
+ GroupPtr->loc.y = 0;
+ GroupPtr->group_counter = 0;
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+
+ return 1;
+ }
+ else
+ return 0;
+}
+
+static void
+check_probe (void)
+{
+ HIPGROUP hGroup;
+ IP_GROUP *GroupPtr;
+
+ if (!GLOBAL (BattleGroupRef))
+ return; // nothing to check
+
+ hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ if (!hGroup)
+ return; // still nothing to check
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ // REFORM_GROUP was set in ipdisp.c:ip_group_collision()
+ // during a collision with the flagship.
+ if (GroupPtr->race_id == URQUAN_DRONE_SHIP
+ && (GroupPtr->task & REFORM_GROUP))
+ {
+ // We just want the probe to take off as fast as possible,
+ // so clear out REFORM_GROUP
+ GroupPtr->task = FLEE | IGNORE_FLAGSHIP;
+ GroupPtr->dest_loc = 0;
+ }
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+}
+
diff --git a/src/uqm/planets/generate/genspa.c b/src/uqm/planets/generate/genspa.c
new file mode 100644
index 0000000..26bc412
--- /dev/null
+++ b/src/uqm/planets/generate/genspa.c
@@ -0,0 +1,283 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lifeform.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../scan.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateSpathi_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateSpathi_generateMoons (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *planet);
+static bool GenerateSpathi_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateSpathi_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static COUNT GenerateSpathi_generateLife (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateSpathi_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+static bool GenerateSpathi_pickupLife (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateSpathiFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateSpathi_generatePlanets,
+ /* .generateMoons = */ GenerateSpathi_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateSpathi_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateSpathi_generateEnergy,
+ /* .generateLife = */ GenerateSpathi_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateSpathi_pickupEnergy,
+ /* .pickupLife = */ GenerateSpathi_pickupLife,
+};
+
+
+static bool
+GenerateSpathi_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ PLANET_DESC *pMinPlanet;
+ COUNT angle;
+
+ pMinPlanet = &solarSys->PlanetDesc[0];
+ solarSys->SunDesc[0].NumPlanets = 1;
+ FillOrbits (solarSys,
+ solarSys->SunDesc[0].NumPlanets, pMinPlanet, FALSE);
+
+ pMinPlanet->radius = EARTH_RADIUS * 1150L / 100;
+ angle = ARCTAN (pMinPlanet->location.x, pMinPlanet->location.y);
+ pMinPlanet->location.x = COSINE (angle, pMinPlanet->radius);
+ pMinPlanet->location.y = SINE (angle, pMinPlanet->radius);
+ pMinPlanet->data_index = WATER_WORLD;
+ if (GET_GAME_STATE (SPATHI_SHIELDED_SELVES))
+ pMinPlanet->data_index |= PLANET_SHIELDED;
+ pMinPlanet->NumPlanets = 1;
+
+ return true;
+}
+
+static bool
+GenerateSpathi_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet)
+{
+ COUNT angle;
+
+ GenerateDefault_generateMoons (solarSys, planet);
+
+ if (matchWorld (solarSys, planet, 0, MATCH_PLANET))
+ {
+#ifdef NOTYET
+ utf8StringCopy (GLOBAL_SIS (PlanetName),
+ sizeof (GLOBAL_SIS (PlanetName)),
+ "Spathiwa");
+#endif /* NOTYET */
+
+ solarSys->MoonDesc[0].data_index = PELLUCID_WORLD;
+ solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS + MOON_DELTA;
+ angle = NORMALIZE_ANGLE (LOWORD (RandomContext_Random (SysGenRNG)));
+ solarSys->MoonDesc[0].location.x =
+ COSINE (angle, solarSys->MoonDesc[0].radius);
+ solarSys->MoonDesc[0].location.y =
+ SINE (angle, solarSys->MoonDesc[0].radius);
+ }
+
+ return true;
+}
+
+static bool
+GenerateSpathi_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ DWORD rand_val;
+
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ /* Spathiwa's moon */
+ if (!GET_GAME_STATE (SPATHI_SHIELDED_SELVES)
+ && StartSphereTracking (SPATHI_SHIP))
+ {
+ NotifyOthers (SPATHI_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (SPATHI_SHIP, &GLOBAL (npc_built_ship_q),
+ INFINITE_FLEET);
+
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ InitCommunication (SPATHI_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+ return true;
+ }
+
+ DoPlanetaryAnalysis (&solarSys->SysInfo, world);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = rand_val;
+ GenerateLifeForms (&solarSys->SysInfo, GENERATE_ALL, NULL);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val;
+ GenerateMineralDeposits (&solarSys->SysInfo, GENERATE_ALL, NULL);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[ENERGY_SCAN] = rand_val;
+
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 28;
+ if (!GET_GAME_STATE (UMGAH_BROADCASTERS))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (UMGAH_BCS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (UMGAH_BCS_STRTAB));
+ if (!GET_GAME_STATE (SPATHI_SHIELDED_SELVES))
+ { // The first report talks extensively about Spathi
+ // slave-shielding selves. If they never did so, the report
+ // makes no sense, so use an alternate.
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ SetAbsStringTableIndex (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString, 1);
+ }
+ }
+ LoadPlanet (NULL);
+ return true;
+ }
+ else if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ /* visiting Spathiwa */
+ DoPlanetaryAnalysis (&solarSys->SysInfo, world);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[MINERAL_SCAN] = rand_val;
+ GenerateMineralDeposits (&solarSys->SysInfo, GENERATE_ALL, NULL);
+ rand_val = RandomContext_GetSeed (SysGenRNG);
+
+ solarSys->SysInfo.PlanetInfo.ScanSeed[BIOLOGICAL_SCAN] = rand_val;
+
+ solarSys->SysInfo.PlanetInfo.PlanetRadius = 120;
+ solarSys->SysInfo.PlanetInfo.SurfaceGravity =
+ CalcGravity (&solarSys->SysInfo.PlanetInfo);
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 31;
+
+ LoadPlanet (NULL);
+ return true;
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ return true;
+}
+
+static COUNT
+GenerateSpathi_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ if (GET_GAME_STATE (UMGAH_BROADCASTERS))
+ { // already picked up
+ return 0;
+ }
+
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateSpathi_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ assert (!GET_GAME_STATE (UMGAH_BROADCASTERS) && whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (UMGAH_BROADCASTERS, 1);
+ SET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP, 1);
+
+ return true; // picked up
+ }
+
+ (void) whichNode;
+ return false;
+}
+
+static COUNT
+GenerateSpathi_generateLife (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ #define NUM_EVIL_ONES 32
+ return GenerateRandomNodes (&solarSys->SysInfo, BIOLOGICAL_SCAN, NUM_EVIL_ONES,
+ NUM_CREATURE_TYPES, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateSpathi_pickupLife (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ assert (!GET_GAME_STATE (SPATHI_CREATURES_ELIMINATED) &&
+ !GET_GAME_STATE (SPATHI_SHIELDED_SELVES));
+
+ SET_GAME_STATE (SPATHI_CREATURES_EXAMINED, 1);
+ if (countNodesRetrieved (&solarSys->SysInfo.PlanetInfo, BIOLOGICAL_SCAN)
+ + 1 == NUM_EVIL_ONES)
+ { // last creature picked up
+ SET_GAME_STATE (SPATHI_CREATURES_ELIMINATED, 1);
+ }
+
+ return true; // picked up
+ }
+
+ return GenerateDefault_pickupLife (solarSys, world, whichNode);
+}
diff --git a/src/uqm/planets/generate/gensup.c b/src/uqm/planets/generate/gensup.c
new file mode 100644
index 0000000..a618c89
--- /dev/null
+++ b/src/uqm/planets/generate/gensup.c
@@ -0,0 +1,159 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateSupox_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateSupox_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateSupox_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateSupox_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateSupoxFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateSupox_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateSupox_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateSupox_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateSupox_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateSupox_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = WATER_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 2;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 152L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GenerateSupox_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (StartSphereTracking (SUPOX_SHIP))
+ {
+ NotifyOthers (SUPOX_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (SUPOX_SHIP, &GLOBAL (npc_built_ship_q),
+ INFINITE_FLEET);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ InitCommunication (SUPOX_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+ return true;
+ }
+ else
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (SUPOX_RUINS_STRTAB));
+ if (GET_GAME_STATE (ULTRON_CONDITION))
+ { // Already picked up the Ultron, skip the report
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ SetAbsStringTableIndex (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString, 1);
+ }
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ return true;
+}
+
+static bool
+GenerateSupox_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ GenerateDefault_landerReportCycle (solarSys);
+
+ // The artifact can be picked up from any ruin
+ if (!GET_GAME_STATE (ULTRON_CONDITION))
+ { // Just picked up the Ultron from a ruin
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (ULTRON_CONDITION, 1);
+ }
+
+ return false; // do not remove the node
+ }
+
+ (void) whichNode;
+ return false;
+}
+
+static COUNT
+GenerateSupox_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
diff --git a/src/uqm/planets/generate/gensyr.c b/src/uqm/planets/generate/gensyr.c
new file mode 100644
index 0000000..ebf3be4
--- /dev/null
+++ b/src/uqm/planets/generate/gensyr.c
@@ -0,0 +1,102 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../comm.h"
+
+static bool GenerateSyreen_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateSyreen_generateMoons (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *planet);
+static bool GenerateSyreen_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+
+
+const GenerateFunctions generateSyreenFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateSyreen_generatePlanets,
+ /* .generateMoons = */ GenerateSyreen_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateSyreen_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateSyreen_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = WATER_WORLD | PLANET_SHIELDED;
+ solarSys->PlanetDesc[0].NumPlanets = 1;
+
+ return true;
+}
+
+static bool
+GenerateSyreen_generateMoons (SOLARSYS_STATE *solarSys, PLANET_DESC *planet)
+{
+ GenerateDefault_generateMoons (solarSys, planet);
+
+ if (matchWorld (solarSys, planet, 0, MATCH_PLANET))
+ {
+ solarSys->MoonDesc[0].data_index = HIERARCHY_STARBASE;
+ solarSys->MoonDesc[0].radius = MIN_MOON_RADIUS;
+ solarSys->MoonDesc[0].location.x =
+ COSINE (QUADRANT, solarSys->MoonDesc[0].radius);
+ solarSys->MoonDesc[0].location.y =
+ SINE (QUADRANT, solarSys->MoonDesc[0].radius);
+ }
+
+ return true;
+}
+
+static bool
+GenerateSyreen_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ /* Syreen home planet */
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 19;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ solarSys->SysInfo.PlanetInfo.Weather = 0;
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = EARTH_ATMOSPHERE * 9 / 10;
+ return true;
+ }
+
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ /* Starbase */
+ InitCommunication (SYREEN_CONVERSATION);
+ return true;
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ return true;
+}
+
diff --git a/src/uqm/planets/generate/genthrad.c b/src/uqm/planets/generate/genthrad.c
new file mode 100644
index 0000000..875e582
--- /dev/null
+++ b/src/uqm/planets/generate/genthrad.c
@@ -0,0 +1,217 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../gendef.h"
+#include "../../starmap.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../setup.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateThraddash_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateThraddash_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateThraddash_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateThraddash_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateThraddashFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateThraddash_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateThraddash_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateThraddash_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateThraddash_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateThraddash_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ if (CurStarDescPtr->Index == AQUA_HELIX_DEFINED)
+ {
+ solarSys->PlanetDesc[0].data_index = PRIMORDIAL_WORLD;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 65L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+ }
+ else /* CurStarDescPtr->Index == THRADD_DEFINED */
+ {
+ solarSys->PlanetDesc[0].data_index = WATER_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 0;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 98L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+ }
+ return true;
+}
+
+static bool
+GenerateThraddash_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (StartSphereTracking (THRADDASH_SHIP)
+ && (CurStarDescPtr->Index == THRADD_DEFINED
+ || (!GET_GAME_STATE (HELIX_UNPROTECTED)
+ && (BYTE)(GET_GAME_STATE (THRADD_MISSION) - 1) >= 3)))
+ {
+ NotifyOthers (THRADDASH_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (THRADDASH_SHIP, &GLOBAL (npc_built_ship_q),
+ INFINITE_FLEET);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ if (CurStarDescPtr->Index == THRADD_DEFINED)
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ }
+ else
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6);
+ }
+ InitCommunication (THRADD_CONVERSATION);
+
+ if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ return true;
+
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+
+ if (CurStarDescPtr->Index == THRADD_DEFINED
+ || (!GET_GAME_STATE (HELIX_UNPROTECTED)
+ && (BYTE)(GET_GAME_STATE (THRADD_MISSION) - 1) >= 3))
+ return true;
+
+ RepairSISBorder ();
+ }
+
+ if (CurStarDescPtr->Index == AQUA_HELIX_DEFINED
+ && !GET_GAME_STATE (AQUA_HELIX))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (AQUA_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (AQUA_STRTAB));
+ }
+ else if (CurStarDescPtr->Index == THRADD_DEFINED)
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (RUINS_STRTAB));
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
+static COUNT
+GenerateThraddash_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (CurStarDescPtr->Index == THRADD_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ if (CurStarDescPtr->Index == AQUA_HELIX_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ if (GET_GAME_STATE (AQUA_HELIX))
+ { // already picked up
+ return 0;
+ }
+
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateThraddash_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (CurStarDescPtr->Index == THRADD_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ if (CurStarDescPtr->Index == AQUA_HELIX_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ assert (!GET_GAME_STATE (AQUA_HELIX) && whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (HELIX_VISITS, 0);
+ SET_GAME_STATE (AQUA_HELIX, 1);
+ SET_GAME_STATE (AQUA_HELIX_ON_SHIP, 1);
+ SET_GAME_STATE (HELIX_UNPROTECTED, 1);
+
+ return true; // picked up
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/generate/gentrap.c b/src/uqm/planets/generate/gentrap.c
new file mode 100644
index 0000000..e8451cd
--- /dev/null
+++ b/src/uqm/planets/generate/gentrap.c
@@ -0,0 +1,80 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+
+
+static bool GenerateTrap_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateTrap_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+
+
+const GenerateFunctions generateTrapFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateTrap_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateTrap_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateTrap_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = TELLURIC_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 1;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 203L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GenerateTrap_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ solarSys->SysInfo.PlanetInfo.AtmoDensity = EARTH_ATMOSPHERE * 2;
+ solarSys->SysInfo.PlanetInfo.SurfaceTemperature = 35;
+ solarSys->SysInfo.PlanetInfo.Weather = 3;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ }
+
+ return true;
+}
+
diff --git a/src/uqm/planets/generate/genutw.c b/src/uqm/planets/generate/genutw.c
new file mode 100644
index 0000000..71ac2aa
--- /dev/null
+++ b/src/uqm/planets/generate/genutw.c
@@ -0,0 +1,269 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../gendef.h"
+#include "../../starmap.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../setup.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateUtwig_initNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateUtwig_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateUtwig_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateUtwig_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateUtwig_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateUtwigFunctions = {
+ /* .initNpcs = */ GenerateUtwig_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateUtwig_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateUtwig_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateUtwig_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateUtwig_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateUtwig_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ if (CurStarDescPtr->Index == BOMB_DEFINED
+ && !GET_GAME_STATE (UTWIG_BOMB))
+ {
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+ }
+ else
+ {
+ GenerateDefault_initNpcs (solarSys);
+ }
+
+ return true;
+}
+
+static bool
+GenerateUtwig_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ if (CurStarDescPtr->Index == UTWIG_DEFINED)
+ {
+ solarSys->PlanetDesc[0].data_index = WATER_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 1;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 174L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+ }
+
+ return true;
+}
+
+static bool
+GenerateUtwig_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if ((CurStarDescPtr->Index == UTWIG_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ || (CurStarDescPtr->Index == BOMB_DEFINED
+ && matchWorld (solarSys, world, 5, 1)
+ && !GET_GAME_STATE (UTWIG_BOMB)))
+ {
+ if ((CurStarDescPtr->Index == UTWIG_DEFINED
+ || !GET_GAME_STATE (UTWIG_HAVE_ULTRON))
+ && StartSphereTracking (UTWIG_SHIP))
+ {
+ NotifyOthers (UTWIG_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (UTWIG_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ if (CurStarDescPtr->Index == UTWIG_DEFINED)
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ }
+ else
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6);
+ }
+ InitCommunication (UTWIG_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+ return true;
+ }
+
+ if (CurStarDescPtr->Index == BOMB_DEFINED
+ && !GET_GAME_STATE (BOMB_UNPROTECTED)
+ && StartSphereTracking (DRUUGE_SHIP))
+ {
+ COUNT i;
+
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ for (i = 0; i < 5; ++i)
+ {
+ CloneShipFragment (DRUUGE_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ }
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6);
+ InitCommunication (DRUUGE_CONVERSATION);
+
+ if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ return true;
+
+ {
+ BOOLEAN DruugeSurvivors;
+
+ DruugeSurvivors =
+ GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0;
+
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+
+ if (DruugeSurvivors)
+ return true;
+
+ RepairSISBorder ();
+ SET_GAME_STATE (BOMB_UNPROTECTED, 1);
+ }
+ }
+
+ if (CurStarDescPtr->Index == BOMB_DEFINED)
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (BOMB_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (BOMB_STRTAB));
+ }
+ else
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (RUINS_STRTAB));
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ if (CurStarDescPtr->Index == UTWIG_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ solarSys->SysInfo.PlanetInfo.Weather = 1;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 1;
+ }
+
+ return true;
+}
+
+static COUNT
+GenerateUtwig_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (CurStarDescPtr->Index == UTWIG_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ if (CurStarDescPtr->Index == BOMB_DEFINED
+ && matchWorld (solarSys, world, 5, 1))
+ {
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ if (GET_GAME_STATE (UTWIG_BOMB))
+ { // already picked up
+ return 0;
+ }
+
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateUtwig_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (CurStarDescPtr->Index == UTWIG_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ if (CurStarDescPtr->Index == BOMB_DEFINED
+ && matchWorld (solarSys, world, 5, 1))
+ {
+ assert (!GET_GAME_STATE (UTWIG_BOMB) && whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (UTWIG_BOMB, 1);
+ SET_GAME_STATE (UTWIG_BOMB_ON_SHIP, 1);
+ SET_GAME_STATE (DRUUGE_MANNER, 1);
+ SET_GAME_STATE (DRUUGE_VISITS, 0);
+ SET_GAME_STATE (DRUUGE_HOME_VISITS, 0);
+
+ return true; // picked up
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/generate/genvault.c b/src/uqm/planets/generate/genvault.c
new file mode 100644
index 0000000..e189897
--- /dev/null
+++ b/src/uqm/planets/generate/genvault.c
@@ -0,0 +1,130 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../globdata.h"
+#include "../../nameref.h"
+#include "../../resinst.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateVault_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateVault_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateVault_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateVaultFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateDefault_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateVault_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateVault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateVault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateVault_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (VAULT_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (VAULT_STRTAB));
+ if (GET_GAME_STATE (SHIP_VAULT_UNLOCKED))
+ {
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ SetAbsStringTableIndex (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString, 2);
+ }
+ else if (GET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP))
+ {
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ SetAbsStringTableIndex (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString, 1);
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
+static COUNT
+GenerateVault_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateVault_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, 0))
+ {
+ assert (whichNode == 0);
+
+ if (GET_GAME_STATE (SHIP_VAULT_UNLOCKED))
+ { // Give the final report, "omg empty" and whatnot
+ GenerateDefault_landerReportCycle (solarSys);
+ }
+ else if (GET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP))
+ {
+ GenerateDefault_landerReportCycle (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (SHIP_VAULT_UNLOCKED, 1);
+ SET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP, 0);
+ SET_GAME_STATE (SYREEN_HOME_VISITS, 0);
+ }
+ else
+ {
+ GenerateDefault_landerReport (solarSys);
+
+ if (!GET_GAME_STATE (KNOW_SYREEN_VAULT))
+ {
+ SET_GAME_STATE (KNOW_SYREEN_VAULT, 1);
+ }
+ }
+
+ // The Vault cannot be "picked up". It is always on the surface.
+ return false;
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/generate/genvux.c b/src/uqm/planets/generate/genvux.c
new file mode 100644
index 0000000..d4a0642
--- /dev/null
+++ b/src/uqm/planets/generate/genvux.c
@@ -0,0 +1,329 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../lifeform.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../gendef.h"
+#include "../../starmap.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../setup.h"
+#include "../../sounds.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateVux_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateVux_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateVux_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static COUNT GenerateVux_generateLife (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateVux_pickupEnergy (SOLARSYS_STATE *, PLANET_DESC *world,
+ COUNT whichNode);
+static bool GenerateVux_pickupLife (SOLARSYS_STATE *, PLANET_DESC *world,
+ COUNT whichNode);
+
+
+const GenerateFunctions generateVuxFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateVux_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateVux_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateVux_generateEnergy,
+ /* .generateLife = */ GenerateVux_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateVux_pickupEnergy,
+ /* .pickupLife = */ GenerateVux_pickupLife,
+};
+
+
+static bool
+GenerateVux_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ if (CurStarDescPtr->Index == MAIDENS_DEFINED)
+ {
+ GenerateDefault_generatePlanets (solarSys);
+ // XXX: this is the second time that this function is
+ // called. Is it safe to remove one, or does this change
+ // the RNG so that the outcome is different?
+ solarSys->PlanetDesc[0].data_index = REDUX_WORLD;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 212L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+ }
+ else
+ {
+ if (CurStarDescPtr->Index == VUX_DEFINED)
+ {
+ solarSys->PlanetDesc[0].data_index = REDUX_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 1;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 42L / 100;
+ angle = HALF_CIRCLE + OCTANT;
+ }
+ else /* if (CurStarDescPtr->Index == VUX_BEAST_DEFINED) */
+ {
+ memmove (&solarSys->PlanetDesc[1], &solarSys->PlanetDesc[0],
+ sizeof (solarSys->PlanetDesc[0])
+ * solarSys->SunDesc[0].NumPlanets);
+ ++solarSys->SunDesc[0].NumPlanets;
+
+ angle = HALF_CIRCLE - OCTANT;
+ solarSys->PlanetDesc[0].data_index = WATER_WORLD;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 110L / 100;
+ solarSys->PlanetDesc[0].NumPlanets = 0;
+ }
+
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].rand_seed = MAKE_DWORD (
+ solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ }
+ return true;
+}
+
+static bool
+GenerateVux_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if ((matchWorld (solarSys, world, 0, MATCH_PLANET)
+ && (CurStarDescPtr->Index == VUX_DEFINED
+ || (CurStarDescPtr->Index == MAIDENS_DEFINED
+ && !GET_GAME_STATE (ZEX_IS_DEAD))))
+ && StartSphereTracking (VUX_SHIP))
+ {
+ NotifyOthers (VUX_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (VUX_SHIP,
+ &GLOBAL (npc_built_ship_q), INFINITE_FLEET);
+ if (CurStarDescPtr->Index == VUX_DEFINED)
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ }
+ else
+ {
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 6);
+ }
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ InitCommunication (VUX_CONVERSATION);
+
+ if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ return true;
+
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+
+ if (CurStarDescPtr->Index == VUX_DEFINED
+ || !GET_GAME_STATE (ZEX_IS_DEAD))
+ return true;
+
+ RepairSISBorder ();
+ }
+ }
+
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (CurStarDescPtr->Index == MAIDENS_DEFINED)
+ {
+ if (!GET_GAME_STATE (SHOFIXTI_MAIDENS))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] = CaptureDrawable (
+ LoadGraphic (MAIDENS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (MAIDENS_STRTAB));
+ }
+ }
+ else if (CurStarDescPtr->Index == VUX_BEAST_DEFINED)
+ {
+ if (!GET_GAME_STATE (VUX_BEAST))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] = 0;
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (
+ LoadStringTable (BEAST_STRTAB));
+ }
+ }
+ else
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (RUINS_STRTAB));
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ solarSys->SysInfo.PlanetInfo.Weather = 2;
+ solarSys->SysInfo.PlanetInfo.Tectonics = 0;
+ }
+
+ return true;
+}
+
+static COUNT
+GenerateVux_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (CurStarDescPtr->Index == MAIDENS_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // This check is redundant since the retrieval bit will keep the
+ // node from showing up again
+ if (GET_GAME_STATE (SHOFIXTI_MAIDENS))
+ { // already picked up
+ return 0;
+ }
+
+ if (info)
+ {
+ info->loc_pt.x = MAP_WIDTH / 3;
+ info->loc_pt.y = MAP_HEIGHT * 5 / 8;
+ }
+
+ return 1; // only matters when count is requested
+ }
+
+ if (CurStarDescPtr->Index == VUX_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateVux_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (CurStarDescPtr->Index == MAIDENS_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ assert (!GET_GAME_STATE (SHOFIXTI_MAIDENS) && whichNode == 0);
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (SHOFIXTI_MAIDENS, 1);
+ SET_GAME_STATE (MAIDENS_ON_SHIP, 1);
+
+ return true; // picked up
+ }
+
+ if (CurStarDescPtr->Index == VUX_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ (void) whichNode;
+ return false;
+}
+
+static COUNT
+GenerateVux_generateLife (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (CurStarDescPtr->Index == MAIDENS_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ static const SBYTE life[] =
+ {
+ 9, 9, 9, 9, /* Carousel Beast */
+ 14, 14, 14, 14, /* Amorphous Trandicula */
+ 18, 18, 18, 18, /* Penguin Cyclops */
+ -1 /* term */
+ };
+ return GeneratePresetLife (&solarSys->SysInfo, life, whichNode, info);
+ }
+
+ if (CurStarDescPtr->Index == VUX_BEAST_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ static const SBYTE life[] =
+ {
+ NUM_CREATURE_TYPES + 2, /* VUX Beast */
+ // Must be the first node, see pickupLife() below
+ 3, 3, 3, 3, 3, /* Whackin' Bush */
+ 8, 8, 8, 8, 8, /* Glowing Medusa */
+ -1 /* term */
+ };
+ return GeneratePresetLife (&solarSys->SysInfo, life, whichNode, info);
+ }
+
+ return GenerateDefault_generateLife (solarSys, world, whichNode, info);
+}
+
+static bool
+GenerateVux_pickupLife (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (CurStarDescPtr->Index == VUX_BEAST_DEFINED
+ && matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (whichNode == 0)
+ { // Picked up Zex' Beauty
+ assert (!GET_GAME_STATE (VUX_BEAST));
+
+ GenerateDefault_landerReport (solarSys);
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (VUX_BEAST, 1);
+ SET_GAME_STATE (VUX_BEAST_ON_SHIP, 1);
+ }
+
+ return true; // picked up
+ }
+
+ return GenerateDefault_pickupLife (solarSys, world, whichNode);
+}
diff --git a/src/uqm/planets/generate/genwreck.c b/src/uqm/planets/generate/genwreck.c
new file mode 100644
index 0000000..05e956e
--- /dev/null
+++ b/src/uqm/planets/generate/genwreck.c
@@ -0,0 +1,111 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../lander.h"
+#include "../planets.h"
+#include "../../globdata.h"
+#include "../../nameref.h"
+#include "../../resinst.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateWreck_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateWreck_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateWreck_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateWreckFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateDefault_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateWreck_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateWreck_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateWreck_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateWreck_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 6, MATCH_PLANET))
+ {
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (WRECK_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (WRECK_STRTAB));
+ if (GET_GAME_STATE (PORTAL_KEY))
+ { // Already picked it up, skip the first report
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ SetAbsStringTableIndex (
+ solarSys->SysInfo.PlanetInfo.DiscoveryString, 1);
+ }
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
+static COUNT
+GenerateWreck_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 6, MATCH_PLANET))
+ {
+ return GenerateDefault_generateArtifact (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateWreck_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 6, MATCH_PLANET))
+ {
+ assert (whichNode == 0);
+
+ GenerateDefault_landerReportCycle (solarSys);
+
+ if (!GET_GAME_STATE (PORTAL_KEY))
+ {
+ SetLanderTakeoff ();
+
+ SET_GAME_STATE (PORTAL_KEY, 1);
+ SET_GAME_STATE (PORTAL_KEY_ON_SHIP, 1);
+ }
+
+ // The Wreck cannot be "picked up". It is always on the surface.
+ return false;
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/generate/genyeh.c b/src/uqm/planets/generate/genyeh.c
new file mode 100644
index 0000000..caae543
--- /dev/null
+++ b/src/uqm/planets/generate/genyeh.c
@@ -0,0 +1,140 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../globdata.h"
+#include "../../ipdisp.h"
+#include "../../nameref.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateYehat_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateYehat_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateYehat_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateYehat_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateYehatFunctions = {
+ /* .initNpcs = */ GenerateDefault_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateYehat_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateYehat_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateYehat_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateYehat_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateYehat_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+ solarSys->PlanetDesc[0].data_index = WATER_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 1;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 106L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GenerateYehat_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (StartSphereTracking (YEHAT_SHIP))
+ {
+ NotifyOthers (YEHAT_SHIP, IPNL_ALL_CLEAR);
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ CloneShipFragment (YEHAT_SHIP, &GLOBAL (npc_built_ship_q),
+ INFINITE_FLEET);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ InitCommunication (YEHAT_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+
+ return true;
+ }
+
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (RUINS_STRTAB));
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
+static COUNT
+GenerateYehat_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateYehat_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/generate/genzfpscout.c b/src/uqm/planets/generate/genzfpscout.c
new file mode 100644
index 0000000..93a6d5d
--- /dev/null
+++ b/src/uqm/planets/generate/genzfpscout.c
@@ -0,0 +1,96 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../../build.h"
+#include "../../globdata.h"
+#include "../../grpinfo.h"
+#include "../../state.h"
+
+
+static bool GenerateZoqFotPikScout_initNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateZoqFotPikScout_reinitNpcs (SOLARSYS_STATE *solarSys);
+
+
+const GenerateFunctions generateZoqFotPikScoutFunctions = {
+ /* .initNpcs = */ GenerateZoqFotPikScout_initNpcs,
+ /* .reinitNpcs = */ GenerateZoqFotPikScout_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateDefault_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateDefault_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateDefault_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateDefault_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateZoqFotPikScout_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ if (!GET_GAME_STATE (MET_ZOQFOT))
+ {
+ GLOBAL (BattleGroupRef) = GET_GAME_STATE_32 (ZOQFOT_GRPOFFS0);
+ if (GLOBAL (BattleGroupRef) == 0)
+ {
+ CloneShipFragment (ZOQFOTPIK_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+ GLOBAL (BattleGroupRef) = PutGroupInfo (GROUPS_ADD_NEW, 1);
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ SET_GAME_STATE_32 (ZOQFOT_GRPOFFS0, GLOBAL (BattleGroupRef));
+ }
+ }
+
+ GenerateDefault_initNpcs (solarSys);
+
+ return true;
+}
+
+static bool
+GenerateZoqFotPikScout_reinitNpcs (SOLARSYS_STATE *solarSys)
+{
+ HIPGROUP hGroup;
+ IP_GROUP *GroupPtr;
+
+ GenerateDefault_reinitNpcs (solarSys);
+
+ if (!GLOBAL (BattleGroupRef))
+ return true; // nothing to check
+
+ hGroup = GetHeadLink (&GLOBAL (ip_group_q));
+ if (!hGroup)
+ return true; // still nothing to check
+
+ GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
+ // REFORM_GROUP was set in ipdisp.c:ip_group_collision()
+ // during a collision with the flagship.
+ if (GroupPtr->race_id == ZOQFOTPIK_SHIP
+ && (GroupPtr->task & REFORM_GROUP))
+ {
+ GroupPtr->task = FLEE | IGNORE_FLAGSHIP | REFORM_GROUP;
+ GroupPtr->dest_loc = 0;
+ }
+ UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
+
+ return true;
+}
+
diff --git a/src/uqm/planets/generate/genzoq.c b/src/uqm/planets/generate/genzoq.c
new file mode 100644
index 0000000..9b30f89
--- /dev/null
+++ b/src/uqm/planets/generate/genzoq.c
@@ -0,0 +1,170 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "genall.h"
+#include "../planets.h"
+#include "../../build.h"
+#include "../../comm.h"
+#include "../../globdata.h"
+#include "../../nameref.h"
+#include "../../state.h"
+#include "libs/mathlib.h"
+
+
+static bool GenerateZoqFotPik_initNpcs (SOLARSYS_STATE *solarSys);
+static bool GenerateZoqFotPik_generatePlanets (SOLARSYS_STATE *solarSys);
+static bool GenerateZoqFotPik_generateOrbital (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world);
+static COUNT GenerateZoqFotPik_generateEnergy (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *);
+static bool GenerateZoqFotPik_pickupEnergy (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT whichNode);
+
+
+const GenerateFunctions generateZoqFotPikFunctions = {
+ /* .initNpcs = */ GenerateZoqFotPik_initNpcs,
+ /* .reinitNpcs = */ GenerateDefault_reinitNpcs,
+ /* .uninitNpcs = */ GenerateDefault_uninitNpcs,
+ /* .generatePlanets = */ GenerateZoqFotPik_generatePlanets,
+ /* .generateMoons = */ GenerateDefault_generateMoons,
+ /* .generateName = */ GenerateDefault_generateName,
+ /* .generateOrbital = */ GenerateZoqFotPik_generateOrbital,
+ /* .generateMinerals = */ GenerateDefault_generateMinerals,
+ /* .generateEnergy = */ GenerateZoqFotPik_generateEnergy,
+ /* .generateLife = */ GenerateDefault_generateLife,
+ /* .pickupMinerals = */ GenerateDefault_pickupMinerals,
+ /* .pickupEnergy = */ GenerateZoqFotPik_pickupEnergy,
+ /* .pickupLife = */ GenerateDefault_pickupLife,
+};
+
+
+static bool
+GenerateZoqFotPik_initNpcs (SOLARSYS_STATE *solarSys)
+{
+ if (GET_GAME_STATE (ZOQFOT_DISTRESS) != 1)
+ GenerateDefault_initNpcs (solarSys);
+
+ return true;
+}
+
+static bool
+GenerateZoqFotPik_generatePlanets (SOLARSYS_STATE *solarSys)
+{
+ COUNT angle;
+
+ GenerateDefault_generatePlanets (solarSys);
+
+ solarSys->PlanetDesc[0].data_index = REDUX_WORLD;
+ solarSys->PlanetDesc[0].NumPlanets = 1;
+ solarSys->PlanetDesc[0].radius = EARTH_RADIUS * 138L / 100;
+ angle = ARCTAN (solarSys->PlanetDesc[0].location.x,
+ solarSys->PlanetDesc[0].location.y);
+ solarSys->PlanetDesc[0].location.x =
+ COSINE (angle, solarSys->PlanetDesc[0].radius);
+ solarSys->PlanetDesc[0].location.y =
+ SINE (angle, solarSys->PlanetDesc[0].radius);
+
+ return true;
+}
+
+static bool
+GenerateZoqFotPik_generateOrbital (SOLARSYS_STATE *solarSys, PLANET_DESC *world)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ if (StartSphereTracking (ZOQFOTPIK_SHIP))
+ {
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ if (GET_GAME_STATE (ZOQFOT_DISTRESS))
+ {
+ CloneShipFragment (BLACK_URQUAN_SHIP,
+ &GLOBAL (npc_built_ship_q), 0);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ InitCommunication (BLACKURQ_CONVERSATION);
+
+ if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ return true;
+
+ if (GetHeadLink (&GLOBAL (npc_built_ship_q)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ return true;
+ }
+ }
+
+ CloneShipFragment (ZOQFOTPIK_SHIP, &GLOBAL (npc_built_ship_q),
+ INFINITE_FLEET);
+
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 1 << 7);
+ InitCommunication (ZOQFOTPIK_CONVERSATION);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_INTERPLANETARY;
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ GetGroupInfo (GROUPS_RANDOM, GROUP_LOAD_IP);
+ }
+
+ return true;
+ }
+
+ LoadStdLanderFont (&solarSys->SysInfo.PlanetInfo);
+ solarSys->PlanetSideFrame[1] =
+ CaptureDrawable (LoadGraphic (RUINS_MASK_PMAP_ANIM));
+ solarSys->SysInfo.PlanetInfo.DiscoveryString =
+ CaptureStringTable (LoadStringTable (RUINS_STRTAB));
+ }
+
+ GenerateDefault_generateOrbital (solarSys, world);
+ return true;
+}
+
+static COUNT
+GenerateZoqFotPik_generateEnergy (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT whichNode, NODE_INFO *info)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ return GenerateDefault_generateRuins (solarSys, whichNode, info);
+ }
+
+ return 0;
+}
+
+static bool
+GenerateZoqFotPik_pickupEnergy (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT whichNode)
+{
+ if (matchWorld (solarSys, world, 0, MATCH_PLANET))
+ {
+ // Standard ruins report
+ GenerateDefault_landerReportCycle (solarSys);
+ return false;
+ }
+
+ (void) whichNode;
+ return false;
+}
diff --git a/src/uqm/planets/gentopo.c b/src/uqm/planets/gentopo.c
new file mode 100644
index 0000000..5212143
--- /dev/null
+++ b/src/uqm/planets/gentopo.c
@@ -0,0 +1,206 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+// See doc/devel/planettopo for details.
+
+#include "libs/gfxlib.h"
+#include "libs/mathlib.h"
+#include "planets.h"
+
+void
+DeltaTopography (COUNT num_iterations, SBYTE *DepthArray, RECT *pRect,
+ SIZE depth_delta)
+{
+ SIZE width, height, delta_y;
+ struct
+ {
+ COORD x_top, x_bot;
+ SIZE x_incr, delta_x, error_term;
+ } LineDDA0, LineDDA1;
+
+ width = pRect->extent.width;
+ height = pRect->extent.height;
+ delta_y = (height - 1) << 1;
+ do
+ {
+ SIZE d;
+ COUNT h, w1, w2;
+ DWORD rand_val;
+ SBYTE *lpDst;
+
+ if ((RandomContext_Random (SysGenRNG) & 1) == 0)
+ depth_delta = -depth_delta;
+
+ rand_val = RandomContext_Random (SysGenRNG);
+ w1 = LOWORD (rand_val);
+ w2 = HIWORD (rand_val);
+
+ LineDDA0.x_top = LOBYTE (w1) % width;
+ LineDDA0.x_bot = HIBYTE (w1) % width;
+ LineDDA0.delta_x = (LineDDA0.x_bot - LineDDA0.x_top) << 1;
+ if (LineDDA0.delta_x >= 0)
+ LineDDA0.x_incr = 1;
+ else
+ {
+ LineDDA0.x_incr = -1;
+ LineDDA0.delta_x = -LineDDA0.delta_x;
+ }
+ if (LineDDA0.delta_x > delta_y)
+ LineDDA0.error_term = -(LineDDA0.delta_x >> 1);
+ else
+ LineDDA0.error_term = -(delta_y >> 1);
+
+ LineDDA1.x_top = (LOBYTE (w2) % (width - 1)) + LineDDA0.x_top + 1;
+ LineDDA1.x_bot = (HIBYTE (w2) % (width - 1)) + LineDDA0.x_bot + 1;
+ LineDDA1.delta_x = (LineDDA1.x_bot - LineDDA1.x_top) << 1;
+ if (LineDDA1.delta_x >= 0)
+ LineDDA1.x_incr = 1;
+ else
+ {
+ LineDDA1.x_incr = -1;
+ LineDDA1.delta_x = -LineDDA1.delta_x;
+ }
+ if (LineDDA1.delta_x > delta_y)
+ LineDDA1.error_term = -(LineDDA1.delta_x >> 1);
+ else
+ LineDDA1.error_term = -(delta_y >> 1);
+
+ lpDst = &DepthArray[LineDDA0.x_top];
+ h = height;
+ do
+ {
+ COUNT w;
+
+ w1 = LineDDA1.x_top - LineDDA0.x_top;
+ w2 = width - w1;
+
+ if ((int)(LineDDA0.x_top + w1) > (int)width)
+ w = width - LineDDA0.x_top;
+ else
+ {
+ w = w1;
+ LineDDA0.x_top += w1;
+ }
+ w1 -= w;
+ while (w--)
+ {
+ d = *lpDst + depth_delta;
+ if (d >= -128 && d <= 127)
+ *lpDst = (SBYTE)d;
+ ++lpDst;
+ }
+ if (w1 == 0)
+ {
+ if (LineDDA0.x_top == width)
+ {
+ LineDDA0.x_top = 0;
+ lpDst -= width;
+ }
+ }
+ else
+ {
+ LineDDA0.x_top = w1;
+ lpDst -= width;
+ do
+ {
+ d = *lpDst + depth_delta;
+ if (d >= -128 && d <= 127)
+ *lpDst = (SBYTE)d;
+ ++lpDst;
+ } while (--w1);
+ }
+
+ if ((int)(LineDDA0.x_top + w2) > (int)width)
+ w = width - LineDDA0.x_top;
+ else
+ {
+ w = w2;
+ LineDDA0.x_top += w2;
+ }
+ w2 -= w;
+ while (w--)
+ {
+ d = *lpDst - depth_delta;
+ if (d >= -128 && d <= 127)
+ *lpDst = (SBYTE)d;
+ ++lpDst;
+ }
+ if (w2 == 0)
+ {
+ if (LineDDA0.x_top == width)
+ {
+ LineDDA0.x_top = 0;
+ lpDst -= width;
+ }
+ }
+ else
+ {
+ LineDDA0.x_top = w2;
+ lpDst -= width;
+ do
+ {
+ d = *lpDst - depth_delta;
+ if (d >= -128 && d <= 127)
+ *lpDst = (SBYTE)d;
+ ++lpDst;
+ } while (--w2);
+ }
+
+ lpDst += pRect->extent.width;
+
+ if (delta_y >= LineDDA0.delta_x)
+ {
+ if ((LineDDA0.error_term += LineDDA0.delta_x) >= 0)
+ {
+ lpDst += LineDDA0.x_incr;
+ LineDDA0.x_top += LineDDA0.x_incr;
+ LineDDA0.error_term -= delta_y;
+ }
+ }
+ else
+ {
+ do
+ {
+ lpDst += LineDDA0.x_incr;
+ LineDDA0.x_top += LineDDA0.x_incr;
+ } while ((LineDDA0.error_term += delta_y) < 0);
+ LineDDA0.error_term -= LineDDA0.delta_x;
+ }
+
+ if (delta_y >= LineDDA1.delta_x)
+ {
+ if ((LineDDA1.error_term += LineDDA1.delta_x) >= 0)
+ {
+ LineDDA1.x_top += LineDDA1.x_incr;
+ LineDDA1.error_term -= delta_y;
+ }
+ }
+ else
+ {
+ do
+ {
+ LineDDA1.x_top += LineDDA1.x_incr;
+ } while ((LineDDA1.error_term += delta_y) < 0);
+ LineDDA1.error_term -= LineDDA1.delta_x;
+ }
+ } while (--h);
+ } while (--num_iterations);
+}
+
+
+
diff --git a/src/uqm/planets/lander.c b/src/uqm/planets/lander.c
new file mode 100644
index 0000000..17aad8f
--- /dev/null
+++ b/src/uqm/planets/lander.c
@@ -0,0 +1,2101 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "lander.h"
+
+#include "lifeform.h"
+#include "scan.h"
+#include "../cons_res.h"
+#include "../controls.h"
+#include "../colors.h"
+#include "../process.h"
+#include "../units.h"
+#include "../gamestr.h"
+#include "../nameref.h"
+#include "../resinst.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "../element.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+
+
+//define SPIN_ON_LAUNCH to let the planet spin while
+// the lander animation is playing
+#define SPIN_ON_LAUNCH
+
+// PLANET_SIDE_RATE governs how fast the lander,
+// bio and planet effects will be
+// We're using the 3DO speed, which is 35 FPS
+// The PC speed was 30 FPS.
+// Remember that all values need to evenly divide
+// ONE_SECOND.
+#define PLANET_SIDE_RATE (ONE_SECOND / 35)
+
+
+// This is a derived type from INPUT_STATE_DESC.
+typedef struct LanderInputState LanderInputState;
+struct LanderInputState {
+ // Fields required by DoInput()
+ BOOLEAN (*InputFunc) (LanderInputState *pMS);
+
+ BOOLEAN Initialized;
+ TimeCount NextTime;
+ // Frame rate control
+};
+
+FRAME LanderFrame[8];
+static SOUND LanderSounds;
+MUSIC_REF LanderMusic;
+#define NUM_ORBIT_THEMES 5
+static MUSIC_REF OrbitMusic[NUM_ORBIT_THEMES];
+
+const LIFEFORM_DESC CreatureData[] =
+{
+ {SPEED_MOTIONLESS | DANGER_HARMLESS, MAKE_BYTE (1, 1)},
+ // Roto-Dendron
+ {SPEED_MOTIONLESS | DANGER_HARMLESS, MAKE_BYTE (6, 1)},
+ // Macrocillia
+ {SPEED_MOTIONLESS | DANGER_WEAK, MAKE_BYTE (3, 1)},
+ // Splort Wort
+ {SPEED_MOTIONLESS | DANGER_NORMAL, MAKE_BYTE (5, 3)},
+ // Whackin' Bush
+ {SPEED_MOTIONLESS | DANGER_HARMLESS, MAKE_BYTE (2, 10)},
+ // Slot Machine Tree
+ {BEHAVIOR_UNPREDICTABLE | SPEED_SLOW | DANGER_HARMLESS, MAKE_BYTE (1, 2)},
+ // Neon Worm
+ {BEHAVIOR_FLEE | AWARENESS_MEDIUM | SPEED_SLOW | DANGER_HARMLESS, MAKE_BYTE (8, 5)},
+ // Stiletto Urchin
+ {BEHAVIOR_HUNT | AWARENESS_LOW | SPEED_SLOW | DANGER_WEAK, MAKE_BYTE (2, 2)},
+ // Deluxe Blob
+ {BEHAVIOR_UNPREDICTABLE | SPEED_SLOW | DANGER_NORMAL, MAKE_BYTE (3, 8)},
+ // Glowing Medusa
+ {BEHAVIOR_HUNT | AWARENESS_MEDIUM | SPEED_SLOW | DANGER_MONSTROUS, MAKE_BYTE (10, 15)},
+ // Carousel Beast
+ {BEHAVIOR_HUNT | AWARENESS_MEDIUM | SPEED_MEDIUM | DANGER_WEAK, MAKE_BYTE (3, 3)},
+ // Mysterious Bees
+ {BEHAVIOR_FLEE | AWARENESS_MEDIUM | SPEED_MEDIUM | DANGER_HARMLESS, MAKE_BYTE (2, 1)},
+ // Hopping Blobby
+ {BEHAVIOR_UNPREDICTABLE | SPEED_MEDIUM | DANGER_WEAK, MAKE_BYTE (2, 2)},
+ // Blood Monkey
+ {BEHAVIOR_HUNT | AWARENESS_HIGH | SPEED_MEDIUM | DANGER_NORMAL, MAKE_BYTE (4, 6)},
+ // Yompin Yiminy
+ {BEHAVIOR_UNPREDICTABLE | SPEED_MEDIUM | DANGER_MONSTROUS, MAKE_BYTE (9, 12)},
+ // Amorphous Trandicula
+ {BEHAVIOR_HUNT | AWARENESS_HIGH | SPEED_FAST | DANGER_WEAK, MAKE_BYTE (3, 1)},
+ // Crazy Weasel
+ {BEHAVIOR_FLEE | AWARENESS_HIGH | SPEED_FAST | DANGER_HARMLESS, MAKE_BYTE (1, 1)},
+ // Merry Whumpet
+ {BEHAVIOR_HUNT | AWARENESS_LOW | SPEED_FAST | DANGER_NORMAL, MAKE_BYTE (7, 8)},
+ // Fungal Squid
+ {BEHAVIOR_FLEE | AWARENESS_HIGH | SPEED_FAST | DANGER_WEAK, MAKE_BYTE (15, 2)},
+ // Penguin Cyclops
+ {BEHAVIOR_FLEE | AWARENESS_LOW | SPEED_FAST | DANGER_WEAK, MAKE_BYTE (1, 1)},
+ // Chicken
+ {BEHAVIOR_UNPREDICTABLE | SPEED_SLOW | DANGER_WEAK, MAKE_BYTE (6, 2)},
+ // Bubble Vine
+ {BEHAVIOR_FLEE | AWARENESS_HIGH | SPEED_SLOW | DANGER_WEAK, MAKE_BYTE (4, 2)},
+ // Bug-Eyed Bait
+ {SPEED_MOTIONLESS | DANGER_WEAK, MAKE_BYTE (8, 5)},
+ // Goo Burger
+
+ {SPEED_MOTIONLESS | DANGER_MONSTROUS, MAKE_BYTE (1, 1)},
+ // Evil One
+ {BEHAVIOR_UNPREDICTABLE | SPEED_SLOW | DANGER_HARMLESS, MAKE_BYTE (0, 1)},
+ // Brainbox Bulldozers
+ {BEHAVIOR_HUNT | AWARENESS_HIGH | SPEED_FAST | DANGER_MONSTROUS, MAKE_BYTE (15, 15)},
+ // Zex's Beauty
+};
+
+
+extern PRIM_LINKS DisplayLinks;
+
+#define DAMAGE_CYCLE 6
+// XXX: There are actually only 9 explosion images.
+// The last frame is drawn twice.
+#define EXPLOSION_LIFE 10
+// How long to wait after the lander explodes, so that the full
+// gravity of the player's situation sinks in
+#define EXPLOSION_WAIT (ONE_SECOND * 2)
+#define EXPLOSION_WAIT_FRAMES (EXPLOSION_WAIT / PLANET_SIDE_RATE)
+// The actual number of frame that the explosion and wait takes is:
+// EXPLOSION_LIFE * 3 + EXPLOSION_WAIT_FRAMES
+
+#define DEATH_EXPLOSION 0
+
+// TODO: redefine these in terms of CONTEXT width/height
+#define SURFACE_WIDTH SIS_SCREEN_WIDTH
+#define SURFACE_HEIGHT (SIS_SCREEN_HEIGHT - MAP_HEIGHT - MAP_BORDER_HEIGHT)
+
+#define REPAIR_LANDER (1 << 7)
+#define REPAIR_TRANSITION (1 << 6)
+#define KILL_CREW (1 << 5)
+#define ADD_AT_END (1 << 4)
+#define REPAIR_COUNT (0xf)
+
+#define LANDER_SPEED_DENOM 10
+
+static BYTE lander_flags;
+static POINT curLanderLoc;
+static int crew_left;
+static int shieldHit;
+ // which shield was hit, assuming it helped
+static int damage_index;
+ // number of lander damage frames left
+static int explosion_index;
+ // lander explosion progression. Semantics are similar to an
+ // inverse of ELEMENT.life_span
+static int turn_wait;
+ // thus named for similar semantics to ELEMENT.turn_wait
+static int weapon_wait;
+ // semantics similar to STARSHIP.weapon_counter
+
+// TODO: We may want to make the PLANETSIDE_DESC fields into static vars
+static PLANETSIDE_DESC *planetSideDesc;
+
+#define ON_THE_GROUND 0
+
+
+static Color
+DamageColorCycle (Color c, COUNT i)
+{
+ static const Color damage_tab[DAMAGE_CYCLE + 1] =
+ {
+ WHITE_COLOR_INIT,
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7E),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7C),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1C, 0x00), 0x78),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1F, 0x0A), 0x0E),
+ };
+
+ if (i)
+ c = damage_tab[i];
+ else if (sameColor(c, WHITE_COLOR))
+ c = damage_tab[6];
+ else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)))
+ c = damage_tab[5];
+ else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1C, 0x00), 0x78)))
+ c = damage_tab[4];
+ else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7A)))
+ c = damage_tab[3];
+ else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0E, 0x00), 0x7C)))
+ c = damage_tab[2];
+ else if (sameColor(c, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x07, 0x00), 0x7E)))
+ c = damage_tab[1];
+ else
+ c = damage_tab[0];
+
+ return c;
+}
+
+static HELEMENT AddGroundDisaster (COUNT which_disaster);
+
+void
+object_animation (ELEMENT *ElementPtr)
+{
+ COUNT frame_index, angle;
+ PRIMITIVE *pPrim;
+
+ pPrim = &DisplayArray[ElementPtr->PrimIndex];
+ if (GetPrimType (pPrim) == STAMPFILL_PRIM
+ && !((ElementPtr->state_flags & FINITE_LIFE)
+ && ElementPtr->mass_points == EARTHQUAKE_DISASTER))
+ {
+ Color c;
+
+ c = DamageColorCycle (GetPrimColor (pPrim), 0);
+ if (sameColor(c, WHITE_COLOR))
+ {
+ SetPrimType (pPrim, STAMP_PRIM);
+ if (ElementPtr->hit_points == 0)
+ {
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ pPrim->Object.Stamp.frame =
+ SetAbsFrameIndex (pPrim->Object.Stamp.frame, 0);
+
+ PlaySound (SetAbsSoundIndex (LanderSounds, LIFEFORM_CANNED),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+ }
+ }
+
+ SetPrimColor (pPrim, c);
+ }
+
+ frame_index = GetFrameIndex (pPrim->Object.Stamp.frame) + 1;
+ if (LONIBBLE (ElementPtr->turn_wait))
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->turn_wait += HINIBBLE (ElementPtr->turn_wait);
+
+ pPrim->Object.Stamp.frame = IncFrameIndex (pPrim->Object.Stamp.frame);
+
+ if (ElementPtr->state_flags & FINITE_LIFE)
+ {
+ /* A natural disaster */
+ if (ElementPtr->mass_points == DEATH_EXPLOSION)
+ { // Lander explosion
+ ++explosion_index;
+ if (explosion_index >= EXPLOSION_LIFE)
+ { // XXX: The last frame is drawn twice
+ pPrim->Object.Stamp.frame =
+ DecFrameIndex (pPrim->Object.Stamp.frame);
+ }
+ }
+ else if (ElementPtr->mass_points == EARTHQUAKE_DISASTER)
+ {
+ SIZE s;
+
+ if (frame_index >= 13)
+ s = 0;
+ else
+ s = (14 - frame_index) >> 1;
+ // XXX: Was 0x8000 the background flag on 3DO?
+ //SetPrimColor (pPrim, BUILD_COLOR (0x8000 | MAKE_RGB15 (0x1F, 0x1F, 0x1F), s));
+ SetPrimColor (pPrim, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), s));
+ if (frame_index == 13)
+ PlaySound (SetAbsSoundIndex (LanderSounds, EARTHQUAKE_DISASTER),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+ }
+
+ if (ElementPtr->mass_points == LAVASPOT_DISASTER
+ && frame_index == 5
+ && TFB_Random () % 100 < 90)
+ {
+ HELEMENT hLavaElement;
+
+ /* Change lava-spot direction of travel */
+ hLavaElement = AddGroundDisaster (LAVASPOT_DISASTER);
+ if (hLavaElement)
+ {
+ ELEMENT *LavaElementPtr;
+
+ angle = FACING_TO_ANGLE (ElementPtr->facing);
+ LockElement (hLavaElement, &LavaElementPtr);
+ LavaElementPtr->next.location = ElementPtr->next.location;
+ LavaElementPtr->next.location.x += COSINE (angle, 4);
+ LavaElementPtr->next.location.y += SINE (angle, 4);
+ if (LavaElementPtr->next.location.y < 0)
+ LavaElementPtr->next.location.y = 0;
+ else if (LavaElementPtr->next.location.y >= (MAP_HEIGHT << MAG_SHIFT))
+ LavaElementPtr->next.location.y = (MAP_HEIGHT << MAG_SHIFT) - 1;
+ if (LavaElementPtr->next.location.x < 0)
+ LavaElementPtr->next.location.x += MAP_WIDTH << MAG_SHIFT;
+ else
+ LavaElementPtr->next.location.x %= MAP_WIDTH << MAG_SHIFT;
+ LavaElementPtr->facing = NORMALIZE_FACING (
+ ElementPtr->facing + (TFB_Random () % 3 - 1));
+ UnlockElement (hLavaElement);
+ }
+ }
+ }
+ else if (!(frame_index & 3) && ElementPtr->hit_points)
+ {
+ BYTE index;
+ COUNT speed;
+
+ index = ElementPtr->mass_points & ~CREATURE_AWARE;
+ speed = CreatureData[index].Attributes & SPEED_MASK;
+ if (speed)
+ {
+ SIZE dx, dy;
+ COUNT old_angle;
+
+ dx = curLanderLoc.x - ElementPtr->next.location.x;
+ if (dx < 0 && dx < -(MAP_WIDTH << (MAG_SHIFT - 1)))
+ dx += MAP_WIDTH << MAG_SHIFT;
+ else if (dx > (MAP_WIDTH << (MAG_SHIFT - 1)))
+ dx -= MAP_WIDTH << MAG_SHIFT;
+ dy = curLanderLoc.y - ElementPtr->next.location.y;
+ angle = ARCTAN (dx, dy);
+ if (dx < 0)
+ dx = -dx;
+ if (dy < 0)
+ dy = -dy;
+
+ if (dx >= SURFACE_WIDTH || dy >= SURFACE_WIDTH
+ || dx * dx + dy * dy >= SURFACE_WIDTH * SURFACE_WIDTH)
+ ElementPtr->mass_points &= ~CREATURE_AWARE;
+ else if (!(ElementPtr->mass_points & CREATURE_AWARE))
+ {
+ BYTE DetectPercent;
+
+ DetectPercent = (((BYTE)(CreatureData[index].Attributes
+ & AWARENESS_MASK) >> AWARENESS_SHIFT) + 1)
+ * (30 / 6);
+ // XXX: Shouldn't this be dependent on
+ // PLANET_SIDE_RATE somehow? And why is it
+ // written as '30 / 6' instead of 5? Does the 30
+ // specify the (PC) framerate? That doesn't make
+ // sense; I would expect it to be in the
+ // denominator. And even then, it wouldn't give
+ // the same results with different frame rates,
+ // as repeating 'random(x / 30)' 30 times doesn't
+ // generally have the same result as repeating
+ // 'random(x / 35)' 25 times. - SvdB
+ if (TFB_Random () % 100 < DetectPercent)
+ {
+ ElementPtr->thrust_wait = 0;
+ ElementPtr->mass_points |= CREATURE_AWARE;
+ }
+ }
+
+ if (ElementPtr->next.location.y == 0
+ || ElementPtr->next.location.y ==
+ (MAP_HEIGHT << MAG_SHIFT) - 1)
+ ElementPtr->thrust_wait = 0;
+
+ old_angle = GetVelocityTravelAngle (&ElementPtr->velocity);
+ if (ElementPtr->thrust_wait)
+ {
+ --ElementPtr->thrust_wait;
+ angle = old_angle;
+ }
+ else if (!(ElementPtr->mass_points & CREATURE_AWARE)
+ || (CreatureData[index].Attributes
+ & BEHAVIOR_MASK) == BEHAVIOR_UNPREDICTABLE)
+ {
+ COUNT rand_val;
+
+ rand_val = TFB_Random ();
+ angle = NORMALIZE_ANGLE (LOBYTE (rand_val));
+ ElementPtr->thrust_wait =
+ (HIBYTE (rand_val) >> 2) + 10;
+ }
+ else if ((CreatureData[index].Attributes
+ & BEHAVIOR_MASK) == BEHAVIOR_FLEE)
+ {
+ if (ElementPtr->next.location.y == 0
+ || ElementPtr->next.location.y ==
+ (MAP_HEIGHT << MAG_SHIFT) - 1)
+ {
+ if (angle & (HALF_CIRCLE - 1))
+ angle = HALF_CIRCLE - angle;
+ else if (old_angle == QUADRANT
+ || old_angle == (FULL_CIRCLE - QUADRANT))
+ angle = old_angle;
+ else
+ angle = ((TFB_Random () & 1)
+ * HALF_CIRCLE) - QUADRANT;
+ ElementPtr->thrust_wait = 5;
+ }
+ angle = NORMALIZE_ANGLE (angle + HALF_CIRCLE);
+ }
+
+ switch (speed)
+ {
+ case SPEED_SLOW:
+ speed = WORLD_TO_VELOCITY (2 * 1) >> 2;
+ break;
+ case SPEED_MEDIUM:
+ speed = WORLD_TO_VELOCITY (2 * 1) >> 1;
+ break;
+ case SPEED_FAST:
+ speed = WORLD_TO_VELOCITY (2 * 1) * 9 / 10;
+ break;
+ }
+
+ SetVelocityComponents (&ElementPtr->velocity,
+ COSINE (angle, speed), SINE (angle, speed));
+ }
+ }
+ }
+
+ if ((ElementPtr->state_flags & FINITE_LIFE)
+ && ElementPtr->mass_points == DEATH_EXPLOSION
+ && GetSuccLink (DisplayLinks) != ElementPtr->PrimIndex)
+ lander_flags |= ADD_AT_END;
+}
+
+#define NUM_CREW_COLS 6
+#define NUM_CREW_ROWS 2
+
+static void
+DeltaLanderCrew (SIZE crew_delta, COUNT which_disaster)
+{
+ STAMP s;
+ CONTEXT OldContext;
+
+ if (crew_delta > 0)
+ {
+ // Filling up the crew bar when landing.
+ crew_delta = crew_left;
+ crew_left += 1;
+
+ s.frame = SetAbsFrameIndex (LanderFrame[0], 55);
+ }
+ else /* if (crew_delta < 0) */
+ {
+ if (crew_left < 1)
+ return; // irrelevant -- all dead
+
+ shieldHit = GET_GAME_STATE (LANDER_SHIELDS);
+ shieldHit &= 1 << which_disaster;
+ if (!shieldHit || TFB_Random () % 100 >= 95)
+ { // No shield, or it did not help
+ shieldHit = 0;
+ --crew_left;
+ }
+
+ damage_index = DAMAGE_CYCLE;
+ if (shieldHit)
+ return;
+
+ crew_delta = crew_left;
+ s.frame = SetAbsFrameIndex (LanderFrame[0], 56);
+
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_INJURED),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+ }
+
+ s.origin.x = 11 + (6 * (crew_delta % NUM_CREW_COLS));
+ s.origin.y = 35 - (6 * (crew_delta / NUM_CREW_COLS));
+
+ OldContext = SetContext (RadarContext);
+ DrawStamp (&s);
+ SetContext (OldContext);
+}
+
+static void
+FillLanderHold (PLANETSIDE_DESC *pPSD, COUNT scan, COUNT NumRetrieved)
+{
+ COUNT start_count;
+ STAMP s;
+ CONTEXT OldContext;
+
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_PICKUP),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+
+ if (scan == BIOLOGICAL_SCAN)
+ {
+ start_count = pPSD->BiologicalLevel;
+
+ s.frame = SetAbsFrameIndex (LanderFrame[0], 41);
+
+ pPSD->BiologicalLevel += NumRetrieved;
+ }
+ else
+ {
+ start_count = pPSD->ElementLevel;
+ pPSD->ElementLevel += NumRetrieved;
+ if (GET_GAME_STATE (IMPROVED_LANDER_CARGO))
+ {
+ start_count >>= 1;
+ NumRetrieved = (pPSD->ElementLevel >> 1) - start_count;
+ }
+
+ s.frame = SetAbsFrameIndex (LanderFrame[0], 43);
+ }
+
+ s.origin.x = 0;
+ s.origin.y = -(int)start_count;
+ if (!(start_count & 1))
+ s.frame = IncFrameIndex (s.frame);
+
+ OldContext = SetContext (RadarContext);
+ while (NumRetrieved--)
+ {
+ if (start_count++ & 1)
+ s.frame = IncFrameIndex (s.frame);
+ else
+ s.frame = DecFrameIndex (s.frame);
+ DrawStamp (&s);
+ --s.origin.y;
+ }
+ SetContext (OldContext);
+}
+
+// returns true iff the node was picked up.
+static bool
+pickupMineralNode (PLANETSIDE_DESC *pPSD, COUNT NumRetrieved,
+ ELEMENT *ElementPtr, const INTERSECT_CONTROL *LanderControl,
+ const INTERSECT_CONTROL *ElementControl)
+{
+ BYTE EType;
+ UNICODE ch;
+ UNICODE *pStr;
+
+ if (pPSD->ElementLevel >= pPSD->MaxElementLevel)
+ {
+ // Lander full
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_FULL),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+ return false;
+ }
+
+ if (pPSD->ElementLevel + NumRetrieved > pPSD->MaxElementLevel)
+ {
+ // Deposit could only be picked up partially.
+ NumRetrieved = (COUNT)(pPSD->MaxElementLevel - pPSD->ElementLevel);
+ }
+
+ FillLanderHold (pPSD, MINERAL_SCAN, NumRetrieved);
+
+ EType = ElementPtr->turn_wait;
+ pPSD->ElementAmounts[ElementCategory (EType)] += NumRetrieved;
+
+ pPSD->NumFrames = NUM_TEXT_FRAMES;
+ sprintf (pPSD->AmountBuf, "%u", NumRetrieved);
+ pStr = GAME_STRING (EType + ELEMENTS_STRING_BASE);
+
+ pPSD->MineralText[0].baseline.x = (SURFACE_WIDTH >> 1)
+ + (ElementControl->EndPoint.x - LanderControl->EndPoint.x);
+ pPSD->MineralText[0].baseline.y = (SURFACE_HEIGHT >> 1)
+ + (ElementControl->EndPoint.y - LanderControl->EndPoint.y);
+ pPSD->MineralText[0].CharCount = (COUNT)~0;
+ pPSD->MineralText[1].pStr = pStr;
+
+ while ((ch = *pStr++) && ch != ' ')
+ ;
+ if (ch == '\0')
+ {
+ pPSD->MineralText[1].CharCount = (COUNT)~0;
+ pPSD->MineralText[2].CharCount = 0;
+ }
+ else /* ch == ' ' */
+ {
+ // Name contains a space. Print over
+ // two lines.
+ pPSD->MineralText[1].CharCount = utf8StringCountN(
+ pPSD->MineralText[1].pStr, pStr - 1);
+ pPSD->MineralText[2].pStr = pStr;
+ pPSD->MineralText[2].CharCount = (COUNT)~0;
+ }
+
+ return true;
+}
+
+static bool
+pickupBioNode (PLANETSIDE_DESC *pPSD, COUNT NumRetrieved)
+{
+ if (pPSD->BiologicalLevel >= MAX_SCROUNGED)
+ {
+ // Lander is full.
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_FULL),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+ return false;
+ }
+
+ if (pPSD->BiologicalLevel + NumRetrieved > MAX_SCROUNGED)
+ {
+ // Node could only be picked up partially.
+ NumRetrieved = (COUNT)(MAX_SCROUNGED - pPSD->BiologicalLevel);
+ }
+
+ FillLanderHold (pPSD, BIOLOGICAL_SCAN, NumRetrieved);
+
+ return true;
+}
+
+static void
+shotCreature (ELEMENT *ElementPtr, BYTE value,
+ INTERSECT_CONTROL *LanderControl, PRIMITIVE *pPrim)
+{
+ if (ElementPtr->hit_points == 0)
+ {
+ // Creature is already canned.
+ return;
+ }
+
+ --ElementPtr->hit_points;
+ if (ElementPtr->hit_points == 0)
+ {
+ // Can creature.
+ ElementPtr->mass_points = value;
+ DisplayArray[ElementPtr->PrimIndex].Object.Stamp.frame =
+ pSolarSysState->PlanetSideFrame[0];
+ }
+ else if (CreatureData[ElementPtr->mass_points & ~CREATURE_AWARE]
+ .Attributes & SPEED_MASK)
+ {
+ COUNT angle;
+
+ angle = FACING_TO_ANGLE (GetFrameIndex (
+ LanderControl->IntersectStamp.frame) -
+ ANGLE_TO_FACING (FULL_CIRCLE));
+ DeltaVelocityComponents (&ElementPtr->velocity,
+ COSINE (angle, WORLD_TO_VELOCITY (1)),
+ SINE (angle, WORLD_TO_VELOCITY (1)));
+ ElementPtr->thrust_wait = 0;
+ ElementPtr->mass_points |= CREATURE_AWARE;
+ }
+
+ SetPrimType (pPrim, STAMPFILL_PRIM);
+ SetPrimColor (pPrim, WHITE_COLOR);
+
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_HITS),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+}
+
+static void
+CheckObjectCollision (COUNT index)
+{
+ INTERSECT_CONTROL LanderControl;
+ DRAWABLE LanderHandle;
+ PRIMITIVE *pPrim;
+ PRIMITIVE *pLanderPrim;
+ PLANETSIDE_DESC *pPSD = planetSideDesc;
+
+ if (index != END_OF_LIST)
+ {
+ pLanderPrim = &DisplayArray[index];
+ LanderControl.IntersectStamp = pLanderPrim->Object.Stamp;
+ index = GetPredLink (GetPrimLinks (pLanderPrim));
+ }
+ else
+ {
+ pLanderPrim = 0;
+ LanderControl.IntersectStamp.origin.x = SURFACE_WIDTH >> 1;
+ LanderControl.IntersectStamp.origin.y = SURFACE_HEIGHT >> 1;
+ LanderControl.IntersectStamp.frame = LanderFrame[0];
+ index = GetSuccLink (DisplayLinks);
+ }
+
+ LanderControl.EndPoint = LanderControl.IntersectStamp.origin;
+ LanderHandle = GetFrameParentDrawable (LanderControl.IntersectStamp.frame);
+
+ for (; index != END_OF_LIST; index = GetPredLink (GetPrimLinks (pPrim)))
+ {
+ INTERSECT_CONTROL ElementControl;
+ HELEMENT hElement, hNextElement;
+
+ pPrim = &DisplayArray[index];
+ ElementControl.IntersectStamp = pPrim->Object.Stamp;
+ ElementControl.EndPoint = ElementControl.IntersectStamp.origin;
+
+ if (GetFrameParentDrawable (ElementControl.IntersectStamp.frame)
+ == LanderHandle)
+ {
+ CheckObjectCollision (index);
+ continue;
+ }
+
+ if (!DrawablesIntersect (&LanderControl,
+ &ElementControl, MAX_TIME_VALUE))
+ continue;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = GetSuccElement (ElementPtr);
+
+ if (&DisplayArray[ElementPtr->PrimIndex] == pLanderPrim)
+ {
+ ElementPtr->state_flags |= DISAPPEARING;
+ UnlockElement (hElement);
+ continue;
+ }
+
+ if (&DisplayArray[ElementPtr->PrimIndex] != pPrim
+ || ElementPtr->playerNr != PS_NON_PLAYER)
+ {
+ UnlockElement (hElement);
+ continue;
+ }
+
+ {
+ COUNT scan, NumRetrieved;
+ SIZE which_node;
+
+ scan = LOBYTE (ElementPtr->scan_node);
+ if (pLanderPrim == 0)
+ {
+ /* Collision of lander with another object */
+ if (crew_left == 0 || pPSD->InTransit)
+ break;
+
+ if (ElementPtr->state_flags & FINITE_LIFE)
+ {
+ /* A natural disaster */
+ scan = ElementPtr->mass_points;
+ switch (scan)
+ {
+ case EARTHQUAKE_DISASTER:
+ case LAVASPOT_DISASTER:
+ if (TFB_Random () % 100 < 25)
+ DeltaLanderCrew (-1, scan);
+ break;
+ }
+
+ UnlockElement (hElement);
+ continue;
+ }
+ else if (scan == ENERGY_SCAN)
+ {
+ // noop; handled by generation funcs, see below
+ }
+ else if (scan == BIOLOGICAL_SCAN && ElementPtr->hit_points)
+ {
+ BYTE danger_vals[] =
+ {
+ 0, 6, 13, 26
+ };
+ int creatureIndex = ElementPtr->mass_points
+ & ~CREATURE_AWARE;
+ int dangerLevel =
+ (CreatureData[creatureIndex].Attributes &
+ DANGER_MASK) >> DANGER_SHIFT;
+
+ if (TFB_Random () % 128 < danger_vals[dangerLevel])
+ {
+ PlaySound (SetAbsSoundIndex (
+ LanderSounds, BIOLOGICAL_DISASTER),
+ NotPositional (), NULL,
+ GAME_SOUND_PRIORITY);
+ DeltaLanderCrew (-1, BIOLOGICAL_DISASTER);
+ }
+ UnlockElement (hElement);
+ continue;
+ }
+
+ NumRetrieved = ElementPtr->mass_points;
+ }
+ else if (ElementPtr->state_flags & FINITE_LIFE)
+ {
+ /* Collision of a stun bolt with a natural disaster */
+ UnlockElement (hElement);
+ continue;
+ }
+ else
+ {
+ BYTE value;
+
+ if (scan == ENERGY_SCAN)
+ {
+ /* Collision of a stun bolt with an energy node */
+ UnlockElement (hElement);
+ break;
+ }
+
+ if (scan == BIOLOGICAL_SCAN
+ && (value = LONIBBLE (CreatureData[
+ ElementPtr->mass_points
+ & ~CREATURE_AWARE
+ ].ValueAndHitPoints)))
+ {
+ /* Collision of a stun bolt with a viable creature */
+ shotCreature (ElementPtr, value, &LanderControl,
+ pPrim);
+ UnlockElement (hElement);
+ break;
+ }
+
+ NumRetrieved = 0;
+ }
+
+ if (NumRetrieved)
+ {
+ switch (scan)
+ {
+ case ENERGY_SCAN:
+ break;
+ case MINERAL_SCAN:
+ if (!pickupMineralNode (pPSD, NumRetrieved,
+ ElementPtr, &LanderControl,
+ &ElementControl))
+ continue;
+ break;
+ case BIOLOGICAL_SCAN:
+ if (!pickupBioNode (pPSD, NumRetrieved))
+ continue;
+ break;
+ }
+ }
+
+ which_node = HIBYTE (ElementPtr->scan_node) - 1;
+ if (callPickupForScanType (pSolarSysState,
+ pSolarSysState->pOrbitalDesc, which_node, scan))
+ { // Node retrieved, remove from the surface
+ setNodeRetrieved (&pSolarSysState->SysInfo.PlanetInfo,
+ scan, which_node);
+ SET_GAME_STATE (PLANETARY_CHANGE, 1);
+ ElementPtr->state_flags |= DISAPPEARING;
+ }
+ UnlockElement (hElement);
+ }
+ }
+ }
+}
+
+static void
+lightning_process (ELEMENT *ElementPtr)
+{
+ PRIMITIVE *pPrim;
+
+ pPrim = &DisplayArray[ElementPtr->PrimIndex];
+ if (LONIBBLE (ElementPtr->turn_wait))
+ --ElementPtr->turn_wait;
+ else
+ {
+ COUNT num_frames;
+
+ num_frames = GetFrameCount (pPrim->Object.Stamp.frame) - 7;
+ if (GetFrameIndex (pPrim->Object.Stamp.frame) >= num_frames)
+ {
+ /* Advance to the next surface strike effect frame */
+ // XXX: This is unused, we never get here
+ pPrim->Object.Stamp.frame =
+ IncFrameIndex (pPrim->Object.Stamp.frame);
+ }
+ else
+ {
+ SIZE s;
+
+ // XXX: Color cycling is largely unused, because the color
+ // never actually changes RGB values (see MAKE_RGB15 below).
+ // This did, however, work in DOS SC2 version (fade effect).
+ s = 7 - ((SIZE)ElementPtr->cycle - (SIZE)ElementPtr->life_span);
+ if (s < 0)
+ s = 0;
+ // XXX: Was 0x8000 the background flag on 3DO?
+ //SetPrimColor (pPrim, BUILD_COLOR (0x8000 | MAKE_RGB15 (0x1F, 0x1F, 0x1F), s));
+ SetPrimColor (pPrim, BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), s));
+
+ if (ElementPtr->mass_points == LIGHTNING_DISASTER)
+ {
+ /* This one always strikes the lander and can hurt */
+ if (crew_left && TFB_Random () % 100 < 10
+ && !planetSideDesc->InTransit)
+ lander_flags |= KILL_CREW;
+
+ ElementPtr->next.location = curLanderLoc;
+ }
+
+ pPrim->Object.Stamp.frame =
+ SetAbsFrameIndex (pPrim->Object.Stamp.frame,
+ TFB_Random () % num_frames);
+ }
+
+ ElementPtr->turn_wait += HINIBBLE (ElementPtr->turn_wait);
+ }
+
+ if (GetSuccLink (DisplayLinks) != ElementPtr->PrimIndex)
+ lander_flags |= ADD_AT_END;
+}
+
+static void
+AddLightning (void)
+{
+ HELEMENT hLightningElement;
+
+ hLightningElement = AllocElement ();
+ if (hLightningElement)
+ {
+ DWORD rand_val;
+ ELEMENT *LightningElementPtr;
+
+ LockElement (hLightningElement, &LightningElementPtr);
+
+ LightningElementPtr->playerNr = PS_NON_PLAYER;
+ LightningElementPtr->state_flags = FINITE_LIFE;
+ LightningElementPtr->preprocess_func = lightning_process;
+ if (TFB_Random () % 100 >= 25)
+ LightningElementPtr->mass_points = 0; /* harmless */
+ else
+ LightningElementPtr->mass_points = LIGHTNING_DISASTER;
+
+ rand_val = TFB_Random ();
+ LightningElementPtr->life_span = 10 + (HIWORD (rand_val) % 10) + 1;
+ LightningElementPtr->next.location.x = (curLanderLoc.x
+ + ((MAP_WIDTH << MAG_SHIFT) - ((SURFACE_WIDTH >> 1) - 6))
+ + (LOBYTE (rand_val) % (SURFACE_WIDTH - 12))
+ ) % (MAP_WIDTH << MAG_SHIFT);
+ LightningElementPtr->next.location.y = (curLanderLoc.y
+ + ((MAP_HEIGHT << MAG_SHIFT) - ((SURFACE_HEIGHT >> 1) - 6))
+ + (HIBYTE (rand_val) % (SURFACE_HEIGHT - 12))
+ ) % (MAP_HEIGHT << MAG_SHIFT);
+
+ LightningElementPtr->cycle = LightningElementPtr->life_span;
+
+ SetPrimType (&DisplayArray[LightningElementPtr->PrimIndex], STAMPFILL_PRIM);
+ SetPrimColor (&DisplayArray[LightningElementPtr->PrimIndex], WHITE_COLOR);
+ DisplayArray[LightningElementPtr->PrimIndex].Object.Stamp.frame =
+ LanderFrame[2];
+
+ UnlockElement (hLightningElement);
+
+ PutElement (hLightningElement);
+
+ PlaySound (SetAbsSoundIndex (LanderSounds, LIGHTNING_DISASTER),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+ }
+}
+
+static HELEMENT
+AddGroundDisaster (COUNT which_disaster)
+{
+ HELEMENT hGroundDisasterElement;
+
+ hGroundDisasterElement = AllocElement ();
+ if (hGroundDisasterElement)
+ {
+ DWORD rand_val;
+ ELEMENT *GroundDisasterElementPtr;
+ PRIMITIVE *pPrim;
+
+ LockElement (hGroundDisasterElement, &GroundDisasterElementPtr);
+
+ pPrim = &DisplayArray[GroundDisasterElementPtr->PrimIndex];
+ GroundDisasterElementPtr->mass_points = which_disaster;
+ GroundDisasterElementPtr->playerNr = PS_NON_PLAYER;
+ GroundDisasterElementPtr->state_flags = FINITE_LIFE;
+ GroundDisasterElementPtr->preprocess_func = object_animation;
+
+ rand_val = TFB_Random ();
+ GroundDisasterElementPtr->next.location.x = (curLanderLoc.x
+ + ((MAP_WIDTH << MAG_SHIFT) - (SURFACE_WIDTH * 3 / 8))
+ + (LOWORD (rand_val) % (SURFACE_WIDTH * 3 / 4))
+ ) % (MAP_WIDTH << MAG_SHIFT);
+ GroundDisasterElementPtr->next.location.y = (curLanderLoc.y
+ + ((MAP_HEIGHT << MAG_SHIFT) - (SURFACE_HEIGHT * 3 / 8))
+ + (HIWORD (rand_val) % (SURFACE_HEIGHT * 3 / 4))
+ ) % (MAP_HEIGHT << MAG_SHIFT);
+
+
+ if (which_disaster == EARTHQUAKE_DISASTER)
+ {
+ SetPrimType (pPrim, STAMPFILL_PRIM);
+ pPrim->Object.Stamp.frame = LanderFrame[1];
+ GroundDisasterElementPtr->turn_wait = MAKE_BYTE (2, 2);
+ }
+ else /* if (which_disaster == LAVASPOT_DISASTER) */
+ {
+ SetPrimType (pPrim, STAMP_PRIM);
+ GroundDisasterElementPtr->facing =
+ NORMALIZE_FACING (TFB_Random ());
+ pPrim->Object.Stamp.frame = LanderFrame[3];
+ GroundDisasterElementPtr->turn_wait = MAKE_BYTE (0, 0);
+ }
+ GroundDisasterElementPtr->life_span =
+ GetFrameCount (pPrim->Object.Stamp.frame)
+ * (LONIBBLE (GroundDisasterElementPtr->turn_wait) + 1) - 1;
+
+ UnlockElement (hGroundDisasterElement);
+
+ PutElement (hGroundDisasterElement);
+ }
+
+ return (hGroundDisasterElement);
+}
+
+// This function replaces the ELEMENT manipulations typically done by
+// PreProcess() and PostProcess() in process.c. Lander code does not
+// call RedrawQueue() & Co and thus does not reap the benefits (or curses,
+// depending how you look at it) of automatic flags processing.
+static void
+BuildObjectList (void)
+{
+ DWORD rand_val;
+ POINT org;
+ HELEMENT hElement, hNextElement;
+ PLANETSIDE_DESC *pPSD = planetSideDesc;
+
+ DisplayLinks = MakeLinks (END_OF_LIST, END_OF_LIST);
+
+ lander_flags &= ~KILL_CREW;
+
+ rand_val = TFB_Random ();
+ if (LOBYTE (HIWORD (rand_val)) < pPSD->FireChance)
+ {
+ AddGroundDisaster (LAVASPOT_DISASTER);
+ PlaySound (SetAbsSoundIndex (LanderSounds, LAVASPOT_DISASTER),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+ }
+
+ if (HIBYTE (LOWORD (rand_val)) < pPSD->TectonicsChance)
+ AddGroundDisaster (EARTHQUAKE_DISASTER);
+
+ if (LOBYTE (LOWORD (rand_val)) < pPSD->WeatherChance)
+ AddLightning ();
+
+ org = curLanderLoc;
+ for (hElement = GetHeadElement ();
+ hElement; hElement = hNextElement)
+ {
+ SIZE dx, dy;
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+
+ if (ElementPtr->life_span == 0
+ || (ElementPtr->state_flags & DISAPPEARING))
+ {
+ hNextElement = GetSuccElement (ElementPtr);
+ UnlockElement (hElement);
+ RemoveElement (hElement);
+ FreeElement (hElement);
+ continue;
+ }
+ else if (ElementPtr->state_flags & FINITE_LIFE)
+ --ElementPtr->life_span;
+
+ lander_flags &= ~ADD_AT_END;
+
+ if (ElementPtr->preprocess_func)
+ (*ElementPtr->preprocess_func) (ElementPtr);
+
+ GetNextVelocityComponents (&ElementPtr->velocity, &dx, &dy, 1);
+ if (dx || dy)
+ {
+ ElementPtr->next.location.x += dx;
+ ElementPtr->next.location.y += dy;
+ /* if not lander's shot */
+ if (ElementPtr->playerNr != PS_HUMAN_PLAYER)
+ {
+ if (ElementPtr->next.location.y < 0)
+ ElementPtr->next.location.y = 0;
+ else if (ElementPtr->next.location.y >= (MAP_HEIGHT << MAG_SHIFT))
+ ElementPtr->next.location.y = (MAP_HEIGHT << MAG_SHIFT) - 1;
+ }
+ if (ElementPtr->next.location.x < 0)
+ ElementPtr->next.location.x += MAP_WIDTH << MAG_SHIFT;
+ else
+ ElementPtr->next.location.x %= MAP_WIDTH << MAG_SHIFT;
+
+ // XXX: APPEARING flag is set by scan.c for scanned blips
+ if (ElementPtr->state_flags & APPEARING)
+ { // Update the location of a moving object on the scan map
+ ElementPtr->current.location.x =
+ ElementPtr->next.location.x >> MAG_SHIFT;
+ ElementPtr->current.location.y =
+ ElementPtr->next.location.y >> MAG_SHIFT;
+ }
+ }
+
+ {
+ PRIMITIVE *pPrim;
+
+ pPrim = &DisplayArray[ElementPtr->PrimIndex];
+ pPrim->Object.Stamp.origin.x =
+ ElementPtr->next.location.x
+ - org.x + (SURFACE_WIDTH >> 1);
+ if (pPrim->Object.Stamp.origin.x >=
+ (MAP_WIDTH << MAG_SHIFT) - (SURFACE_WIDTH * 3 / 2))
+ pPrim->Object.Stamp.origin.x -= MAP_WIDTH << MAG_SHIFT;
+ else if (pPrim->Object.Stamp.origin.x <=
+ -((MAP_WIDTH << MAG_SHIFT) - (SURFACE_WIDTH * 3 / 2)))
+ pPrim->Object.Stamp.origin.x += MAP_WIDTH << MAG_SHIFT;
+
+ pPrim->Object.Stamp.origin.y =
+ ElementPtr->next.location.y
+ - org.y + (SURFACE_HEIGHT >> 1);
+
+ if (lander_flags & ADD_AT_END)
+ InsertPrim (&DisplayLinks, ElementPtr->PrimIndex, END_OF_LIST);
+ else
+ InsertPrim (&DisplayLinks, ElementPtr->PrimIndex, GetPredLink (DisplayLinks));
+ }
+
+ hNextElement = GetSuccElement (ElementPtr);
+ UnlockElement (hElement);
+ }
+}
+
+static void
+ScrollPlanetSide (SIZE dx, SIZE dy, int landingOffset)
+{
+ POINT new_pt;
+ STAMP lander_s, shadow_s, shield_s;
+ CONTEXT OldContext;
+
+ new_pt.y = curLanderLoc.y + dy;
+ if (new_pt.y < 0)
+ {
+ new_pt.y = 0;
+ dy = new_pt.y - curLanderLoc.y;
+ dx = 0;
+ ZeroVelocityComponents (&GLOBAL (velocity));
+ }
+ else if (new_pt.y > (MAP_HEIGHT << MAG_SHIFT) - 1)
+ {
+ new_pt.y = (MAP_HEIGHT << MAG_SHIFT) - 1;
+ dy = new_pt.y - curLanderLoc.y;
+ dx = 0;
+ ZeroVelocityComponents (&GLOBAL (velocity));
+ }
+
+ new_pt.x = curLanderLoc.x + dx;
+ if (new_pt.x < 0)
+ new_pt.x += MAP_WIDTH << MAG_SHIFT;
+ else if (new_pt.x >= MAP_WIDTH << MAG_SHIFT)
+ new_pt.x -= MAP_WIDTH << MAG_SHIFT;
+
+ curLanderLoc = new_pt;
+
+ OldContext = SetContext (PlanetContext);
+
+ BatchGraphics ();
+
+ // Display planet area, accounting for horizontal wrapping if
+ // near the edges.
+ {
+ STAMP s;
+
+ ClearDrawable ();
+ s.origin.x = -new_pt.x + (SURFACE_WIDTH >> 1);
+ s.origin.y = -new_pt.y + (SURFACE_HEIGHT >> 1);
+ s.frame = pSolarSysState->Orbit.TopoZoomFrame;
+ DrawStamp (&s);
+ s.origin.x += MAP_WIDTH << MAG_SHIFT;
+ DrawStamp (&s);
+ s.origin.x -= MAP_WIDTH << (MAG_SHIFT + 1);
+ DrawStamp (&s);
+ }
+
+ BuildObjectList ();
+
+ DrawBatch (DisplayArray, DisplayLinks, 0);
+
+ // Draw the lander while is still alive and keep drawing for a few
+ // frames while it is exploding
+ if (crew_left || damage_index || explosion_index < 3)
+ {
+ lander_s.origin.x = SURFACE_WIDTH >> 1;
+ lander_s.origin.y = (SURFACE_HEIGHT >> 1) + landingOffset;
+ lander_s.frame = LanderFrame[0];
+
+ if (landingOffset != ON_THE_GROUND)
+ { // Landing, draw a shadow
+ shadow_s.origin.x = lander_s.origin.y + (SURFACE_WIDTH >> 1) - (SURFACE_HEIGHT >> 1);//2;
+ shadow_s.origin.y = lander_s.origin.y;
+ shadow_s.frame = lander_s.frame;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledStamp (&shadow_s);
+ }
+
+ if (damage_index == 0)
+ { // No damage -- normal lander
+ DrawStamp (&lander_s);
+ }
+ else if (shieldHit)
+ { // Was protected by a shield
+ --damage_index;
+ if (damage_index > 0)
+ {
+ shield_s.origin = lander_s.origin;
+ shield_s.frame = SetEquFrameIndex (
+ LanderFrame[4], lander_s.frame);
+
+ // XXX: Shouldn't this color-cycle with damage_index?
+ // damage_index is used, but only as a VGA index!
+ /*SetContextForeGroundColor (BUILD_COLOR (
+ MAKE_RGB15 (0x1F, 0x1F, 0x1F) | 0x8000,
+ damage_index));*/
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), damage_index));
+ DrawFilledStamp (&shield_s);
+ }
+ DrawStamp (&lander_s);
+ }
+ else
+ { // Direct hit, no shield
+ --damage_index;
+ SetContextForeGroundColor (
+ DamageColorCycle (BLACK_COLOR, damage_index));
+ DrawFilledStamp (&lander_s);
+ }
+ }
+
+ if (landingOffset == ON_THE_GROUND && crew_left
+ && GetPredLink (DisplayLinks) != END_OF_LIST)
+ CheckObjectCollision (END_OF_LIST);
+
+ {
+ PLANETSIDE_DESC *pPSD = planetSideDesc;
+ if (pPSD->NumFrames)
+ {
+ --pPSD->NumFrames;
+ SetContextForeGroundColor (pPSD->ColorCycle[pPSD->NumFrames >> 1]);
+
+ pPSD->MineralText[0].baseline.x -= dx;
+ pPSD->MineralText[0].baseline.y -= dy;
+ font_DrawText (&pPSD->MineralText[0]);
+ pPSD->MineralText[1].baseline.x =
+ pPSD->MineralText[0].baseline.x;
+ pPSD->MineralText[1].baseline.y =
+ pPSD->MineralText[0].baseline.y + 7;
+ font_DrawText (&pPSD->MineralText[1]);
+ pPSD->MineralText[2].baseline.x =
+ pPSD->MineralText[1].baseline.x;
+ pPSD->MineralText[2].baseline.y =
+ pPSD->MineralText[1].baseline.y + 7;
+ font_DrawText (&pPSD->MineralText[2]);
+ }
+ }
+
+ RedrawSurfaceScan (&new_pt);
+
+ if (lander_flags & KILL_CREW)
+ DeltaLanderCrew (-1, LIGHTNING_DISASTER);
+
+ UnbatchGraphics ();
+
+ SetContext (OldContext);
+}
+
+static void
+animationInterframe (TimeCount *TimeIn, COUNT periods)
+{
+#define ANIM_FRAME_RATE (ONE_SECOND / 30)
+
+ for ( ; periods; --periods)
+ {
+ RotatePlanetSphere (TRUE);
+
+ SleepThreadUntil (*TimeIn + ANIM_FRAME_RATE);
+ *TimeIn = GetTimeCounter ();
+ }
+}
+
+static void
+AnimateLaunch (FRAME farray)
+{
+ RECT r;
+ STAMP s;
+ COUNT num_frames;
+ TimeCount NextTime;
+
+ SetContext (PlanetContext);
+
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = 0;
+ r.extent.height = 0;
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = farray;
+
+ for (num_frames = GetFrameCount (s.frame); num_frames; --num_frames)
+ {
+ NextTime = GetTimeCounter () + (ONE_SECOND / 22);
+
+ BatchGraphics ();
+ RepairBackRect (&r);
+#ifdef SPIN_ON_LAUNCH
+ RotatePlanetSphere (FALSE);
+#else
+ DrawDefaultPlanetSphere ();
+#endif
+ DrawStamp (&s);
+ UnbatchGraphics ();
+
+ GetFrameRect (s.frame, &r);
+ s.frame = IncFrameIndex (s.frame);
+
+ SleepThreadUntil (NextTime);
+ }
+
+ RepairBackRect (&r);
+}
+
+static void
+AnimateLanderWarmup (void)
+{
+ SIZE num_crew;
+ STAMP s;
+ CONTEXT OldContext;
+ TimeCount TimeIn = GetTimeCounter ();
+
+ OldContext = SetContext (RadarContext);
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (LanderFrame[0],
+ (ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 1);
+
+ DrawStamp (&s);
+
+ animationInterframe (&TimeIn, 2);
+
+ for (num_crew = 0; num_crew < (NUM_CREW_COLS * NUM_CREW_ROWS)
+ && GLOBAL_SIS (CrewEnlisted); ++num_crew)
+ {
+ animationInterframe (&TimeIn, 1);
+
+ DeltaSISGauges (-1, 0, 0);
+ DeltaLanderCrew (1, 0);
+ }
+
+ animationInterframe (&TimeIn, 2);
+
+ if (GET_GAME_STATE (IMPROVED_LANDER_SHOT))
+ s.frame = SetAbsFrameIndex (s.frame, 58);
+ else
+ s.frame = SetAbsFrameIndex (s.frame,
+ (ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 2);
+ DrawStamp (&s);
+
+ animationInterframe (&TimeIn, 2);
+
+ if (GET_GAME_STATE (IMPROVED_LANDER_SPEED))
+ s.frame = SetAbsFrameIndex (s.frame, 57);
+ else
+ {
+ s.frame = SetAbsFrameIndex (s.frame,
+ (ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 3);
+ DrawStamp (&s);
+
+ animationInterframe (&TimeIn, 2);
+
+ s.frame = IncFrameIndex (s.frame);
+ }
+ DrawStamp (&s);
+
+ if (GET_GAME_STATE (IMPROVED_LANDER_CARGO))
+ {
+ animationInterframe (&TimeIn, 2);
+
+ s.frame = SetAbsFrameIndex (s.frame, 59);
+ DrawStamp (&s);
+ }
+
+ animationInterframe (&TimeIn, 2);
+
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_DEPARTS),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY + 1);
+}
+
+static void
+InitPlanetSide (POINT pt)
+{
+ // Adjust landing location by a random jitter.
+#define RANDOM_MISS 64
+ // Jitter the X landing point.
+ pt.x -= RANDOM_MISS - TFB_Random () % (RANDOM_MISS << 1);
+ if (pt.x < 0)
+ pt.x += (MAP_WIDTH << MAG_SHIFT);
+ else if (pt.x >= (MAP_WIDTH << MAG_SHIFT))
+ pt.x -= (MAP_WIDTH << MAG_SHIFT);
+
+ // Jitter the Y landing point.
+ pt.y -= RANDOM_MISS - TFB_Random () % (RANDOM_MISS << 1);
+ if (pt.y < 0)
+ pt.y = 0;
+ else if (pt.y >= (MAP_HEIGHT << MAG_SHIFT))
+ pt.y = (MAP_HEIGHT << MAG_SHIFT) - 1;
+
+ curLanderLoc = pt;
+
+ SetContext (PlanetContext);
+ SetContextFont (TinyFont);
+
+ {
+ RECT r;
+
+ GetContextClipRect (&r);
+
+ SetTransitionSource (&r);
+ BatchGraphics ();
+
+ {
+ STAMP s;
+
+ // Note - This code is the same as in ScrollPlanetSize,
+ // Display planet area, accounting for horizontal wrapping if
+ // near the edges.
+ ClearDrawable ();
+ s.origin.x = -pt.x + (SURFACE_WIDTH >> 1);
+ s.origin.y = -pt.y + (SURFACE_HEIGHT >> 1);
+ s.frame = pSolarSysState->Orbit.TopoZoomFrame;
+ DrawStamp (&s);
+ s.origin.x += MAP_WIDTH << MAG_SHIFT;
+ DrawStamp (&s);
+ s.origin.x -= MAP_WIDTH << (MAG_SHIFT + 1);
+ DrawStamp (&s);
+ }
+
+ ScreenTransition (3, &r);
+ UnbatchGraphics ();
+ }
+
+
+ SET_GAME_STATE (PLANETARY_LANDING, 1);
+}
+
+static void
+LanderFire (SIZE facing)
+{
+#define SHUTTLE_FIRE_WAIT 15
+ HELEMENT hWeaponElement;
+ SIZE wdx, wdy;
+ ELEMENT *WeaponElementPtr;
+ COUNT angle;
+
+ hWeaponElement = AllocElement ();
+ if (hWeaponElement == NULL)
+ return;
+
+ LockElement (hWeaponElement, &WeaponElementPtr);
+
+ WeaponElementPtr->playerNr = PS_HUMAN_PLAYER;
+ WeaponElementPtr->mass_points = 1;
+ WeaponElementPtr->life_span = 12;
+ WeaponElementPtr->state_flags = FINITE_LIFE;
+ WeaponElementPtr->next.location = curLanderLoc;
+
+ SetPrimType (&DisplayArray[WeaponElementPtr->PrimIndex], STAMP_PRIM);
+ DisplayArray[WeaponElementPtr->PrimIndex].Object.Stamp.frame =
+ SetAbsFrameIndex (LanderFrame[0],
+ /* shot images immediately follow the lander images */
+ facing + ANGLE_TO_FACING (FULL_CIRCLE));
+
+ if (!CurrentInputState.key[PlayerControls[0]][KEY_UP])
+ {
+ wdx = 0;
+ wdy = 0;
+ }
+ else
+ {
+ GetCurrentVelocityComponents (&GLOBAL (velocity), &wdx, &wdy);
+ }
+
+ angle = FACING_TO_ANGLE (facing);
+ SetVelocityComponents (
+ &WeaponElementPtr->velocity,
+ COSINE (angle, WORLD_TO_VELOCITY (2 * 3)) + wdx,
+ SINE (angle, WORLD_TO_VELOCITY (2 * 3)) + wdy);
+
+ UnlockElement (hWeaponElement);
+
+ InsertElement (hWeaponElement, GetHeadElement ());
+
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_SHOOTS),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY);
+}
+
+static BOOLEAN
+LanderExplosion (void)
+{
+ HELEMENT hExplosionElement;
+ ELEMENT *ExplosionElementPtr;
+
+ hExplosionElement = AllocElement ();
+ if (!hExplosionElement)
+ return FALSE;
+
+ LockElement (hExplosionElement, &ExplosionElementPtr);
+
+ ExplosionElementPtr->playerNr = PS_HUMAN_PLAYER;
+ ExplosionElementPtr->mass_points = DEATH_EXPLOSION;
+ ExplosionElementPtr->state_flags = FINITE_LIFE;
+ ExplosionElementPtr->next.location = curLanderLoc;
+ ExplosionElementPtr->preprocess_func = object_animation;
+ // Animation advances every 3rd frame
+ ExplosionElementPtr->turn_wait = MAKE_BYTE (2, 2);
+ ExplosionElementPtr->life_span = EXPLOSION_LIFE
+ * (LONIBBLE (ExplosionElementPtr->turn_wait) + 1);
+
+ SetPrimType (&DisplayArray[ExplosionElementPtr->PrimIndex],
+ STAMP_PRIM);
+ DisplayArray[ExplosionElementPtr->PrimIndex].Object.Stamp.frame =
+ SetAbsFrameIndex (LanderFrame[0], 46);
+
+ UnlockElement (hExplosionElement);
+
+ InsertElement (hExplosionElement, GetHeadElement ());
+
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_DESTROYED),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY + 1);
+
+ return TRUE;
+}
+
+static BOOLEAN
+DoPlanetSide (LanderInputState *pMS)
+{
+ SIZE dx = 0;
+ SIZE dy = 0;
+
+#define SHUTTLE_TURN_WAIT 2
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return (FALSE);
+
+ if (!pMS->Initialized)
+ {
+ COUNT landerSpeedNumer;
+ COUNT angle;
+
+ pMS->Initialized = TRUE;
+
+ turn_wait = 0;
+ weapon_wait = 0;
+
+ angle = FACING_TO_ANGLE (GetFrameIndex (LanderFrame[0]));
+ landerSpeedNumer = GET_GAME_STATE (IMPROVED_LANDER_SPEED) ?
+ WORLD_TO_VELOCITY (2 * 14) :
+ WORLD_TO_VELOCITY (2 * 8);
+
+#ifdef FAST_FAST
+landerSpeedNumer = WORLD_TO_VELOCITY (48);
+#endif
+
+ SetVelocityComponents (&GLOBAL (velocity),
+ COSINE (angle, landerSpeedNumer) / LANDER_SPEED_DENOM,
+ SINE (angle, landerSpeedNumer) / LANDER_SPEED_DENOM);
+
+ return TRUE;
+ }
+ else if (crew_left /* alive and taking off */
+ && ((CurrentInputState.key[PlayerControls[0]][KEY_ESCAPE] ||
+ CurrentInputState.key[PlayerControls[0]][KEY_SPECIAL])
+ || planetSideDesc->InTransit))
+ {
+ return FALSE;
+ }
+ else if (!crew_left && !damage_index)
+ { // Dead, damage dealt, and exploding
+ if (explosion_index > EXPLOSION_LIFE + EXPLOSION_WAIT_FRAMES)
+ return FALSE;
+
+ if (explosion_index > EXPLOSION_LIFE)
+ { // Keep going until the wait expires
+ ++explosion_index;
+ }
+ else if (explosion_index == 0)
+ { // Start the explosion animation
+ if (LanderExplosion ())
+ {
+ // Advance the state only once we've got the element
+ ++explosion_index;
+ }
+ else
+ { // We could not allocate because the queue was full, but
+ // we will get another chance on the next iteration
+ log_add (log_Warning, "DoPlanetSide(): could not"
+ " allocate explosion element!");
+ }
+ }
+ }
+ else
+ {
+ if (crew_left)
+ {
+ SIZE index = GetFrameIndex (LanderFrame[0]);
+ if (turn_wait)
+ --turn_wait;
+ else if (CurrentInputState.key[PlayerControls[0]][KEY_LEFT] ||
+ CurrentInputState.key[PlayerControls[0]][KEY_RIGHT])
+ {
+ COUNT landerSpeedNumer;
+ COUNT angle;
+
+ if (CurrentInputState.key[PlayerControls[0]][KEY_LEFT])
+ --index;
+ else
+ ++index;
+
+ index = NORMALIZE_FACING (index);
+ LanderFrame[0] = SetAbsFrameIndex (LanderFrame[0], index);
+
+ angle = FACING_TO_ANGLE (index);
+ landerSpeedNumer = GET_GAME_STATE (IMPROVED_LANDER_SPEED) ?
+ WORLD_TO_VELOCITY (2 * 14) :
+ WORLD_TO_VELOCITY (2 * 8);
+
+#ifdef FAST_FAST
+landerSpeedNumer = WORLD_TO_VELOCITY (48);
+#endif
+
+ SetVelocityComponents (&GLOBAL (velocity),
+ COSINE (angle, landerSpeedNumer) / LANDER_SPEED_DENOM,
+ SINE (angle, landerSpeedNumer) / LANDER_SPEED_DENOM);
+
+ turn_wait = SHUTTLE_TURN_WAIT;
+ }
+
+ if (!CurrentInputState.key[PlayerControls[0]][KEY_UP])
+ {
+ dx = 0;
+ dy = 0;
+ }
+ else
+ GetNextVelocityComponents (&GLOBAL (velocity), &dx, &dy, 1);
+
+ if (weapon_wait)
+ --weapon_wait;
+ else if (CurrentInputState.key[PlayerControls[0]][KEY_WEAPON])
+ {
+ LanderFire (index);
+
+ weapon_wait = SHUTTLE_FIRE_WAIT;
+ if (GET_GAME_STATE (IMPROVED_LANDER_SHOT))
+ weapon_wait >>= 1;
+ }
+ }
+ }
+
+ ScrollPlanetSide (dx, dy, ON_THE_GROUND);
+
+ SleepThreadUntil (pMS->NextTime);
+ // NOTE: The rate is not stabilized
+ pMS->NextTime = GetTimeCounter () + PLANET_SIDE_RATE;
+
+ return TRUE;
+}
+
+void
+FreeLanderData (void)
+{
+ COUNT i;
+ COUNT landerFrameCount;
+
+ if (LanderFrame[0] == NULL)
+ return;
+
+ for (i = 0; i < NUM_ORBIT_THEMES; ++i)
+ {
+ DestroyMusic (OrbitMusic[i]);
+ OrbitMusic[i] = 0;
+ }
+
+ DestroySound (ReleaseSound (LanderSounds));
+ LanderSounds = 0;
+
+ landerFrameCount = sizeof (LanderFrame) / sizeof (LanderFrame[0]);
+ for (i = 0; i < landerFrameCount; ++i)
+ {
+ DestroyDrawable (ReleaseDrawable (LanderFrame[i]));
+ LanderFrame[i] = 0;
+ }
+}
+
+void
+LoadLanderData (void)
+{
+ if (LanderFrame[0] != 0)
+ return;
+
+ LanderFrame[0] =
+ CaptureDrawable (LoadGraphic (LANDER_MASK_PMAP_ANIM));
+ LanderFrame[1] =
+ CaptureDrawable (LoadGraphic (QUAKE_MASK_PMAP_ANIM));
+ LanderFrame[2] =
+ CaptureDrawable (LoadGraphic (LIGHTNING_MASK_ANIM));
+ LanderFrame[3] =
+ CaptureDrawable (LoadGraphic (LAVA_MASK_PMAP_ANIM));
+ LanderFrame[4] =
+ CaptureDrawable (LoadGraphic (LANDER_SHIELD_MASK_ANIM));
+ LanderFrame[5] =
+ CaptureDrawable (LoadGraphic (LANDER_LAUNCH_MASK_PMAP_ANIM));
+ LanderFrame[6] =
+ CaptureDrawable (LoadGraphic (LANDER_RETURN_MASK_PMAP_ANIM));
+ LanderFrame[7] =
+ CaptureDrawable (LoadGraphic (ORBIT_VIEW_ANIM));
+
+ LanderSounds = CaptureSound (LoadSound (LANDER_SOUNDS));
+
+ {
+ COUNT i;
+
+ for (i = 0; i < NUM_ORBIT_THEMES; ++i)
+ OrbitMusic[i] = load_orbit_theme (i);
+ }
+}
+
+void
+SetPlanetMusic (BYTE planet_type)
+{
+ LanderMusic = OrbitMusic[planet_type % NUM_ORBIT_THEMES];
+}
+
+static void
+ReturnToOrbit (void)
+{
+ CONTEXT OldContext;
+ RECT r;
+
+ OldContext = SetContext (PlanetContext);
+ GetContextClipRect (&r);
+
+ SetTransitionSource (&r);
+ BatchGraphics ();
+ DrawStarBackGround ();
+ DrawPlanetSurfaceBorder ();
+ RedrawSurfaceScan (NULL);
+ ScreenTransition (3, &r);
+ UnbatchGraphics ();
+
+ SetContext (OldContext);
+}
+
+static void
+IdlePlanetSide (LanderInputState *inputState, TimeCount howLong)
+{
+#define IDLE_OFFSET
+ TimeCount TimeOut = GetTimeCounter () + howLong;
+
+ while (GetTimeCounter () < TimeOut)
+ {
+ // 10 to clear the lander off of the screen
+ ScrollPlanetSide (0, 0, -(SURFACE_HEIGHT / 2 + 10));
+ SleepThreadUntil (inputState->NextTime);
+ inputState->NextTime += PLANET_SIDE_RATE;
+ }
+}
+
+static void
+LandingTakeoffSequence (LanderInputState *inputState, BOOLEAN landing)
+{
+// We cannot solve a quadratic equation in a macro, so use a sensible max
+#define MAX_OFFSETS 20
+// 10 to clear the lander off of the screen
+#define DISTANCE_COVERED (SURFACE_HEIGHT / 2 + 10)
+ int landingOfs[MAX_OFFSETS];
+ int start;
+ int end;
+ int delta;
+ int index;
+
+ // Produce smooth acceleration deltas from a simple 1..x progression
+ delta = 0;
+ for (index = 0; index < MAX_OFFSETS && delta < DISTANCE_COVERED; ++index)
+ {
+ delta += index + 1;
+ landingOfs[index] = -delta;
+ }
+ assert (delta >= DISTANCE_COVERED && "Increase MAX_OFFSETS!");
+
+ if (landing)
+ {
+ start = index - 1;
+ end = -1;
+ delta = -1;
+ }
+ else
+ { // takeoff
+ start = 0;
+ end = index;
+ delta = +1;
+ }
+
+ if (landing)
+ IdlePlanetSide (inputState, ONE_SECOND);
+
+ // Draw the landing/takeoff lander positions
+ for (index = start; index != end; index += delta)
+ {
+ ScrollPlanetSide (0, 0, landingOfs[index]);
+ SleepThreadUntil (inputState->NextTime);
+ inputState->NextTime += PLANET_SIDE_RATE;
+ }
+
+ if (!landing)
+ IdlePlanetSide (inputState, ONE_SECOND / 2);
+}
+
+void
+SetLanderTakeoff (void)
+{
+ assert (planetSideDesc != NULL);
+ if (planetSideDesc)
+ planetSideDesc->InTransit = TRUE;
+}
+
+// Returns whether the lander is still alive at the end of the sequence
+bool
+KillLanderCrewSeq (COUNT numKilled, DWORD period)
+{
+ TimeCount TimeOut;
+ COUNT i;
+
+ TimeOut = GetTimeCounter ();
+ for (i = 0; i < numKilled && crew_left; ++i)
+ {
+ TimeOut += period;
+ DeltaLanderCrew (-1, LANDER_INJURED);
+ SleepThreadUntil (TimeOut);
+ }
+
+ return crew_left > 0;
+}
+
+// Maps a temperature to a (0-7) hazard rating.
+// Thermal hazards aren't exposed to the user as a hazard number,
+// but the code still works with them that way.
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof (*array))
+unsigned
+GetThermalHazardRating (int temp)
+{
+ static const int tempBreakpoints[] = { 50, 100, 150, 250, 350, 550, 800 };
+ const size_t numBreakpoints = ARRAY_SIZE (tempBreakpoints);
+ unsigned i;
+
+ for (i = 0; i < numBreakpoints; ++i)
+ {
+ if (temp < tempBreakpoints[i])
+ return i;
+ }
+
+ return numBreakpoints;
+}
+
+// Given a hazard type and rating, return the chance (out of 256) of the hazard
+// being generated.
+static BYTE
+GetHazardChance (int hazardType, unsigned HazardRating)
+{
+ static const BYTE TectonicsChanceTab[] = {0*3, 0*3, 1*3, 2*3, 4*3, 8*3, 16*3, 32*3};
+ static const BYTE WeatherChanceTab [] = {0*3, 0*3, 1*3, 2*3, 3*3, 6*3, 12*3, 24*3};
+ static const BYTE FireChanceTab [] = {0*3, 0*3, 1*3, 2*3, 4*3, 12*3, 24*3, 48*3};
+
+ switch (hazardType)
+ {
+ case EARTHQUAKE_DISASTER:
+ return TectonicsChanceTab[HazardRating];
+ case LIGHTNING_DISASTER:
+ return WeatherChanceTab[HazardRating];
+ case LAVASPOT_DISASTER:
+ return FireChanceTab[HazardRating];
+ }
+
+ return 0;
+}
+
+void
+PlanetSide (POINT planetLoc)
+{
+ SIZE index;
+ LanderInputState landerInputState;
+ PLANETSIDE_DESC PSD;
+
+ memset (&PSD, 0, sizeof (PSD));
+ PSD.InTransit = TRUE;
+
+ // Set our chances of hazards occurring.
+ PSD.TectonicsChance = GetHazardChance (EARTHQUAKE_DISASTER,
+ pSolarSysState->SysInfo.PlanetInfo.Tectonics);
+ PSD.WeatherChance = GetHazardChance (LIGHTNING_DISASTER,
+ pSolarSysState->SysInfo.PlanetInfo.Weather);
+ PSD.FireChance = GetHazardChance (LAVASPOT_DISASTER, GetThermalHazardRating (
+ pSolarSysState->SysInfo.PlanetInfo.SurfaceTemperature));
+
+ PSD.ElementLevel = GetStorageBayCapacity () - GLOBAL_SIS (TotalElementMass);
+ PSD.MaxElementLevel = MAX_SCROUNGED;
+ if (GET_GAME_STATE (IMPROVED_LANDER_CARGO))
+ PSD.MaxElementLevel <<= 1;
+ if (PSD.ElementLevel < PSD.MaxElementLevel)
+ PSD.MaxElementLevel = PSD.ElementLevel;
+ PSD.ElementLevel = 0;
+
+ PSD.MineralText[0].align = ALIGN_CENTER;
+ PSD.MineralText[0].pStr = PSD.AmountBuf;
+ PSD.MineralText[1] = PSD.MineralText[0];
+ PSD.MineralText[2] = PSD.MineralText[1];
+
+ PSD.ColorCycle[0] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x03, 0x00), 0x7F);
+ PSD.ColorCycle[1] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D);
+ PSD.ColorCycle[2] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x11, 0x00), 0x7B);
+ PSD.ColorCycle[3] = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x71);
+ for (index = 4; index < (NUM_TEXT_FRAMES >> 1) - 4; ++index)
+ {
+ PSD.ColorCycle[index] =
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F);
+ }
+ PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 4] =
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x71);
+ PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 3] =
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x11, 0x00), 0x7B);
+ PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 2] =
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D);
+ PSD.ColorCycle[(NUM_TEXT_FRAMES >> 1) - 1] =
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x03, 0x00), 0x7F);
+ planetSideDesc = &PSD;
+
+ index = NORMALIZE_FACING (TFB_Random ());
+ LanderFrame[0] = SetAbsFrameIndex (LanderFrame[0], index);
+ crew_left = 0;
+ damage_index = 0;
+ explosion_index = 0;
+
+ AnimateLanderWarmup ();
+ AnimateLaunch (LanderFrame[5]);
+ InitPlanetSide (planetLoc);
+
+ landerInputState.NextTime = GetTimeCounter () + PLANET_SIDE_RATE;
+ LandingTakeoffSequence (&landerInputState, TRUE);
+ PSD.InTransit = FALSE;
+
+ landerInputState.Initialized = FALSE;
+ landerInputState.InputFunc = DoPlanetSide;
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ DoInput (&landerInputState, FALSE);
+
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ if (crew_left == 0)
+ {
+ --GLOBAL_SIS (NumLanders);
+ DrawLanders ();
+
+ ReturnToOrbit ();
+ }
+ else
+ {
+ PSD.InTransit = TRUE;
+ PlaySound (SetAbsSoundIndex (LanderSounds, LANDER_RETURNS),
+ NotPositional (), NULL, GAME_SOUND_PRIORITY + 1);
+
+ LandingTakeoffSequence (&landerInputState, FALSE);
+ ReturnToOrbit ();
+ AnimateLaunch (LanderFrame[6]);
+
+ DeltaSISGauges (crew_left, 0, 0);
+
+ if (PSD.ElementLevel)
+ {
+ for (index = 0; index < NUM_ELEMENT_CATEGORIES; ++index)
+ {
+ GLOBAL_SIS (ElementAmounts[index]) +=
+ PSD.ElementAmounts[index];
+ GLOBAL_SIS (TotalElementMass) +=
+ PSD.ElementAmounts[index];
+ }
+ DrawStorageBays (FALSE);
+ }
+
+ GLOBAL_SIS (TotalBioMass) += PSD.BiologicalLevel;
+ }
+ }
+
+ planetSideDesc = NULL;
+
+ {
+ HELEMENT hElement, hNextElement;
+
+ for (hElement = GetHeadElement ();
+ hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = _GetSuccLink (ElementPtr);
+ if (ElementPtr->state_flags & FINITE_LIFE)
+ {
+ UnlockElement (hElement);
+
+ RemoveElement (hElement);
+ FreeElement (hElement);
+
+ continue;
+ }
+ UnlockElement (hElement);
+ }
+ }
+
+ ZeroVelocityComponents (&GLOBAL (velocity));
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+}
+
+void
+InitLander (BYTE LanderFlags)
+{
+ RECT r;
+
+ SetContext (RadarContext);
+ BatchGraphics ();
+
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = RADAR_WIDTH;
+ r.extent.height = RADAR_HEIGHT;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ if (GLOBAL_SIS (NumLanders) || LanderFlags)
+ {
+ BYTE ShieldFlags, capacity_shift;
+ COUNT free_space;
+ STAMP s;
+
+ s.origin.x = 0; /* set up powered-down lander */
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (LanderFrame[0],
+ ANGLE_TO_FACING (FULL_CIRCLE) << 1);
+ DrawStamp (&s);
+ if (LanderFlags == 0)
+ {
+ ShieldFlags = GET_GAME_STATE (LANDER_SHIELDS);
+ capacity_shift = GET_GAME_STATE (IMPROVED_LANDER_CARGO);
+ }
+ else
+ {
+ ShieldFlags = (unsigned char)(LanderFlags &
+ ((1 << EARTHQUAKE_DISASTER)
+ | (1 << BIOLOGICAL_DISASTER)
+ | (1 << LIGHTNING_DISASTER)
+ | (1 << LAVASPOT_DISASTER)));
+ s.frame = IncFrameIndex (s.frame);
+ DrawStamp (&s);
+ if (LanderFlags & (1 << (4 + 0)))
+ s.frame = SetAbsFrameIndex (s.frame, 57);
+ else
+ {
+ s.frame = SetAbsFrameIndex (s.frame,
+ (ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 3);
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ }
+ DrawStamp (&s);
+ if (!(LanderFlags & (1 << (4 + 1))))
+ capacity_shift = 0;
+ else
+ {
+ capacity_shift = 1;
+ s.frame = SetAbsFrameIndex (s.frame, 59);
+ DrawStamp (&s);
+ }
+ if (LanderFlags & (1 << (4 + 2)))
+ s.frame = SetAbsFrameIndex (s.frame, 58);
+ else
+ s.frame = SetAbsFrameIndex (s.frame,
+ (ANGLE_TO_FACING (FULL_CIRCLE) << 1) + 2);
+ DrawStamp (&s);
+ }
+
+ free_space = GetStorageBayCapacity () - GLOBAL_SIS (TotalElementMass);
+ if ((int)free_space < (int)(MAX_SCROUNGED << capacity_shift))
+ {
+ r.corner.x = 1;
+ r.extent.width = 4;
+ r.extent.height = MAX_SCROUNGED
+ - (free_space >> capacity_shift) + 1;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ }
+
+ s.frame = SetAbsFrameIndex (s.frame, 37);
+ if (ShieldFlags & (1 << EARTHQUAKE_DISASTER))
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ if (ShieldFlags & (1 << BIOLOGICAL_DISASTER))
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ if (ShieldFlags & (1 << LIGHTNING_DISASTER))
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ if (ShieldFlags & (1 << LAVASPOT_DISASTER))
+ DrawStamp (&s);
+ }
+
+ UnbatchGraphics ();
+}
diff --git a/src/uqm/planets/lander.h b/src/uqm/planets/lander.h
new file mode 100644
index 0000000..d41129b
--- /dev/null
+++ b/src/uqm/planets/lander.h
@@ -0,0 +1,88 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_PLANETS_LANDER_H_
+#define UQM_PLANETS_LANDER_H_
+
+#include "elemdata.h"
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+#include "libs/sndlib.h"
+#include "libs/timelib.h"
+#include "../element.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// Surface magnification shift (x4)
+#define MAG_SHIFT 2
+
+#define NUM_TEXT_FRAMES 32
+
+// XXX: This is a private type now. Move it to lander.c?
+// We may also want to merge it with LanderInputState.
+typedef struct
+{
+ BOOLEAN InTransit;
+ // Landing on or taking of from a planet.
+ // Setting it while landed will initiate takeoff.
+
+ COUNT ElementLevel;
+ COUNT MaxElementLevel;
+ COUNT BiologicalLevel;
+ COUNT ElementAmounts[NUM_ELEMENT_CATEGORIES];
+
+ COUNT NumFrames;
+ UNICODE AmountBuf[40];
+ TEXT MineralText[3];
+
+ Color ColorCycle[NUM_TEXT_FRAMES >> 1];
+
+ BYTE TectonicsChance;
+ BYTE WeatherChance;
+ BYTE FireChance;
+} PLANETSIDE_DESC;
+
+extern MUSIC_REF LanderMusic;
+
+extern void PlanetSide (POINT planetLoc);
+extern void DoDiscoveryReport (SOUND ReadOutSounds);
+extern void SetPlanetMusic (BYTE planet_type);
+extern void LoadLanderData (void);
+extern void FreeLanderData (void);
+
+extern void object_animation (ELEMENT *ElementPtr);
+
+extern void SetLanderTakeoff (void);
+extern bool KillLanderCrewSeq (COUNT numKilled, DWORD period);
+
+extern unsigned GetThermalHazardRating (int temp);
+
+// ELEMENT.playerNr constants
+enum
+{
+ PS_HUMAN_PLAYER,
+ PS_NON_PLAYER,
+};
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PLANETS_LANDER_H_ */
diff --git a/src/uqm/planets/lifeform.h b/src/uqm/planets/lifeform.h
new file mode 100644
index 0000000..fbe2d9d
--- /dev/null
+++ b/src/uqm/planets/lifeform.h
@@ -0,0 +1,75 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_PLANETS_LIFEFORM_H_
+#define UQM_PLANETS_LIFEFORM_H_
+
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define BEHAVIOR_HUNT (0 << 0)
+#define BEHAVIOR_FLEE (1 << 0)
+#define BEHAVIOR_UNPREDICTABLE (2 << 0)
+
+#define BEHAVIOR_MASK 0x03
+#define BEHAVIOR_SHIFT 0
+
+#define AWARENESS_LOW (0 << 2)
+#define AWARENESS_MEDIUM (1 << 2)
+#define AWARENESS_HIGH (2 << 2)
+
+#define AWARENESS_MASK 0x0C
+#define AWARENESS_SHIFT (BEHAVIOR_SHIFT + 2)
+
+#define SPEED_MOTIONLESS (0 << 4)
+#define SPEED_SLOW (1 << 4)
+#define SPEED_MEDIUM (2 << 4)
+#define SPEED_FAST (3 << 4)
+
+#define SPEED_MASK 0x30
+#define SPEED_SHIFT (AWARENESS_SHIFT + 2)
+
+#define DANGER_HARMLESS (0 << 6)
+#define DANGER_WEAK (1 << 6)
+#define DANGER_NORMAL (2 << 6)
+#define DANGER_MONSTROUS (3 << 6)
+
+#define DANGER_MASK 0xC0
+#define DANGER_SHIFT (SPEED_SHIFT + 2)
+
+#define NUM_CREATURE_TYPES 23
+#define NUM_SPECIAL_CREATURE_TYPES 3
+#define MAX_LIFE_VARIATION 3
+
+#define CREATURE_AWARE (BYTE)(1 << 7)
+
+typedef struct
+{
+ BYTE Attributes, ValueAndHitPoints;
+} LIFEFORM_DESC;
+
+extern const LIFEFORM_DESC CreatureData[];
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PLANETS_LIFEFORM_H */
diff --git a/src/uqm/planets/orbits.c b/src/uqm/planets/orbits.c
new file mode 100644
index 0000000..f1ad0f4
--- /dev/null
+++ b/src/uqm/planets/orbits.c
@@ -0,0 +1,629 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "planets.h"
+#include "../starmap.h"
+#include "libs/compiler.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+
+
+//#define DEBUG_ORBITS
+
+enum
+{
+ PLANET_NEVER = 0,
+ PLANET_RARE = 15,
+ PLANET_FEW = 63,
+ PLANET_COMMON = 127,
+ PLANET_ALWAYS = 255
+};
+
+static BYTE
+BlueDistribution (BYTE which_world)
+{
+ const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] =
+ {
+ PLANET_ALWAYS, /* OOLITE_WORLD */
+ PLANET_ALWAYS, /* YTTRIC_WORLD */
+ PLANET_ALWAYS, /* QUASI_DEGENERATE_WORLD */
+ PLANET_ALWAYS, /* LANTHANIDE_WORLD */
+ PLANET_ALWAYS, /* TREASURE_WORLD */
+ PLANET_ALWAYS, /* UREA_WORLD */
+ PLANET_ALWAYS, /* METAL_WORLD */
+ PLANET_ALWAYS, /* RADIOACTIVE_WORLD */
+ PLANET_ALWAYS, /* OPALESCENT_WORLD */
+ PLANET_ALWAYS, /* CYANIC_WORLD */
+ PLANET_ALWAYS, /* ACID_WORLD */
+ PLANET_ALWAYS, /* ALKALI_WORLD */
+ PLANET_ALWAYS, /* HALIDE_WORLD */
+ PLANET_ALWAYS, /* GREEN_WORLD */
+ PLANET_ALWAYS, /* COPPER_WORLD */
+ PLANET_ALWAYS, /* CARBIDE_WORLD */
+ PLANET_ALWAYS, /* ULTRAMARINE_WORLD */
+ PLANET_ALWAYS, /* NOBLE_WORLD */
+ PLANET_ALWAYS, /* AZURE_WORLD */
+ PLANET_NEVER, /* CHONDRITE_WORLD */
+ PLANET_NEVER, /* PURPLE_WORLD */
+ PLANET_NEVER, /* SUPER_DENSE_WORLD */
+ PLANET_NEVER, /* PELLUCID_WORLD */
+ PLANET_NEVER, /* DUST_WORLD */
+ PLANET_NEVER, /* CRIMSON_WORLD */
+ PLANET_NEVER, /* CIMMERIAN_WORLD */
+ PLANET_NEVER, /* INFRARED_WORLD */
+ PLANET_ALWAYS, /* SELENIC_WORLD */
+ PLANET_ALWAYS, /* AURIC_WORLD */
+
+ PLANET_ALWAYS, /* FLUORESCENT_WORLD */
+ PLANET_ALWAYS, /* ULTRAVIOLET_WORLD */
+ PLANET_ALWAYS, /* PLUTONIC_WORLD */
+ PLANET_NEVER, /* RAINBOW_WORLD */
+ PLANET_NEVER, /* SHATTERED_WORLD */
+ PLANET_ALWAYS, /* SAPPHIRE_WORLD */
+ PLANET_ALWAYS, /* ORGANIC_WORLD */
+ PLANET_ALWAYS, /* XENOLITHIC_WORLD */
+ PLANET_ALWAYS, /* REDUX_WORLD */
+ PLANET_ALWAYS, /* PRIMORDIAL_WORLD */
+ PLANET_NEVER, /* EMERALD_WORLD */
+ PLANET_ALWAYS, /* CHLORINE_WORLD */
+ PLANET_ALWAYS, /* MAGNETIC_WORLD */
+ PLANET_ALWAYS, /* WATER_WORLD */
+ PLANET_ALWAYS, /* TELLURIC_WORLD */
+ PLANET_NEVER, /* HYDROCARBON_WORLD */
+ PLANET_NEVER, /* IODINE_WORLD */
+ PLANET_NEVER, /* VINYLOGOUS_WORLD */
+ PLANET_NEVER, /* RUBY_WORLD */
+ PLANET_NEVER, /* MAGMA_WORLD */
+ PLANET_NEVER, /* MAROON_WORLD */
+
+ PLANET_ALWAYS, /* BLU_GAS_GIANT */
+ PLANET_ALWAYS, /* CYA_GAS_GIANT */
+ PLANET_ALWAYS, /* GRN_GAS_GIANT */
+ PLANET_ALWAYS, /* GRY_GAS_GIANT */
+ PLANET_ALWAYS, /* ORA_GAS_GIANT */
+ PLANET_ALWAYS, /* PUR_GAS_GIANT */
+ PLANET_ALWAYS, /* RED_GAS_GIANT */
+ PLANET_ALWAYS, /* VIO_GAS_GIANT */
+ PLANET_ALWAYS, /* YEL_GAS_GIANT */
+ };
+
+ return (PlanetDistribution[which_world]);
+}
+
+static BYTE
+GreenDistribution (BYTE which_world)
+{
+ const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] =
+ {
+ PLANET_NEVER, /* OOLITE_WORLD */
+ PLANET_NEVER, /* YTTRIC_WORLD */
+ PLANET_ALWAYS, /* QUASI_DEGENERATE_WORLD */
+ PLANET_ALWAYS, /* LANTHANIDE_WORLD */
+ PLANET_ALWAYS, /* TREASURE_WORLD */
+ PLANET_ALWAYS, /* UREA_WORLD */
+ PLANET_ALWAYS, /* METAL_WORLD */
+ PLANET_ALWAYS, /* RADIOACTIVE_WORLD */
+ PLANET_ALWAYS, /* OPALESCENT_WORLD */
+ PLANET_ALWAYS, /* CYANIC_WORLD */
+ PLANET_ALWAYS, /* ACID_WORLD */
+ PLANET_ALWAYS, /* ALKALI_WORLD */
+ PLANET_ALWAYS, /* HALIDE_WORLD */
+ PLANET_ALWAYS, /* GREEN_WORLD */
+ PLANET_ALWAYS, /* COPPER_WORLD */
+ PLANET_ALWAYS, /* CARBIDE_WORLD */
+ PLANET_ALWAYS, /* ULTRAMARINE_WORLD */
+ PLANET_ALWAYS, /* NOBLE_WORLD */
+ PLANET_ALWAYS, /* AZURE_WORLD */
+ PLANET_ALWAYS, /* CHONDRITE_WORLD */
+ PLANET_ALWAYS, /* PURPLE_WORLD */
+ PLANET_ALWAYS, /* SUPER_DENSE_WORLD */
+ PLANET_ALWAYS, /* PELLUCID_WORLD */
+ PLANET_NEVER, /* DUST_WORLD */
+ PLANET_NEVER, /* CRIMSON_WORLD */
+ PLANET_NEVER, /* CIMMERIAN_WORLD */
+ PLANET_NEVER, /* INFRARED_WORLD */
+ PLANET_ALWAYS, /* SELENIC_WORLD */
+ PLANET_ALWAYS, /* AURIC_WORLD */
+
+ PLANET_ALWAYS, /* FLUORESCENT_WORLD */
+ PLANET_ALWAYS, /* ULTRAVIOLET_WORLD */
+ PLANET_ALWAYS, /* PLUTONIC_WORLD */
+ PLANET_NEVER, /* RAINBOW_WORLD */
+ PLANET_NEVER, /* SHATTERED_WORLD */
+ PLANET_NEVER, /* SAPPHIRE_WORLD */
+ PLANET_ALWAYS, /* ORGANIC_WORLD */
+ PLANET_ALWAYS, /* XENOLITHIC_WORLD */
+ PLANET_ALWAYS, /* REDUX_WORLD */
+ PLANET_ALWAYS, /* PRIMORDIAL_WORLD */
+ PLANET_ALWAYS, /* EMERALD_WORLD */
+ PLANET_ALWAYS, /* CHLORINE_WORLD */
+ PLANET_ALWAYS, /* MAGNETIC_WORLD */
+ PLANET_ALWAYS, /* WATER_WORLD */
+ PLANET_ALWAYS, /* TELLURIC_WORLD */
+ PLANET_ALWAYS, /* HYDROCARBON_WORLD */
+ PLANET_ALWAYS, /* IODINE_WORLD */
+ PLANET_NEVER, /* VINYLOGOUS_WORLD */
+ PLANET_NEVER, /* RUBY_WORLD */
+ PLANET_NEVER, /* MAGMA_WORLD */
+ PLANET_NEVER, /* MAROON_WORLD */
+
+ PLANET_ALWAYS, /* BLU_GAS_GIANT */
+ PLANET_ALWAYS, /* CYA_GAS_GIANT */
+ PLANET_ALWAYS, /* GRN_GAS_GIANT */
+ PLANET_ALWAYS, /* GRY_GAS_GIANT */
+ PLANET_ALWAYS, /* ORA_GAS_GIANT */
+ PLANET_ALWAYS, /* PUR_GAS_GIANT */
+ PLANET_ALWAYS, /* RED_GAS_GIANT */
+ PLANET_ALWAYS, /* VIO_GAS_GIANT */
+ PLANET_ALWAYS, /* YEL_GAS_GIANT */
+ };
+
+ return (PlanetDistribution[which_world]);
+}
+
+static BYTE
+OrangeDistribution (BYTE which_world)
+{
+ const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] =
+ {
+ PLANET_NEVER, /* OOLITE_WORLD */
+ PLANET_NEVER, /* YTTRIC_WORLD */
+ PLANET_NEVER, /* QUASI_DEGENERATE_WORLD */
+ PLANET_NEVER, /* LANTHANIDE_WORLD */
+ PLANET_NEVER, /* TREASURE_WORLD */
+ PLANET_ALWAYS, /* UREA_WORLD */
+ PLANET_NEVER, /* METAL_WORLD */
+ PLANET_ALWAYS, /* RADIOACTIVE_WORLD */
+ PLANET_NEVER, /* OPALESCENT_WORLD */
+ PLANET_NEVER, /* CYANIC_WORLD */
+ PLANET_ALWAYS, /* ACID_WORLD */
+ PLANET_ALWAYS, /* ALKALI_WORLD */
+ PLANET_ALWAYS, /* HALIDE_WORLD */
+ PLANET_ALWAYS, /* GREEN_WORLD */
+ PLANET_ALWAYS, /* COPPER_WORLD */
+ PLANET_ALWAYS, /* CARBIDE_WORLD */
+ PLANET_ALWAYS, /* ULTRAMARINE_WORLD */
+ PLANET_ALWAYS, /* NOBLE_WORLD */
+ PLANET_ALWAYS, /* AZURE_WORLD */
+ PLANET_ALWAYS, /* CHONDRITE_WORLD */
+ PLANET_ALWAYS, /* PURPLE_WORLD */
+ PLANET_ALWAYS, /* SUPER_DENSE_WORLD */
+ PLANET_ALWAYS, /* PELLUCID_WORLD */
+ PLANET_ALWAYS, /* DUST_WORLD */
+ PLANET_ALWAYS, /* CRIMSON_WORLD */
+ PLANET_ALWAYS, /* CIMMERIAN_WORLD */
+ PLANET_ALWAYS, /* INFRARED_WORLD */
+ PLANET_ALWAYS, /* SELENIC_WORLD */
+ PLANET_NEVER, /* AURIC_WORLD */
+
+ PLANET_NEVER, /* FLUORESCENT_WORLD */
+ PLANET_NEVER, /* ULTRAVIOLET_WORLD */
+ PLANET_NEVER, /* PLUTONIC_WORLD */
+ PLANET_NEVER, /* RAINBOW_WORLD */
+ PLANET_NEVER, /* SHATTERED_WORLD */
+ PLANET_NEVER, /* SAPPHIRE_WORLD */
+ PLANET_NEVER, /* ORGANIC_WORLD */
+ PLANET_NEVER, /* XENOLITHIC_WORLD */
+ PLANET_NEVER, /* REDUX_WORLD */
+ PLANET_NEVER, /* PRIMORDIAL_WORLD */
+ PLANET_NEVER, /* EMERALD_WORLD */
+ PLANET_ALWAYS, /* CHLORINE_WORLD */
+ PLANET_ALWAYS, /* MAGNETIC_WORLD */
+ PLANET_ALWAYS, /* WATER_WORLD */
+ PLANET_ALWAYS, /* TELLURIC_WORLD */
+ PLANET_ALWAYS, /* HYDROCARBON_WORLD */
+ PLANET_ALWAYS, /* IODINE_WORLD */
+ PLANET_ALWAYS, /* VINYLOGOUS_WORLD */
+ PLANET_NEVER, /* RUBY_WORLD */
+ PLANET_ALWAYS, /* MAGMA_WORLD */
+ PLANET_ALWAYS, /* MAROON_WORLD */
+
+ PLANET_ALWAYS, /* BLU_GAS_GIANT */
+ PLANET_ALWAYS, /* CYA_GAS_GIANT */
+ PLANET_ALWAYS, /* GRN_GAS_GIANT */
+ PLANET_ALWAYS, /* GRY_GAS_GIANT */
+ PLANET_ALWAYS, /* ORA_GAS_GIANT */
+ PLANET_ALWAYS, /* PUR_GAS_GIANT */
+ PLANET_ALWAYS, /* RED_GAS_GIANT */
+ PLANET_ALWAYS, /* VIO_GAS_GIANT */
+ PLANET_ALWAYS, /* YEL_GAS_GIANT */
+ };
+
+ return (PlanetDistribution[which_world]);
+}
+
+static BYTE
+RedDistribution (BYTE which_world)
+{
+ const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] =
+ {
+ PLANET_NEVER, /* OOLITE_WORLD */
+ PLANET_NEVER, /* YTTRIC_WORLD */
+ PLANET_NEVER, /* QUASI_DEGENERATE_WORLD */
+ PLANET_NEVER, /* LANTHANIDE_WORLD */
+ PLANET_NEVER, /* TREASURE_WORLD */
+ PLANET_ALWAYS, /* UREA_WORLD */
+ PLANET_ALWAYS, /* METAL_WORLD */
+ PLANET_NEVER, /* RADIOACTIVE_WORLD */
+ PLANET_NEVER, /* OPALESCENT_WORLD */
+ PLANET_NEVER, /* CYANIC_WORLD */
+ PLANET_NEVER, /* ACID_WORLD */
+ PLANET_NEVER, /* ALKALI_WORLD */
+ PLANET_NEVER, /* HALIDE_WORLD */
+ PLANET_NEVER, /* GREEN_WORLD */
+ PLANET_NEVER, /* COPPER_WORLD */
+ PLANET_NEVER, /* CARBIDE_WORLD */
+ PLANET_ALWAYS, /* ULTRAMARINE_WORLD */
+ PLANET_ALWAYS, /* NOBLE_WORLD */
+ PLANET_ALWAYS, /* AZURE_WORLD */
+ PLANET_ALWAYS, /* CHONDRITE_WORLD */
+ PLANET_ALWAYS, /* PURPLE_WORLD */
+ PLANET_ALWAYS, /* SUPER_DENSE_WORLD */
+ PLANET_ALWAYS, /* PELLUCID_WORLD */
+ PLANET_ALWAYS, /* DUST_WORLD */
+ PLANET_ALWAYS, /* CRIMSON_WORLD */
+ PLANET_ALWAYS, /* CIMMERIAN_WORLD */
+ PLANET_ALWAYS, /* INFRARED_WORLD */
+ PLANET_ALWAYS, /* SELENIC_WORLD */
+ PLANET_NEVER, /* AURIC_WORLD */
+
+ PLANET_NEVER, /* FLUORESCENT_WORLD */
+ PLANET_NEVER, /* ULTRAVIOLET_WORLD */
+ PLANET_NEVER, /* PLUTONIC_WORLD */
+ PLANET_NEVER, /* RAINBOW_WORLD */
+ PLANET_NEVER, /* SHATTERED_WORLD */
+ PLANET_NEVER, /* SAPPHIRE_WORLD */
+ PLANET_NEVER, /* ORGANIC_WORLD */
+ PLANET_NEVER, /* XENOLITHIC_WORLD */
+ PLANET_NEVER, /* REDUX_WORLD */
+ PLANET_NEVER, /* PRIMORDIAL_WORLD */
+ PLANET_NEVER, /* EMERALD_WORLD */
+ PLANET_NEVER, /* CHLORINE_WORLD */
+ PLANET_ALWAYS, /* MAGNETIC_WORLD */
+ PLANET_ALWAYS, /* WATER_WORLD */
+ PLANET_ALWAYS, /* TELLURIC_WORLD */
+ PLANET_ALWAYS, /* HYDROCARBON_WORLD */
+ PLANET_ALWAYS, /* IODINE_WORLD */
+ PLANET_ALWAYS, /* VINYLOGOUS_WORLD */
+ PLANET_ALWAYS, /* RUBY_WORLD */
+ PLANET_ALWAYS, /* MAGMA_WORLD */
+ PLANET_ALWAYS, /* MAROON_WORLD */
+
+ PLANET_ALWAYS, /* BLU_GAS_GIANT */
+ PLANET_ALWAYS, /* CYA_GAS_GIANT */
+ PLANET_ALWAYS, /* GRN_GAS_GIANT */
+ PLANET_ALWAYS, /* GRY_GAS_GIANT */
+ PLANET_ALWAYS, /* ORA_GAS_GIANT */
+ PLANET_ALWAYS, /* PUR_GAS_GIANT */
+ PLANET_ALWAYS, /* RED_GAS_GIANT */
+ PLANET_ALWAYS, /* VIO_GAS_GIANT */
+ PLANET_ALWAYS, /* YEL_GAS_GIANT */
+ };
+
+ return (PlanetDistribution[which_world]);
+}
+
+static BYTE
+WhiteDistribution (BYTE which_world)
+{
+ const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] =
+ {
+ PLANET_ALWAYS, /* OOLITE_WORLD */
+ PLANET_ALWAYS, /* YTTRIC_WORLD */
+ PLANET_ALWAYS, /* QUASI_DEGENERATE_WORLD */
+ PLANET_ALWAYS, /* LANTHANIDE_WORLD */
+ PLANET_ALWAYS, /* TREASURE_WORLD */
+ PLANET_ALWAYS, /* UREA_WORLD */
+ PLANET_ALWAYS, /* METAL_WORLD */
+ PLANET_ALWAYS, /* RADIOACTIVE_WORLD */
+ PLANET_ALWAYS, /* OPALESCENT_WORLD */
+ PLANET_ALWAYS, /* CYANIC_WORLD */
+ PLANET_ALWAYS, /* ACID_WORLD */
+ PLANET_ALWAYS, /* ALKALI_WORLD */
+ PLANET_ALWAYS, /* HALIDE_WORLD */
+ PLANET_NEVER, /* GREEN_WORLD */
+ PLANET_NEVER, /* COPPER_WORLD */
+ PLANET_NEVER, /* CARBIDE_WORLD */
+ PLANET_NEVER, /* ULTRAMARINE_WORLD */
+ PLANET_NEVER, /* NOBLE_WORLD */
+ PLANET_NEVER, /* AZURE_WORLD */
+ PLANET_NEVER, /* CHONDRITE_WORLD */
+ PLANET_NEVER, /* PURPLE_WORLD */
+ PLANET_NEVER, /* SUPER_DENSE_WORLD */
+ PLANET_NEVER, /* PELLUCID_WORLD */
+ PLANET_NEVER, /* DUST_WORLD */
+ PLANET_NEVER, /* CRIMSON_WORLD */
+ PLANET_NEVER, /* CIMMERIAN_WORLD */
+ PLANET_NEVER, /* INFRARED_WORLD */
+ PLANET_ALWAYS, /* SELENIC_WORLD */
+ PLANET_ALWAYS, /* AURIC_WORLD */
+
+ PLANET_ALWAYS, /* FLUORESCENT_WORLD */
+ PLANET_ALWAYS, /* ULTRAVIOLET_WORLD */
+ PLANET_ALWAYS, /* PLUTONIC_WORLD */
+ PLANET_NEVER, /* RAINBOW_WORLD */
+ PLANET_NEVER, /* SHATTERED_WORLD */
+ PLANET_ALWAYS, /* SAPPHIRE_WORLD */
+ PLANET_ALWAYS, /* ORGANIC_WORLD */
+ PLANET_ALWAYS, /* XENOLITHIC_WORLD */
+ PLANET_ALWAYS, /* REDUX_WORLD */
+ PLANET_ALWAYS, /* PRIMORDIAL_WORLD */
+ PLANET_ALWAYS, /* EMERALD_WORLD */
+ PLANET_NEVER, /* CHLORINE_WORLD */
+ PLANET_NEVER, /* MAGNETIC_WORLD */
+ PLANET_NEVER, /* WATER_WORLD */
+ PLANET_NEVER, /* TELLURIC_WORLD */
+ PLANET_NEVER, /* HYDROCARBON_WORLD */
+ PLANET_NEVER, /* IODINE_WORLD */
+ PLANET_ALWAYS, /* VINYLOGOUS_WORLD */
+ PLANET_ALWAYS, /* RUBY_WORLD */
+ PLANET_NEVER, /* MAGMA_WORLD */
+ PLANET_NEVER, /* MAROON_WORLD */
+
+ PLANET_ALWAYS, /* BLU_GAS_GIANT */
+ PLANET_ALWAYS, /* CYA_GAS_GIANT */
+ PLANET_ALWAYS, /* GRN_GAS_GIANT */
+ PLANET_ALWAYS, /* GRY_GAS_GIANT */
+ PLANET_ALWAYS, /* ORA_GAS_GIANT */
+ PLANET_ALWAYS, /* PUR_GAS_GIANT */
+ PLANET_ALWAYS, /* RED_GAS_GIANT */
+ PLANET_ALWAYS, /* VIO_GAS_GIANT */
+ PLANET_ALWAYS, /* YEL_GAS_GIANT */
+ };
+
+ return (PlanetDistribution[which_world]);
+}
+
+static BYTE
+YellowDistribution (BYTE which_world)
+{
+ const BYTE PlanetDistribution[NUMBER_OF_PLANET_TYPES] =
+ {
+ PLANET_NEVER, /* OOLITE_WORLD */
+ PLANET_NEVER, /* YTTRIC_WORLD */
+ PLANET_NEVER, /* QUASI_DEGENERATE_WORLD */
+ PLANET_NEVER, /* LANTHANIDE_WORLD */
+ PLANET_ALWAYS, /* TREASURE_WORLD */
+ PLANET_ALWAYS, /* UREA_WORLD */
+ PLANET_ALWAYS, /* METAL_WORLD */
+ PLANET_ALWAYS, /* RADIOACTIVE_WORLD */
+ PLANET_ALWAYS, /* OPALESCENT_WORLD */
+ PLANET_ALWAYS, /* CYANIC_WORLD */
+ PLANET_ALWAYS, /* ACID_WORLD */
+ PLANET_ALWAYS, /* ALKALI_WORLD */
+ PLANET_ALWAYS, /* HALIDE_WORLD */
+ PLANET_ALWAYS, /* GREEN_WORLD */
+ PLANET_ALWAYS, /* COPPER_WORLD */
+ PLANET_ALWAYS, /* CARBIDE_WORLD */
+ PLANET_ALWAYS, /* ULTRAMARINE_WORLD */
+ PLANET_ALWAYS, /* NOBLE_WORLD */
+ PLANET_ALWAYS, /* AZURE_WORLD */
+ PLANET_ALWAYS, /* CHONDRITE_WORLD */
+ PLANET_ALWAYS, /* PURPLE_WORLD */
+ PLANET_ALWAYS, /* SUPER_DENSE_WORLD */
+ PLANET_ALWAYS, /* PELLUCID_WORLD */
+ PLANET_ALWAYS, /* DUST_WORLD */
+ PLANET_ALWAYS, /* CRIMSON_WORLD */
+ PLANET_ALWAYS, /* CIMMERIAN_WORLD */
+ PLANET_ALWAYS, /* INFRARED_WORLD */
+ PLANET_ALWAYS, /* SELENIC_WORLD */
+ PLANET_ALWAYS, /* AURIC_WORLD */
+
+ PLANET_NEVER, /* FLUORESCENT_WORLD */
+ PLANET_NEVER, /* ULTRAVIOLET_WORLD */
+ PLANET_NEVER, /* PLUTONIC_WORLD */
+ PLANET_NEVER, /* RAINBOW_WORLD */
+ PLANET_NEVER, /* SHATTERED_WORLD */
+ PLANET_NEVER, /* SAPPHIRE_WORLD */
+ PLANET_ALWAYS, /* ORGANIC_WORLD */
+ PLANET_ALWAYS, /* XENOLITHIC_WORLD */
+ PLANET_ALWAYS, /* REDUX_WORLD */
+ PLANET_ALWAYS, /* PRIMORDIAL_WORLD */
+ PLANET_NEVER, /* EMERALD_WORLD */
+ PLANET_ALWAYS, /* CHLORINE_WORLD */
+ PLANET_ALWAYS, /* MAGNETIC_WORLD */
+ PLANET_ALWAYS, /* WATER_WORLD */
+ PLANET_ALWAYS, /* TELLURIC_WORLD */
+ PLANET_ALWAYS, /* HYDROCARBON_WORLD */
+ PLANET_ALWAYS, /* IODINE_WORLD */
+ PLANET_ALWAYS, /* VINYLOGOUS_WORLD */
+ PLANET_NEVER, /* RUBY_WORLD */
+ PLANET_ALWAYS, /* MAGMA_WORLD */
+ PLANET_ALWAYS, /* MAROON_WORLD */
+
+ PLANET_ALWAYS, /* BLU_GAS_GIANT */
+ PLANET_ALWAYS, /* CYA_GAS_GIANT */
+ PLANET_ALWAYS, /* GRN_GAS_GIANT */
+ PLANET_ALWAYS, /* GRY_GAS_GIANT */
+ PLANET_ALWAYS, /* ORA_GAS_GIANT */
+ PLANET_ALWAYS, /* PUR_GAS_GIANT */
+ PLANET_ALWAYS, /* RED_GAS_GIANT */
+ PLANET_ALWAYS, /* VIO_GAS_GIANT */
+ PLANET_ALWAYS, /* YEL_GAS_GIANT */
+ };
+
+ return (PlanetDistribution[which_world]);
+}
+
+#define DWARF_ROCK_DIST MIN_PLANET_RADIUS
+#define DWARF_GASG_DIST SCALE_RADIUS (12)
+
+#define GIANT_ROCK_DIST SCALE_RADIUS (8)
+#define GIANT_GASG_DIST SCALE_RADIUS (13)
+
+#define SUPERGIANT_ROCK_DIST SCALE_RADIUS (16)
+#define SUPERGIANT_GASG_DIST SCALE_RADIUS (33)
+
+void
+FillOrbits (SOLARSYS_STATE *system, BYTE NumPlanets,
+ PLANET_DESC *pBaseDesc, BOOLEAN TypesDefined)
+{ /* Generate Planets in orbit around star */
+ BYTE StarColor, PlanetCount, MaxPlanet;
+ BOOLEAN GeneratingMoons;
+ COUNT StarSize;
+ PLANET_DESC *pPD;
+ struct
+ {
+ COUNT MinRockyDist, MinGasGDist;
+ } Suns[] =
+ {
+ {DWARF_ROCK_DIST, DWARF_GASG_DIST},
+ {GIANT_ROCK_DIST, GIANT_GASG_DIST},
+ {SUPERGIANT_ROCK_DIST, SUPERGIANT_GASG_DIST},
+ };
+#ifdef DEBUG_ORBITS
+UNICODE buf[256];
+char stype[] = {'D', 'G', 'S'};
+char scolor[] = {'B', 'G', 'O', 'R', 'W', 'Y'};
+#endif /* DEBUG_ORBITS */
+
+ pPD = pBaseDesc;
+ StarSize = system->SunDesc[0].data_index;
+ StarColor = STAR_COLOR (CurStarDescPtr->Type);
+
+ if (NumPlanets == (BYTE)~0)
+ {
+#define MAX_GENERATED_PLANETS 9
+ // XXX: This is pretty funny. Instead of calling RNG once, like so:
+ // 1 + Random % MAX_GENERATED_PLANETS
+ // we spin in a loop until the result > 0.
+ // Note that this behavior must be kept to preserve the universe.
+ do
+ NumPlanets = LOWORD (RandomContext_Random (SysGenRNG))
+ % (MAX_GENERATED_PLANETS + 1);
+ while (NumPlanets == 0);
+ system->SunDesc[0].NumPlanets = NumPlanets;
+ }
+
+#ifdef DEBUG_ORBITS
+ GetClusterName (CurStarDescPtr, buf);
+ log_add (log_Debug, "cluster name = %s color = %c type = %c", buf,
+ scolor[STAR_COLOR (CurStarDescPtr->Type)],
+ stype[STAR_TYPE (CurStarDescPtr->Type)]);
+#endif /* DEBUG_ORBITS */
+ GeneratingMoons = (BOOLEAN) (pBaseDesc == system->MoonDesc);
+ if (GeneratingMoons)
+ MaxPlanet = FIRST_LARGE_ROCKY_WORLD;
+ else
+ MaxPlanet = NUMBER_OF_PLANET_TYPES;
+ PlanetCount = NumPlanets;
+ while (NumPlanets--)
+ {
+ BYTE chance;
+ DWORD rand_val;
+ COUNT min_radius, angle;
+ SIZE delta_r;
+ PLANET_DESC *pLocPD;
+
+ do
+ {
+ rand_val = RandomContext_Random (SysGenRNG);
+ if (TypesDefined)
+ rand_val = 0;
+ else
+ pPD->data_index =
+ (BYTE)(HIBYTE (LOWORD (rand_val)) % MaxPlanet);
+
+ chance = PLANET_NEVER;
+ switch (StarColor)
+ {
+ case BLUE_BODY:
+ chance = BlueDistribution (pPD->data_index);
+ break;
+ case GREEN_BODY:
+ chance = GreenDistribution (pPD->data_index);
+ break;
+ case ORANGE_BODY:
+ chance = OrangeDistribution (pPD->data_index);
+ break;
+ case RED_BODY:
+ chance = RedDistribution (pPD->data_index);
+ break;
+ case WHITE_BODY:
+ chance = WhiteDistribution (pPD->data_index);
+ break;
+ case YELLOW_BODY:
+ chance = YellowDistribution (pPD->data_index);
+ break;
+ }
+ } while (LOBYTE (LOWORD (rand_val)) >= chance);
+
+ if (pPD->data_index < FIRST_GAS_GIANT)
+ min_radius = Suns[StarSize].MinRockyDist;
+ else
+ min_radius = Suns[StarSize].MinGasGDist;
+RelocatePlanet:
+ rand_val = RandomContext_Random (SysGenRNG);
+ if (GeneratingMoons)
+ {
+ pPD->radius = MIN_MOON_RADIUS
+ + ((LOWORD (rand_val) % MAX_MOONS) * MOON_DELTA);
+ for (pLocPD = pPD - 1; pLocPD >= pBaseDesc; --pLocPD)
+ {
+ if (pPD->radius == pLocPD->radius)
+ goto RelocatePlanet;
+ }
+ pPD->NumPlanets = 0;
+ }
+ else
+ {
+ pPD->radius =
+ (LOWORD (rand_val) % (MAX_PLANET_RADIUS - min_radius))
+ + min_radius;
+ for (pLocPD = pPD - 1; pLocPD >= pBaseDesc; --pLocPD)
+ {
+ delta_r = UNSCALE_RADIUS (pLocPD->radius) / 5
+ - UNSCALE_RADIUS (pPD->radius) / 5;
+ if (delta_r < 0)
+ delta_r = -delta_r;
+ if (delta_r <= 1)
+ goto RelocatePlanet;
+ }
+ }
+
+ rand_val = RandomContext_Random (SysGenRNG);
+ angle = NORMALIZE_ANGLE (LOWORD (rand_val));
+ pPD->location.x = COSINE (angle, pPD->radius);
+ pPD->location.y = SINE (angle, pPD->radius);
+ pPD->rand_seed = MAKE_DWORD (pPD->location.x, pPD->location.y);
+
+ ++pPD;
+ }
+
+ {
+ BYTE i;
+
+ for (i = 0; i < PlanetCount; ++i)
+ {
+ BYTE j;
+
+ for (j = (BYTE)(PlanetCount - 1); j > i; --j)
+ {
+ if (pBaseDesc[i].radius > pBaseDesc[j].radius)
+ {
+ PLANET_DESC temp;
+
+ temp = pBaseDesc[i];
+ pBaseDesc[i] = pBaseDesc[j];
+ pBaseDesc[j] = temp;
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/uqm/planets/oval.c b/src/uqm/planets/oval.c
new file mode 100644
index 0000000..4112997
--- /dev/null
+++ b/src/uqm/planets/oval.c
@@ -0,0 +1,329 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../units.h"
+#include "libs/gfxlib.h"
+#include "libs/graphics/context.h"
+#include "libs/graphics/drawable.h"
+
+#include "planets.h"
+
+#define NUM_QUADS 4
+
+void
+DrawOval (RECT *pRect, BYTE num_off_pixels)
+{
+#define FIRST_QUAD (1 << 0)
+#define SECOND_QUAD (1 << 1)
+#define THIRD_QUAD (1 << 2)
+#define FOURTH_QUAD (1 << 3)
+ COUNT off;
+ COORD x, y;
+ SIZE A, B;
+ long Asquared, TwoAsquared,
+ Bsquared, TwoBsquared;
+ long d, dx, dy;
+ BYTE quad_visible;
+ LINE corners;
+ POINT mp;
+ PRIMITIVE prim[NUM_QUADS];
+ COUNT StartPrim;
+ RECT ClipRect;
+ BRESENHAM_LINE ClipLine;
+
+ ClipRect.corner.x = ClipRect.corner.y = 0;
+
+ corners.first.x = pRect->corner.x - ClipRect.corner.x;
+ corners.first.y = pRect->corner.y - ClipRect.corner.y;
+ corners.second.x = corners.first.x + pRect->extent.width - 1;
+ corners.second.y = corners.first.y + pRect->extent.height - 1;
+ if (corners.second.x <= corners.first.x
+ || corners.second.y <= corners.first.y)
+ {
+ if (corners.second.x < corners.first.x)
+ corners.second.x = corners.first.x;
+ if (corners.second.y < corners.first.y)
+ corners.second.y = corners.first.y;
+
+ DrawLine (&corners);
+ return;
+ }
+
+ ClipRect.extent.width = SIS_SCREEN_WIDTH;
+ ClipRect.extent.height = SIS_SCREEN_HEIGHT;
+
+ quad_visible = 0;
+ mp.x = (corners.first.x + corners.second.x) >> 1;
+ mp.y = (corners.first.y + corners.second.y) >> 1;
+ ClipRect.corner.x = ClipRect.corner.y = 0;
+
+ if (corners.first.y >= 0 && corners.first.y < ClipRect.extent.height
+ && corners.second.x >= 0 && corners.second.x < ClipRect.extent.width)
+ quad_visible |= FIRST_QUAD;
+ else
+ {
+ ClipLine.first.x = mp.x;
+ ClipLine.first.y = corners.first.y;
+ ClipLine.second.x = corners.second.x;
+ ClipLine.second.y = mp.y;
+ if (_clip_line (&ClipRect, &ClipLine))
+ quad_visible |= FIRST_QUAD;
+ }
+
+ if (corners.first.y >= 0 && corners.first.y < ClipRect.extent.height
+ && corners.first.x >= 0 && corners.first.x < ClipRect.extent.width)
+ quad_visible |= SECOND_QUAD;
+ else
+ {
+ ClipLine.first.x = mp.x;
+ ClipLine.first.y = corners.first.y;
+ ClipLine.second.x = corners.first.x;
+ ClipLine.second.y = mp.y;
+ if (_clip_line (&ClipRect, &ClipLine))
+ quad_visible |= SECOND_QUAD;
+ }
+
+ if (corners.second.y >= 0 && corners.second.y < ClipRect.extent.height
+ && corners.first.x >= 0 && corners.first.x < ClipRect.extent.width)
+ quad_visible |= THIRD_QUAD;
+ else
+ {
+ ClipLine.first.x = mp.x;
+ ClipLine.first.y = corners.second.y;
+ ClipLine.second.x = corners.first.x;
+ ClipLine.second.y = mp.y;
+ if (_clip_line (&ClipRect, &ClipLine))
+ quad_visible |= THIRD_QUAD;
+ }
+
+ if (corners.second.y >= 0 && corners.second.y < ClipRect.extent.height
+ && corners.second.x >= 0 && corners.second.x < ClipRect.extent.width)
+ quad_visible |= FOURTH_QUAD;
+ else
+ {
+ ClipLine.first.x = mp.x;
+ ClipLine.first.y = corners.second.y;
+ ClipLine.second.x = corners.second.x;
+ ClipLine.second.y = mp.y;
+ if (_clip_line (&ClipRect, &ClipLine))
+ quad_visible |= FOURTH_QUAD;
+ }
+
+ if (!quad_visible)
+ return;
+
+ StartPrim = END_OF_LIST;
+ for (x = 0; x < NUM_QUADS; ++x)
+ {
+ if (quad_visible & (1 << x))
+ {
+ SetPrimNextLink (&prim[x], StartPrim);
+ SetPrimType (&prim[x], POINT_PRIM);
+ SetPrimColor (&prim[x], _get_context_fg_color ());
+
+ StartPrim = x;
+ }
+ }
+
+ A = pRect->extent.width >> 1;
+ B = pRect->extent.height >> 1;
+
+ x = 0;
+ y = B;
+
+ Asquared = ((long)A * A) << 1;
+ Bsquared = ((long)B * B) << 1;
+ do
+ {
+ Asquared >>= 1;
+ Bsquared >>= 1;
+ TwoAsquared = Asquared << 1;
+ dy = TwoAsquared * B;
+ } while (dy / B != TwoAsquared);
+ TwoBsquared = Bsquared << 1;
+
+ dx = 0;
+ d = Bsquared - (dy >> 1) + (Asquared >> 2);
+
+ off = 0;
+ A += pRect->corner.x;
+ B += pRect->corner.y;
+ while (dx < dy)
+ {
+ if (off-- == 0)
+ {
+ prim[0].Object.Point.x = prim[3].Object.Point.x = A + x;
+ prim[0].Object.Point.y = prim[1].Object.Point.y = B - y;
+ prim[1].Object.Point.x = prim[2].Object.Point.x = A - x;
+ prim[2].Object.Point.y = prim[3].Object.Point.y = B + y;
+
+ DrawBatch (prim, StartPrim, 0);
+ off = num_off_pixels;
+ }
+
+ if (d > 0)
+ {
+ --y;
+ dy -= TwoAsquared;
+ d -= dy;
+ }
+
+ ++x;
+ dx += TwoBsquared;
+ d += Bsquared + dx;
+ }
+
+ d += ((((Asquared - Bsquared) * 3) >> 1) - (dx + dy)) >> 1;
+
+ while (y >= 0)
+ {
+ if (off-- == 0)
+ {
+ prim[0].Object.Point.x = prim[3].Object.Point.x = A + x;
+ prim[0].Object.Point.y = prim[1].Object.Point.y = B - y;
+ prim[1].Object.Point.x = prim[2].Object.Point.x = A - x;
+ prim[2].Object.Point.y = prim[3].Object.Point.y = B + y;
+
+ DrawBatch (prim, StartPrim, 0);
+ off = num_off_pixels;
+ }
+
+ if (d < 0)
+ {
+ ++x;
+ dx += TwoBsquared;
+ d += dx;
+ }
+
+ --y;
+ dy -= TwoAsquared;
+ d += Asquared - dy;
+ }
+}
+
+void
+DrawFilledOval (RECT *pRect)
+{
+ COORD x, y;
+ SIZE A, B;
+ long Asquared, TwoAsquared,
+ Bsquared, TwoBsquared;
+ long d, dx, dy;
+ LINE corners;
+ PRIMITIVE prim[NUM_QUADS >> 1];
+ COUNT StartPrim;
+
+ corners.first.x = pRect->corner.x;
+ corners.first.y = pRect->corner.y;
+ corners.second.x = corners.first.x + pRect->extent.width - 1;
+ corners.second.y = corners.first.y + pRect->extent.height - 1;
+ if (corners.second.x <= corners.first.x
+ || corners.second.y <= corners.first.y)
+ {
+ if (corners.second.x < corners.first.x)
+ corners.second.x = corners.first.x;
+ if (corners.second.y < corners.first.y)
+ corners.second.y = corners.first.y;
+
+ DrawLine (&corners);
+ return;
+ }
+
+ StartPrim = END_OF_LIST;
+ for (x = 0; x < (NUM_QUADS >> 1); ++x)
+ {
+ SetPrimNextLink (&prim[x], StartPrim);
+ SetPrimType (&prim[x], RECTFILL_PRIM);
+ SetPrimColor (&prim[x], _get_context_fg_color ());
+ prim[x].Object.Rect.extent.height = 1;
+
+ StartPrim = x;
+ }
+
+ A = pRect->extent.width >> 1;
+ B = pRect->extent.height >> 1;
+
+ x = 0;
+ y = B;
+
+ Asquared = ((long)A * A) << 1;
+ Bsquared = ((long)B * B) << 1;
+ do
+ {
+ Asquared >>= 1;
+ Bsquared >>= 1;
+ TwoAsquared = Asquared << 1;
+ dy = TwoAsquared * B;
+ } while (dy / B != TwoAsquared);
+ TwoBsquared = Bsquared << 1;
+
+ dx = 0;
+ d = Bsquared - (dy >> 1) + (Asquared >> 2);
+
+ A += pRect->corner.x;
+ B += pRect->corner.y;
+ while (dx < dy)
+ {
+ if (d > 0)
+ {
+ prim[0].Object.Rect.corner.x =
+ prim[1].Object.Rect.corner.x = A - x;
+ prim[0].Object.Rect.extent.width =
+ prim[1].Object.Rect.extent.width = (x << 1) + 1;
+ prim[0].Object.Rect.corner.y = B - y;
+ prim[1].Object.Rect.corner.y = B + y;
+
+ DrawBatch (prim, StartPrim, 0);
+
+ --y;
+ dy -= TwoAsquared;
+ d -= dy;
+ }
+
+ ++x;
+ dx += TwoBsquared;
+ d += Bsquared + dx;
+ }
+
+ d += ((((Asquared - Bsquared) * 3) >> 1) - (dx + dy)) >> 1;
+
+ while (y >= 0)
+ {
+ prim[0].Object.Rect.corner.x =
+ prim[1].Object.Rect.corner.x = A - x;
+ prim[0].Object.Rect.extent.width =
+ prim[1].Object.Rect.extent.width = (x << 1) + 1;
+ prim[0].Object.Rect.corner.y = B - y;
+ prim[1].Object.Rect.corner.y = B + y;
+
+ DrawBatch (prim, StartPrim, 0);
+
+ if (d < 0)
+ {
+ ++x;
+ dx += TwoBsquared;
+ d += dx;
+ }
+
+ --y;
+ dy -= TwoAsquared;
+ d += Asquared - dy;
+ }
+}
+
+
diff --git a/src/uqm/planets/pl_stuff.c b/src/uqm/planets/pl_stuff.c
new file mode 100644
index 0000000..073e215
--- /dev/null
+++ b/src/uqm/planets/pl_stuff.c
@@ -0,0 +1,318 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "planets.h"
+#include "../colors.h"
+#include "../setup.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/drawable.h"
+#include "libs/mathlib.h"
+#include "scan.h"
+#include "options.h"
+
+#include <math.h>
+
+
+// define USE_ADDITIVE_SCAN_BLIT to use additive blittting
+// instead of transparency for the planet scans.
+// It still doesn't look right though (it is too bright)
+#define USE_ADDITIVE_SCAN_BLIT
+
+static int rotFrameIndex;
+static int rotDirection;
+static bool throbShield;
+static int rotPointIndex;
+
+// Draw the planet sphere and any extra graphic (like a shield) if present
+void
+DrawPlanetSphere (int x, int y)
+{
+ STAMP s;
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+
+ s.origin.x = x;
+ s.origin.y = y;
+
+ BatchGraphics ();
+ s.frame = Orbit->SphereFrame;
+ DrawStamp (&s);
+ if (Orbit->ObjectFrame)
+ {
+ s.frame = Orbit->ObjectFrame;
+ DrawStamp (&s);
+ }
+ UnbatchGraphics ();
+}
+
+void
+DrawDefaultPlanetSphere (void)
+{
+ CONTEXT oldContext;
+
+ oldContext = SetContext (PlanetContext);
+ DrawPlanetSphere (SIS_SCREEN_WIDTH / 2, PLANET_ORG_Y);
+ SetContext (oldContext);
+}
+
+void
+InitSphereRotation (int direction, BOOLEAN shielded)
+{
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+
+ rotDirection = direction;
+ rotPointIndex = 0;
+ throbShield = shielded && optWhichShield == OPT_3DO;
+
+ if (throbShield)
+ {
+ // ObjectFrame must contain the shield graphic
+ Orbit->WorkFrame = Orbit->ObjectFrame;
+ // We need a scratch frame so that we can apply throbbing
+ // to the shield, so create one
+ Orbit->ObjectFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP | WANT_ALPHA,
+ GetFrameWidth (Orbit->ObjectFrame),
+ GetFrameHeight (Orbit->ObjectFrame), 2));
+ }
+
+ // Render the first sphere/shield frame
+ // Prepare will set the next one
+ rotFrameIndex = 1;
+ PrepareNextRotationFrame ();
+}
+
+void
+UninitSphereRotation (void)
+{
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+
+ if (Orbit->WorkFrame)
+ {
+ DestroyDrawable (ReleaseDrawable (Orbit->ObjectFrame));
+ Orbit->ObjectFrame = Orbit->WorkFrame;
+ Orbit->WorkFrame = NULL;
+ }
+}
+
+void
+PrepareNextRotationFrame (void)
+{
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+
+ // Generate the next rotation frame
+ // We alternate between the frames because we do not call FlushGraphics()
+ // The frame we just drew may not have made it to the screen yet
+ rotFrameIndex ^= 1;
+
+ // Go to next point, taking care of wraparounds
+ rotPointIndex += rotDirection;
+ if (rotPointIndex < 0)
+ rotPointIndex = MAP_WIDTH - 1;
+ else if (rotPointIndex >= MAP_WIDTH)
+ rotPointIndex = 0;
+
+ // prepare the next sphere frame
+ Orbit->SphereFrame = SetAbsFrameIndex (Orbit->SphereFrame, rotFrameIndex);
+ RenderPlanetSphere (Orbit->SphereFrame, rotPointIndex, throbShield);
+
+ if (throbShield)
+ { // prepare the next shield throb frame
+ Orbit->ObjectFrame = SetAbsFrameIndex (Orbit->ObjectFrame,
+ rotFrameIndex);
+ SetShieldThrobEffect (Orbit->WorkFrame, rotPointIndex,
+ Orbit->ObjectFrame);
+ }
+}
+
+#define ZOOM_RATE 24
+#define ZOOM_TIME (ONE_SECOND * 6 / 5)
+
+// This takes care of zooming the planet sphere into place
+// when entering orbit
+void
+ZoomInPlanetSphere (void)
+{
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+ const int base = GSCALE_IDENTITY;
+ int dx, dy;
+ int oldScale;
+ int oldMode;
+ int i;
+ int frameCount;
+ int zoomCorner;
+ RECT frameRect;
+ RECT repairRect;
+ TimeCount NextTime;
+
+ frameCount = ZOOM_TIME / (ONE_SECOND / ZOOM_RATE);
+
+ // Planet zoom in from a randomly chosen corner
+ zoomCorner = TFB_Random ();
+ dx = 1 - (zoomCorner & 1) * 2;
+ dy = 1 - (zoomCorner & 2);
+
+ if (Orbit->ObjectFrame)
+ GetFrameRect (Orbit->ObjectFrame, &frameRect);
+ else
+ GetFrameRect (Orbit->SphereFrame, &frameRect);
+ repairRect = frameRect;
+
+ for (i = 0; i <= frameCount; ++i)
+ {
+ double scale;
+ POINT pt;
+
+ NextTime = GetTimeCounter () + (ONE_SECOND / ZOOM_RATE);
+
+ // Use 1 + e^-2 - e^(-2x / frameCount)) function to get a decelerating
+ // zoom like the one 3DO does (supposedly)
+ if (i < frameCount)
+ scale = 1.134 - exp (-2.0 * i / frameCount);
+ else
+ scale = 1.0; // final frame
+
+ // start from beyond the screen
+ pt.x = SIS_SCREEN_WIDTH / 2 + (int) (dx * (1.0 - scale)
+ * (SIS_SCREEN_WIDTH * 6 / 10) + 0.5);
+ pt.y = PLANET_ORG_Y + (int) (dy * (1.0 - scale)
+ * (SCAN_SCREEN_HEIGHT * 6 / 10) + 0.5);
+
+ SetContext (PlanetContext);
+
+ BatchGraphics ();
+ if (i > 0)
+ RepairBackRect (&repairRect);
+
+ oldMode = SetGraphicScaleMode (TFB_SCALE_BILINEAR);
+ oldScale = SetGraphicScale ((int)(base * scale + 0.5));
+ DrawPlanetSphere (pt.x, pt.y);
+ SetGraphicScale (oldScale);
+ SetGraphicScaleMode (oldMode);
+
+ UnbatchGraphics ();
+
+ repairRect.corner.x = pt.x + frameRect.corner.x;
+ repairRect.corner.y = pt.y + frameRect.corner.y;
+
+ PrepareNextRotationFrame ();
+
+ SleepThreadUntil (NextTime);
+ }
+}
+
+void
+RotatePlanetSphere (BOOLEAN keepRate)
+{
+ static TimeCount NextTime;
+ TimeCount Now = GetTimeCounter ();
+
+ if (keepRate && Now < NextTime)
+ return; // not time yet
+
+ NextTime = Now + PLANET_ROTATION_RATE;
+ DrawDefaultPlanetSphere ();
+
+ PrepareNextRotationFrame ();
+}
+
+static void
+renderTintFrame (Color tintColor)
+{
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+ CONTEXT oldContext;
+ DrawMode mode, oldMode;
+ STAMP s;
+ RECT r;
+
+ oldContext = SetContext (OffScreenContext);
+ SetContextFGFrame (Orbit->TintFrame);
+ SetContextClipRect (NULL);
+ // get the rect of the whole context (or our frame really)
+ GetContextClipRect (&r);
+
+ // copy the topo frame to the tint frame
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = pSolarSysState->TopoFrame;
+ DrawStamp (&s);
+
+ // apply the tint
+#ifdef USE_ADDITIVE_SCAN_BLIT
+ mode = MAKE_DRAW_MODE (DRAW_ADDITIVE, DRAW_FACTOR_1 / 2);
+#else
+ mode = MAKE_DRAW_MODE (DRAW_ALPHA, DRAW_FACTOR_1 / 2);
+#endif
+ oldMode = SetContextDrawMode (mode);
+ SetContextForeGroundColor (tintColor);
+ DrawFilledRectangle (&r);
+ SetContextDrawMode (oldMode);
+
+ SetContext (oldContext);
+}
+
+// tintColor.a is ignored
+void
+DrawPlanet (int tintY, Color tintColor)
+{
+ STAMP s;
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+
+ BatchGraphics ();
+ if (sameColor (tintColor, BLACK_COLOR))
+ { // no tint -- just draw the surface
+ s.frame = pSolarSysState->TopoFrame;
+ DrawStamp (&s);
+ }
+ else
+ { // apply different scan type tints
+ FRAME tintFrame = Orbit->TintFrame;
+ int height = GetFrameHeight (tintFrame);
+
+ if (!sameColor (tintColor, Orbit->TintColor))
+ {
+ renderTintFrame (tintColor);
+ Orbit->TintColor = tintColor;
+ }
+
+ if (tintY < height - 1)
+ { // untinted piece showing, draw regular topo
+ s.frame = pSolarSysState->TopoFrame;
+ DrawStamp (&s);
+ }
+
+ if (tintY >= 0)
+ { // tinted piece showing, draw tinted piece
+ RECT oldClipRect;
+ RECT clipRect;
+
+ // adjust cliprect to confine the tint
+ GetContextClipRect (&oldClipRect);
+ clipRect = oldClipRect;
+ clipRect.extent.height = tintY + 1;
+ SetContextClipRect (&clipRect);
+ s.frame = tintFrame;
+ DrawStamp (&s);
+ SetContextClipRect (&oldClipRect);
+ }
+ }
+ UnbatchGraphics ();
+}
+
diff --git a/src/uqm/planets/plandata.h b/src/uqm/planets/plandata.h
new file mode 100644
index 0000000..cd2a1c7
--- /dev/null
+++ b/src/uqm/planets/plandata.h
@@ -0,0 +1,318 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_PLANETS_PLANDATA_H_
+#define UQM_PLANETS_PLANDATA_H_
+
+#include "libs/reslib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/*------------------------------ Type Defines ----------------------------- */
+#define NUMBER_OF_ORBITS 16
+#define VACANT 0xFF
+
+enum
+{
+ SMALL_ROCKY_WORLD = 0,
+ LARGE_ROCKY_WORLD,
+ GAS_GIANT,
+
+ NUM_PLANET_TYPES
+};
+
+enum
+{
+ DWARF_STAR = 0,
+ GIANT_STAR,
+ SUPER_GIANT_STAR,
+
+ NUM_STAR_TYPES
+};
+
+enum
+{
+ BLUE_BODY = 0,
+ GREEN_BODY,
+ ORANGE_BODY,
+ RED_BODY,
+ WHITE_BODY,
+ GRAY_BODY = WHITE_BODY,
+ YELLOW_BODY,
+
+ NUM_STAR_COLORS,
+
+ CYAN_BODY = NUM_STAR_COLORS,
+ PURPLE_BODY,
+ VIOLET_BODY
+};
+
+enum
+{
+ OWNER_NOBODY = 0,
+ OWNER_NEUTRAL,
+ OWNER_HIERARCHY,
+
+ OWNER_PLAYER = (1 << 2)
+};
+
+#define STAR_OWNER_SHIFT 0
+#define STAR_TYPE_SHIFT 3 /* STAR_OWNER_SHIFT + 3 */
+#define STAR_COLOR_SHIFT 5 /* STAR_TYPE_SHIFT + 2 */
+#define STAR_COLOR_MASK (BYTE)(0xFF << STAR_COLOR_SHIFT)
+#define STAR_TYPE_MASK (BYTE)((0xFF << STAR_TYPE_SHIFT) \
+ & ~STAR_COLOR_MASK)
+#define STAR_OWNER_MASK (BYTE)((0xFF << STAR_OWNER_SHIFT) \
+ & ~(STAR_COLOR_MASK \
+ | STAR_TYPE_MASK))
+#define STAR_UNKNOWN_MASK (STAR_OWNER_MASK & ~OWNER_PLAYER)
+
+#define MAKE_STAR(t,c,o) \
+ (BYTE)((((BYTE)(t) << STAR_TYPE_SHIFT) & STAR_TYPE_MASK) \
+ | (((BYTE)(c) << STAR_COLOR_SHIFT) & STAR_COLOR_MASK) \
+ | (((BYTE)(o) << STAR_OWNER_SHIFT) & STAR_OWNER_MASK))
+#define STAR_TYPE(f) (BYTE)(((f) & STAR_TYPE_MASK) >> STAR_TYPE_SHIFT)
+#define STAR_COLOR(f) (BYTE)(((f) & STAR_COLOR_MASK) >> STAR_COLOR_SHIFT)
+#define STAR_OWNER(f) (BYTE)(((f) & STAR_OWNER_MASK) >> STAR_OWNER_SHIFT)
+#define STAR_UNKNOWN(f) (BOOLEAN)((STAR_OWNER(f) \
+ & STAR_UNKNOWN_MASK) == STAR_UNKNOWN_MASK)
+
+#define PLAN_SIZE_MASK 0x03
+
+#define TOPO_ALGO (0 << 2)
+#define CRATERED_ALGO (1 << 2)
+#define GAS_GIANT_ALGO (2 << 2)
+#define PLAN_ALGO_MASK 0x0C
+
+#define PLANSIZE(type) ((BYTE)((type) & PLAN_SIZE_MASK))
+#define PLANALGO(type) ((BYTE)((type) & PLAN_ALGO_MASK))
+#define PLANCOLOR(type) HINIBBLE (type)
+
+#define THIN_ATMOSPHERE 10
+#define NORMAL_ATMOSPHERE 75
+#define THICK_ATMOSPHERE 200
+#define SUPER_THICK_ATMOSPHERE 2500
+#define GAS_GIANT_ATMOSPHERE 0xFFFF
+
+enum
+{
+ FIRST_ROCKY_WORLD = 0,
+ FIRST_SMALL_ROCKY_WORLD = FIRST_ROCKY_WORLD,
+
+ OOLITE_WORLD = FIRST_SMALL_ROCKY_WORLD,
+ YTTRIC_WORLD,
+ QUASI_DEGENERATE_WORLD,
+ LANTHANIDE_WORLD,
+ TREASURE_WORLD,
+ UREA_WORLD,
+ METAL_WORLD,
+ RADIOACTIVE_WORLD,
+ OPALESCENT_WORLD,
+ CYANIC_WORLD,
+ ACID_WORLD,
+ ALKALI_WORLD,
+ HALIDE_WORLD,
+ GREEN_WORLD,
+ COPPER_WORLD,
+ CARBIDE_WORLD,
+ ULTRAMARINE_WORLD,
+ NOBLE_WORLD,
+ AZURE_WORLD,
+ CHONDRITE_WORLD,
+ PURPLE_WORLD,
+ SUPER_DENSE_WORLD,
+ PELLUCID_WORLD,
+ DUST_WORLD,
+ CRIMSON_WORLD,
+ CIMMERIAN_WORLD,
+ INFRARED_WORLD,
+ SELENIC_WORLD,
+ AURIC_WORLD,
+ LAST_SMALL_ROCKY_WORLD = AURIC_WORLD,
+
+ FIRST_LARGE_ROCKY_WORLD,
+ FLUORESCENT_WORLD = FIRST_LARGE_ROCKY_WORLD,
+ ULTRAVIOLET_WORLD,
+ PLUTONIC_WORLD,
+ RAINBOW_WORLD,
+ SHATTERED_WORLD,
+ SAPPHIRE_WORLD,
+ ORGANIC_WORLD,
+ XENOLITHIC_WORLD,
+ REDUX_WORLD,
+ PRIMORDIAL_WORLD,
+ EMERALD_WORLD,
+ CHLORINE_WORLD,
+ MAGNETIC_WORLD,
+ WATER_WORLD,
+ TELLURIC_WORLD,
+ HYDROCARBON_WORLD,
+ IODINE_WORLD,
+ VINYLOGOUS_WORLD,
+ RUBY_WORLD,
+ MAGMA_WORLD,
+ MAROON_WORLD,
+ LAST_LARGE_ROCKY_WORLD = MAROON_WORLD,
+
+ FIRST_GAS_GIANT,
+ BLU_GAS_GIANT = FIRST_GAS_GIANT, /* Gas Giants */
+ CYA_GAS_GIANT,
+ GRN_GAS_GIANT,
+ GRY_GAS_GIANT,
+ ORA_GAS_GIANT,
+ PUR_GAS_GIANT,
+ RED_GAS_GIANT,
+ VIO_GAS_GIANT,
+ YEL_GAS_GIANT,
+ LAST_GAS_GIANT = YEL_GAS_GIANT,
+
+ NUMBER_OF_PLANET_TYPES,
+
+ WORLD_TYPE_SPECIAL = 0x80,
+ PLANET_SHIELDED = WORLD_TYPE_SPECIAL,
+
+ HIERARCHY_STARBASE = 127 | WORLD_TYPE_SPECIAL,
+ SA_MATRA = 126 | WORLD_TYPE_SPECIAL,
+};
+
+#define NUMBER_OF_SMALL_ROCKY_WORLDS (LAST_SMALL_ROCKY_WORLD - FIRST_SMALL_ROCKY_WORLD + 1)
+#define NUMBER_OF_LARGE_ROCKY_WORLDS (LAST_LARGE_ROCKY_WORLD - FIRST_LARGE_ROCKY_WORLD + 1)
+#define NUMBER_OF_ROCKY_WORLDS (NUMBER_OF_SMALL_ROCKY_WORLDS + NUMBER_OF_LARGE_ROCKY_WORLDS)
+#define NUMBER_OF_GAS_GIANTS (LAST_GAS_GIANT - FIRST_GAS_GIANT + 1)
+
+// TODO: This struct is highly alignment and padding dependent and
+// should not be used! The data is loaded as binary from files and
+// cast to this struct.
+typedef struct
+{
+ const SIZE level_tab[3];
+ const BYTE xlat_tab[256];
+} XLAT_DESC;
+
+typedef struct
+{
+ BYTE ElementType;
+ /* Index of this element in element_array */
+ BYTE Density;
+ /* bits 0-3: quantity of the deposits (maximum number of
+ * deposits), one of FEW, MODERATE, or NUMEROUS
+ * bits 4-7: quality of the deposit, one of LOW, MEDIUM, or HEAVY
+ */
+} ELEMENT_ENTRY;
+
+// PlanetFrame describes a type of planet. It is not used to describe
+// individual planets.
+typedef struct
+{
+ BYTE Type;
+ /* bits 0-1: size, one of SMALL_ROCKY_WORLD, LARGE_ROCKY_WORLD, or
+ * GAS_GIANT
+ * bits 2-3: map creation algoritm, one of TOPO_ALGO,
+ * CRATERED_ALGO, or GAS_GIANT_ALGO
+ * bits 4-7: interplanetary color, one of BLUE_BODY, GREEN_BODY,
+ * ORANGE_BODY, RED_BODY, WHITE_BODY (same as
+ * GRAY_BODY), YELLOW_BODY, CYAN_BODY, PURPLE_BODY,
+ * VIOLET_BODY)
+ */
+ BYTE BaseTectonics;
+ /* Base constant for calculation of tectonic activity,
+ * relative to Earth at 100.
+ * One of: NO_TECTONICS, LOW_TECTONICS, MED_TECTONICS,
+ * HIGH_TECTONICS, or SUPER_TECTONICS
+ */
+ BYTE AtmoAndDensity;
+ /* bits 0-3: planet density, one of GAS_DENSITY, LIGHT_DENSITY,
+ * LOW_DENSITY, NORMAL_DENSITY, HIGH_DENSITY,
+ * SUPER_DENSITY
+ * bits 4-7: atmosphere, one of LIGHT, MEDIUM, HEAVY, or
+ * (no define for this) super thick.
+ */
+#define NUM_USEFUL_ELEMENTS 8
+ ELEMENT_ENTRY UsefulElements[NUM_USEFUL_ELEMENTS];
+ /* Minerals on the planet */
+
+ RESOURCE CMapInstance;
+ /* Color map */
+ RESOURCE XlatTabInstance;
+ /* Color translation map */
+
+ // Parameters for map-generation algoritms:
+ COUNT num_faults;
+ SIZE fault_depth;
+ COUNT num_blemishes;
+ SIZE base_elevation;
+} PlanetFrame;
+
+typedef struct
+{
+ SIZE AxialTilt;
+ UWORD Tectonics;
+ UWORD Weather;
+ UWORD PlanetDensity;
+ UWORD PlanetRadius;
+ UWORD SurfaceGravity;
+ SIZE SurfaceTemperature;
+ UWORD RotationPeriod;
+ UWORD AtmoDensity;
+ SIZE LifeChance;
+ UWORD PlanetToSunDist;
+
+ const PlanetFrame *PlanDataPtr;
+
+ DWORD ScanSeed[NUM_SCAN_TYPES];
+ DWORD ScanRetrieveMask[NUM_SCAN_TYPES];
+
+ STRING DiscoveryString;
+ FONT LanderFont;
+ FRAME LanderFontEff;
+} PLANET_INFO;
+
+enum
+{
+ GAS_DENSITY,
+ LIGHT_DENSITY,
+ LOW_DENSITY,
+ NORMAL_DENSITY,
+ HIGH_DENSITY,
+ SUPER_DENSITY
+};
+
+extern UWORD CalcGravity (const PLANET_INFO*);
+
+#define EARTH_ATMOSPHERE 50
+
+#define COLD_THRESHOLD -40
+#define HOT_THRESHOLD 100
+
+/*------------------------------ Global Data ------------------------------ */
+
+#define NO_TECTONICS 0
+#define LOW_TECTONICS 40
+#define MED_TECTONICS 80
+#define HIGH_TECTONICS 140
+#define SUPER_TECTONICS 200
+
+extern const PlanetFrame *PlanData;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PLANETS_PLANDATA_H_ */
diff --git a/src/uqm/planets/planets.c b/src/uqm/planets/planets.c
new file mode 100644
index 0000000..c76c2bb
--- /dev/null
+++ b/src/uqm/planets/planets.c
@@ -0,0 +1,483 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "planets.h"
+
+#include "scan.h"
+#include "lander.h"
+#include "../colors.h"
+#include "../element.h"
+#include "../settings.h"
+#include "../controls.h"
+#include "../sounds.h"
+#include "../gameopt.h"
+#include "../shipcont.h"
+#include "../setup.h"
+#include "../uqmdebug.h"
+#include "../resinst.h"
+#include "../nameref.h"
+#include "options.h"
+#include "libs/graphics/gfx_common.h"
+
+
+// PlanetOrbitMenu() items
+enum PlanetMenuItems
+{
+ // XXX: Must match the enum in menustat.h
+ SCAN = 0,
+ STARMAP,
+ EQUIP_DEVICE,
+ CARGO,
+ ROSTER,
+ GAME_MENU,
+ NAVIGATION,
+};
+
+CONTEXT PlanetContext;
+ // Context for rotating planet view and lander surface view
+
+static void
+CreatePlanetContext (void)
+{
+ CONTEXT oldContext;
+ RECT r;
+
+ assert (PlanetContext == NULL);
+
+ // PlanetContext rect is relative to SpaceContext
+ oldContext = SetContext (SpaceContext);
+ GetContextClipRect (&r);
+
+ PlanetContext = CreateContext ("PlanetContext");
+ SetContext (PlanetContext);
+ SetContextFGFrame (Screen);
+ r.extent.height -= MAP_HEIGHT + MAP_BORDER_HEIGHT;
+ SetContextClipRect (&r);
+
+ SetContext (oldContext);
+}
+
+static void
+DestroyPlanetContext (void)
+{
+ if (PlanetContext)
+ {
+ DestroyContext (PlanetContext);
+ PlanetContext = NULL;
+ }
+}
+
+void
+DrawScannedObjects (BOOLEAN Reversed)
+{
+ HELEMENT hElement, hNextElement;
+
+ for (hElement = Reversed ? GetTailElement () : GetHeadElement ();
+ hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = Reversed ?
+ GetPredElement (ElementPtr) :
+ GetSuccElement (ElementPtr);
+
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ STAMP s;
+
+ s.origin = ElementPtr->current.location;
+ s.frame = ElementPtr->next.image.frame;
+ DrawStamp (&s);
+ }
+
+ UnlockElement (hElement);
+ }
+}
+
+void
+DrawPlanetSurfaceBorder (void)
+{
+ CONTEXT oldContext;
+ RECT oldClipRect;
+ RECT clipRect;
+ RECT r;
+
+ oldContext = SetContext (SpaceContext);
+ GetContextClipRect (&oldClipRect);
+
+ // Expand the context clip-rect so that we can tweak the existing border
+ clipRect = oldClipRect;
+ clipRect.corner.x -= 1;
+ clipRect.extent.width += 2;
+ clipRect.extent.height += 1;
+ SetContextClipRect (&clipRect);
+
+ BatchGraphics ();
+
+ // Border bulk
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ r.corner.x = 0;
+ r.corner.y = clipRect.extent.height - MAP_HEIGHT - MAP_BORDER_HEIGHT;
+ r.extent.width = clipRect.extent.width;
+ r.extent.height = MAP_BORDER_HEIGHT - 2;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (SIS_BOTTOM_RIGHT_BORDER_COLOR);
+
+ // Border top shadow line
+ r.extent.width -= 1;
+ r.extent.height = 1;
+ r.corner.x = 1;
+ r.corner.y -= 1;
+ DrawFilledRectangle (&r);
+
+ // XXX: We will need bulk left and right rects here if MAP_WIDTH changes
+
+ // Right shadow line
+ r.extent.width = 1;
+ r.extent.height = MAP_HEIGHT + 2;
+ r.corner.y += MAP_BORDER_HEIGHT - 1;
+ r.corner.x = clipRect.extent.width - 1;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (SIS_LEFT_BORDER_COLOR);
+
+ // Left shadow line
+ r.corner.x -= MAP_WIDTH + 1;
+ DrawFilledRectangle (&r);
+
+ // Border bottom shadow line
+ r.extent.width = MAP_WIDTH + 2;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+
+ UnbatchGraphics ();
+
+ SetContextClipRect (&oldClipRect);
+ SetContext (oldContext);
+}
+
+typedef enum
+{
+ DRAW_ORBITAL_FULL,
+ DRAW_ORBITAL_WAIT,
+ DRAW_ORBITAL_UPDATE,
+
+} DRAW_ORBITAL_MODE;
+
+static void
+DrawOrbitalDisplay (DRAW_ORBITAL_MODE Mode)
+{
+ RECT r;
+
+ SetContext (SpaceContext);
+ GetContextClipRect (&r);
+
+ BatchGraphics ();
+
+ if (Mode != DRAW_ORBITAL_UPDATE)
+ {
+ SetTransitionSource (NULL);
+
+ DrawSISFrame ();
+ DrawSISMessage (NULL);
+ DrawSISTitle (GLOBAL_SIS (PlanetName));
+ DrawStarBackGround ();
+ DrawPlanetSurfaceBorder ();
+ }
+
+ if (Mode == DRAW_ORBITAL_WAIT)
+ {
+ STAMP s;
+
+ SetContext (GetScanContext (NULL));
+ s.frame = CaptureDrawable (LoadGraphic (ORBENTER_PMAP_ANIM));
+ s.origin.x = -SAFE_X;
+ s.origin.y = 0;
+ DrawStamp (&s);
+ DestroyDrawable (ReleaseDrawable (s.frame));
+ }
+ else if (Mode == DRAW_ORBITAL_FULL)
+ {
+ DrawDefaultPlanetSphere ();
+ }
+
+ if (Mode != DRAW_ORBITAL_WAIT)
+ {
+ SetContext (GetScanContext (NULL));
+ DrawPlanet (0, BLACK_COLOR);
+ }
+
+ if (Mode != DRAW_ORBITAL_UPDATE)
+ {
+ ScreenTransition (3, &r);
+ }
+
+ UnbatchGraphics ();
+
+ // for later RepairBackRect()
+ LoadIntoExtraScreen (&r);
+}
+
+// Initialise the surface graphics, and start the planet music.
+// Called from the GenerateFunctions.generateOribital() function
+// (when orbit is entered; either from IP, or from loading a saved game)
+// and when "starmap" is selected from orbit and then cancelled;
+// also after in-orbit comm and after defeating planet guards in combat.
+// SurfDefFrame contains surface definition images when a planet comes
+// with its own bitmap (currently only for Earth)
+void
+LoadPlanet (FRAME SurfDefFrame)
+{
+ bool WaitMode = !(LastActivity & CHECK_LOAD);
+ PLANET_DESC *pPlanetDesc;
+
+#ifdef DEBUG
+ if (disableInteractivity)
+ return;
+#endif
+
+ assert (pSolarSysState->InOrbit && !pSolarSysState->TopoFrame);
+
+ CreatePlanetContext ();
+
+ if (WaitMode)
+ {
+ DrawOrbitalDisplay (DRAW_ORBITAL_WAIT);
+ }
+
+ StopMusic ();
+
+ pPlanetDesc = pSolarSysState->pOrbitalDesc;
+ GeneratePlanetSurface (pPlanetDesc, SurfDefFrame);
+ SetPlanetMusic (pPlanetDesc->data_index & ~PLANET_SHIELDED);
+ GeneratePlanetSide ();
+
+ if (!PLRPlaying ((MUSIC_REF)~0))
+ PlayMusic (LanderMusic, TRUE, 1);
+
+ if (WaitMode)
+ {
+ ZoomInPlanetSphere ();
+ DrawOrbitalDisplay (DRAW_ORBITAL_UPDATE);
+ }
+ else
+ {
+ DrawOrbitalDisplay (DRAW_ORBITAL_FULL);
+ }
+}
+
+void
+FreePlanet (void)
+{
+ COUNT i;
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+
+ UninitSphereRotation ();
+
+ StopMusic ();
+
+ for (i = 0; i < sizeof (pSolarSysState->PlanetSideFrame)
+ / sizeof (pSolarSysState->PlanetSideFrame[0]); ++i)
+ {
+ DestroyDrawable (ReleaseDrawable (pSolarSysState->PlanetSideFrame[i]));
+ pSolarSysState->PlanetSideFrame[i] = 0;
+ }
+
+// FreeLanderData ();
+
+ DestroyStringTable (ReleaseStringTable (pSolarSysState->XlatRef));
+ pSolarSysState->XlatRef = 0;
+ DestroyDrawable (ReleaseDrawable (pSolarSysState->TopoFrame));
+ pSolarSysState->TopoFrame = 0;
+ DestroyColorMap (ReleaseColorMap (pSolarSysState->OrbitalCMap));
+ pSolarSysState->OrbitalCMap = 0;
+
+ HFree (Orbit->lpTopoData);
+ Orbit->lpTopoData = 0;
+ DestroyDrawable (ReleaseDrawable (Orbit->TopoZoomFrame));
+ Orbit->TopoZoomFrame = 0;
+ DestroyDrawable (ReleaseDrawable (Orbit->SphereFrame));
+ Orbit->SphereFrame = NULL;
+
+ DestroyDrawable (ReleaseDrawable (Orbit->TintFrame));
+ Orbit->TintFrame = 0;
+ Orbit->TintColor = BLACK_COLOR;
+
+ DestroyDrawable (ReleaseDrawable (Orbit->ObjectFrame));
+ Orbit->ObjectFrame = 0;
+ DestroyDrawable (ReleaseDrawable (Orbit->WorkFrame));
+ Orbit->WorkFrame = 0;
+
+ HFree (Orbit->TopoColors);
+ Orbit->TopoColors = NULL;
+ HFree (Orbit->ScratchArray);
+ Orbit->ScratchArray = NULL;
+
+ DestroyStringTable (ReleaseStringTable (
+ pSolarSysState->SysInfo.PlanetInfo.DiscoveryString
+ ));
+ pSolarSysState->SysInfo.PlanetInfo.DiscoveryString = 0;
+ FreeLanderFont (&pSolarSysState->SysInfo.PlanetInfo);
+
+ // Need to make sure our own CONTEXTs are not active because
+ // we will destroy them now
+ SetContext (SpaceContext);
+ DestroyPlanetContext ();
+ DestroyScanContext ();
+
+}
+
+void
+LoadStdLanderFont (PLANET_INFO *info)
+{
+ info->LanderFont = LoadFont (LANDER_FONT);
+ info->LanderFontEff = CaptureDrawable (
+ LoadGraphic (LANDER_FONTEFF_PMAP_ANIM));
+}
+
+void
+FreeLanderFont (PLANET_INFO *info)
+{
+ DestroyFont (info->LanderFont);
+ info->LanderFont = NULL;
+ DestroyDrawable (ReleaseDrawable (info->LanderFontEff));
+ info->LanderFontEff = NULL;
+}
+
+static BOOLEAN
+DoPlanetOrbit (MENU_STATE *pMS)
+{
+ BOOLEAN select = PulsedInputState.menu[KEY_MENU_SELECT];
+ BOOLEAN handled;
+
+ if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0)
+ return FALSE;
+
+ // XXX: pMS actually refers to pSolarSysState->MenuState
+ handled = DoMenuChooser (pMS, PM_SCAN);
+ if (handled)
+ return TRUE;
+
+ if (!select)
+ return TRUE;
+
+ SetFlashRect (NULL);
+
+ switch (pMS->CurState)
+ {
+ case SCAN:
+ ScanSystem ();
+ if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ { // Found Fwiffo on Pluto
+ return FALSE;
+ }
+ break;
+ case EQUIP_DEVICE:
+ select = DevicesMenu ();
+ if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ { // Invoked Talking Pet, a Caster or Sun Device over Chmmr,
+ // or a Caster for Ilwrath
+ // Going into conversation
+ return FALSE;
+ }
+ break;
+ case CARGO:
+ CargoMenu ();
+ break;
+ case ROSTER:
+ select = RosterMenu ();
+ break;
+ case GAME_MENU:
+ if (!GameOptions ())
+ return FALSE; // abort or load
+ break;
+ case STARMAP:
+ {
+ BOOLEAN AutoPilotSet;
+ InputFrameCallback *oldCallback;
+
+ // Deactivate planet rotation
+ oldCallback = SetInputCallback (NULL);
+
+ RepairSISBorder ();
+
+ AutoPilotSet = StarMap ();
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ // Reactivate planet rotation
+ SetInputCallback (oldCallback);
+
+ if (!AutoPilotSet)
+ { // Redraw the orbital display
+ DrawOrbitalDisplay (DRAW_ORBITAL_FULL);
+ break;
+ }
+ // Fall through !!!
+ }
+ case NAVIGATION:
+ return FALSE;
+ }
+
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ if (select)
+ { // 3DO menu jumps to NAVIGATE after a successful submenu run
+ if (optWhichMenu != OPT_PC)
+ pMS->CurState = NAVIGATION;
+ DrawMenuStateStrings (PM_SCAN, pMS->CurState);
+ }
+ SetFlashRect (SFR_MENU_3DO);
+ }
+
+ return TRUE;
+}
+
+static void
+on_input_frame (void)
+{
+ RotatePlanetSphere (TRUE);
+}
+
+void
+PlanetOrbitMenu (void)
+{
+ MENU_STATE MenuState;
+ InputFrameCallback *oldCallback;
+
+ memset (&MenuState, 0, sizeof MenuState);
+
+ DrawMenuStateStrings (PM_SCAN, SCAN);
+ SetFlashRect (SFR_MENU_3DO);
+
+ MenuState.CurState = SCAN;
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ oldCallback = SetInputCallback (on_input_frame);
+
+ MenuState.InputFunc = DoPlanetOrbit;
+ DoInput (&MenuState, TRUE);
+
+ SetInputCallback (oldCallback);
+
+ SetFlashRect (NULL);
+ DrawMenuStateStrings (PM_STARMAP, -NAVIGATION);
+}
diff --git a/src/uqm/planets/planets.h b/src/uqm/planets/planets.h
new file mode 100644
index 0000000..c6f440d
--- /dev/null
+++ b/src/uqm/planets/planets.h
@@ -0,0 +1,322 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_PLANETS_PLANETS_H_
+#define UQM_PLANETS_PLANETS_H_
+
+#include "libs/mathlib.h"
+
+#define END_INTERPLANETARY START_INTERPLANETARY
+
+enum PlanetScanTypes
+{
+ MINERAL_SCAN = 0,
+ ENERGY_SCAN,
+ BIOLOGICAL_SCAN,
+
+ NUM_SCAN_TYPES,
+};
+
+#define MAP_WIDTH SIS_SCREEN_WIDTH
+#define MAP_HEIGHT (75 - SAFE_Y)
+
+enum
+{
+ BIOLOGICAL_DISASTER = 0,
+ EARTHQUAKE_DISASTER,
+ LIGHTNING_DISASTER,
+ LAVASPOT_DISASTER,
+
+ /* additional lander sounds */
+ LANDER_INJURED,
+ LANDER_SHOOTS,
+ LANDER_HITS,
+ LIFEFORM_CANNED,
+ LANDER_PICKUP,
+ LANDER_FULL,
+ LANDER_DEPARTS,
+ LANDER_RETURNS,
+ LANDER_DESTROYED
+};
+
+#define MAX_SCROUNGED 50 /* max lander can hold */
+
+#define SCALE_RADIUS(r) ((r) << 6)
+#define UNSCALE_RADIUS(r) ((r) >> 6)
+#define MAX_ZOOM_RADIUS SCALE_RADIUS(128)
+#define MIN_ZOOM_RADIUS (MAX_ZOOM_RADIUS>>3)
+#define EARTH_RADIUS SCALE_RADIUS(8)
+
+#define MIN_PLANET_RADIUS SCALE_RADIUS (4)
+#define MAX_PLANET_RADIUS SCALE_RADIUS (124)
+
+#define DISPLAY_FACTOR ((SIS_SCREEN_WIDTH >> 1) - 8)
+
+#define NUM_SCANDOT_TRANSITIONS 4
+
+#define MIN_MOON_RADIUS 35
+#define MOON_DELTA 20
+
+#define MAX_SUNS 1
+#define MAX_PLANETS 16
+#define MAX_MOONS 4
+
+#define MAP_BORDER_HEIGHT 5
+#define SCAN_SCREEN_HEIGHT (SIS_SCREEN_HEIGHT - MAP_HEIGHT - MAP_BORDER_HEIGHT)
+
+#define PLANET_ROTATION_TIME (ONE_SECOND * 12)
+#define PLANET_ROTATION_RATE (PLANET_ROTATION_TIME / MAP_WIDTH)
+// XXX: -9 to match the original, but why? I have no idea
+#define PLANET_ORG_Y ((SCAN_SCREEN_HEIGHT - 9) / 2)
+
+#define NUM_RACE_RUINS 16
+
+typedef struct planet_desc PLANET_DESC;
+typedef struct star_desc STAR_DESC;
+typedef struct node_info NODE_INFO;
+typedef struct planet_orbit PLANET_ORBIT;
+typedef struct solarsys_state SOLARSYS_STATE;
+
+
+#include "generate.h"
+#include "../menustat.h"
+#include "../units.h"
+
+#include "elemdata.h"
+#include "lifeform.h"
+#include "plandata.h"
+#include "sundata.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct planet_desc
+{
+ DWORD rand_seed;
+
+ BYTE data_index;
+ BYTE NumPlanets;
+ SIZE radius;
+ POINT location;
+
+ Color temp_color;
+ COUNT NextIndex;
+ STAMP image;
+
+ PLANET_DESC *pPrevDesc;
+ // The Sun or planet that this world is orbiting around.
+};
+
+struct star_desc
+{
+ POINT star_pt;
+ BYTE Type;
+ BYTE Index;
+ BYTE Prefix;
+ BYTE Postfix;
+};
+
+struct node_info
+{
+ // This structire is filled in when a generateMinerals, generateEnergy,
+ // or generateLife call is made.
+ POINT loc_pt;
+ // Position of the mineral/bio/energy node on the planet.
+ COUNT density;
+ // For bio and energy: undefined
+ // For minerals the low byte is the gross size of the
+ // deposit (this determines the image), and the high
+ // byte is the fine size (the actual quantity).
+ COUNT type;
+ // For minerals: the type of element
+ // For bio: the type of the creature.
+ // 0 through NUM_CREATURE_TYPES - 1 are normal creatures,
+ // NUM_CREATURE_TYPES is an Evil One
+ // NUM_CREATURE_TYPES + 1 is a Brainbox Bulldozer
+ // NUM_CREATURE_TYPES + 2 is Zex' Beauty
+ // For energy: undefined
+};
+
+struct planet_orbit
+{
+ FRAME TopoZoomFrame;
+ // 4x scaled topo image for planet-side
+ SBYTE *lpTopoData;
+ // normal topo data; expressed in elevation levels
+ // data is signed for planets other than gas giants
+ // transformed to light variance map for 3d planet
+ FRAME SphereFrame;
+ // rotating 3d planet frames (current and next)
+ FRAME ObjectFrame;
+ // any extra planetary object (shield, atmo, rings)
+ // automatically drawn if present
+ FRAME TintFrame;
+ // tinted topo images for current scan type (dynamic)
+ Color TintColor;
+ // the color of the last used tint
+ Color *TopoColors;
+ // RGBA version of topo image; for 3d planet
+ Color *ScratchArray;
+ // temp RGBA data for whatever transforms (nuked often)
+ FRAME WorkFrame;
+ // any extra frame workspace (for dynamic objects)
+};
+
+// See doc/devel/generate for information on how this structure is
+// filled.
+struct solarsys_state
+{
+ // Standard field required by DoInput()
+ BOOLEAN (*InputFunc) (struct solarsys_state *);
+
+ BOOLEAN InIpFlight;
+ // Set to TRUE when player is flying around in interplanetary
+ // Reset to FALSE when going into orbit or encounter
+
+ COUNT WaitIntersect;
+ // Planet/moon number with which the flagship should not collide
+ // For example, if the player just left the planet or inner system
+ // If set to (COUNT)~0, all planet collisions are disabled until
+ // the flagship stops intersecting with all planets.
+ PLANET_DESC SunDesc[MAX_SUNS];
+ PLANET_DESC PlanetDesc[MAX_PLANETS];
+ // Description of the planets in the system.
+ // Only defined after a call to (*genFuncs)->generatePlanets()
+ // and overwritten by subsequent calls.
+ PLANET_DESC MoonDesc[MAX_MOONS];
+ // Description of the moons orbiting the planet pointed to
+ // by pBaseDesc.
+ // Only defined after a call to (*genFuncs)->generateMoons()
+ // as its argument, and overwritten by subsequent calls.
+ PLANET_DESC *pBaseDesc;
+ // In outer system: points to PlanetDesc[]
+ // In inner system: points to MoonDesc[]
+ PLANET_DESC *pOrbitalDesc;
+ // In orbit: points into PlanetDesc or MoonDesc to the planet
+ // currently orbiting.
+ // In inner system: points into PlanetDesc to the planet whose
+ // inner system the ship is inside
+ SIZE FirstPlanetIndex, LastPlanetIndex;
+ // The planets get sorted on their image.origin.y value.
+ // PlanetDesc[FirstPlanetIndex] is the planet with the lowest
+ // image.origin.y, and PlanetDesc[LastPlanetIndex] has the
+ // highest image.origin.y.
+ // PlanetDesc[PlanetDesc[i].NextIndex] is the next planet
+ // after PlanetDesc[i] in the ordering.
+
+ BYTE turn_counter;
+ BYTE turn_wait;
+ BYTE thrust_counter;
+ BYTE max_ship_speed;
+
+ STRING XlatRef;
+ const void *XlatPtr;
+ COLORMAP OrbitalCMap;
+
+ SYSTEM_INFO SysInfo;
+
+ const GenerateFunctions *genFuncs;
+ // Functions to call to fill in various parts of this structure.
+ // See generate.h, doc/devel/generate
+
+ FRAME PlanetSideFrame[3 + MAX_LIFE_VARIATION];
+ /* Frames for planet-side elements.
+ * [0] = bio cannister
+ * [1] = energy node (world-specific)
+ * [2] = unused (formerly static slave shield, presumed)
+ * [3] = bio 1 (world-specific)
+ * [4] = bio 2 (world-specific)
+ * [5] = bio 3 (world-specific)
+ */
+ FRAME TopoFrame;
+ PLANET_ORBIT Orbit;
+ BOOLEAN InOrbit;
+ // Set to TRUE when player hits a world in an inner system
+ // Homeworld encounters count as 'in orbit'
+};
+
+extern SOLARSYS_STATE *pSolarSysState;
+extern MUSIC_REF SpaceMusic;
+extern CONTEXT PlanetContext;
+
+// Random context used for all solar system, planets and surfaces generation
+extern RandomContext *SysGenRNG;
+
+bool playerInSolarSystem (void);
+bool playerInPlanetOrbit (void);
+bool playerInInnerSystem (void);
+bool worldIsPlanet (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world);
+bool worldIsMoon (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world);
+COUNT planetIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world);
+COUNT moonIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *moon);
+#define MATCH_PLANET ((BYTE) -1)
+bool matchWorld (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world,
+ BYTE planetI, BYTE moonI);
+
+DWORD GetRandomSeedForStar (const STAR_DESC *star);
+
+POINT locationToDisplay (POINT pt, SIZE scaleRadius);
+POINT displayToLocation (POINT pt, SIZE scaleRadius);
+POINT planetOuterLocation (COUNT planetI);
+
+extern void LoadPlanet (FRAME SurfDefFrame);
+extern void DrawPlanet (int dy, Color tintColor);
+extern void FreePlanet (void);
+extern void LoadStdLanderFont (PLANET_INFO *info);
+extern void FreeLanderFont (PLANET_INFO *info);
+
+extern void ExploreSolarSys (void);
+extern void DrawStarBackGround (void);
+extern void XFormIPLoc (POINT *pIn, POINT *pOut, BOOLEAN ToDisplay);
+extern void DrawOval (RECT *pRect, BYTE num_off_pixels);
+extern void DrawFilledOval (RECT *pRect);
+extern void FillOrbits (SOLARSYS_STATE *system, BYTE NumPlanets,
+ PLANET_DESC *pBaseDesc, BOOLEAN TypesDefined);
+extern void InitLander (BYTE LanderFlags);
+
+extern void InitSphereRotation (int direction, BOOLEAN shielded);
+extern void UninitSphereRotation (void);
+extern void PrepareNextRotationFrame (void);
+extern void DrawPlanetSphere (int x, int y);
+extern void DrawDefaultPlanetSphere (void);
+extern void RenderPlanetSphere (FRAME Frame, int offset, BOOLEAN doThrob);
+extern void SetShieldThrobEffect (FRAME FromFrame, int offset, FRAME ToFrame);
+
+extern void ZoomInPlanetSphere (void);
+extern void RotatePlanetSphere (BOOLEAN keepRate);
+
+extern void DrawScannedObjects (BOOLEAN Reversed);
+extern void GeneratePlanetSurface (PLANET_DESC *pPlanetDesc,
+ FRAME SurfDefFrame);
+extern void DeltaTopography (COUNT num_iterations, SBYTE *DepthArray,
+ RECT *pRect, SIZE depth_delta);
+
+extern void DrawPlanetSurfaceBorder (void);
+
+extern UNICODE* GetNamedPlanetaryBody (void);
+extern void GetPlanetOrMoonName (UNICODE *buf, COUNT bufsize);
+
+extern void PlanetOrbitMenu (void);
+extern void SaveSolarSysLocation (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PLANETS_PLANETS_H_ */
diff --git a/src/uqm/planets/plangen.c b/src/uqm/planets/plangen.c
new file mode 100644
index 0000000..005a968
--- /dev/null
+++ b/src/uqm/planets/plangen.c
@@ -0,0 +1,1954 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "planets.h"
+#include "scan.h"
+#include "../nameref.h"
+#include "../resinst.h"
+#include "../setup.h"
+#include "options.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/drawable.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+#include "libs/memlib.h"
+#include <math.h>
+#include <time.h>
+
+
+#undef PROFILE_ROTATION
+
+// define USE_ALPHA_SHIELD to use an aloha overlay instead of
+// an additive overlay for the shield effect
+#undef USE_ALPHA_SHIELD
+
+#define SHIELD_GLOW_COMP 120
+#define SHIELD_REFLECT_COMP 100
+
+#define NUM_BATCH_POINTS 64
+#define RADIUS 37
+//2*RADIUS
+#define TWORADIUS (RADIUS << 1)
+//RADIUS^2
+#define RADIUS_2 (RADIUS * RADIUS)
+// distance beyond which all pixels are transparent (for aa)
+#define RADIUS_THRES ((RADIUS + 1) * (RADIUS + 1))
+#define DIAMETER (TWORADIUS + 1)
+#if 0
+# define SPHERE_SPAN_X (MAP_WIDTH >> 1)
+#else
+# define SPHERE_SPAN_X (MAP_HEIGHT)
+#endif
+ // XXX: technically, the sphere's span over X should be MAP_WIDTH/2
+ // but this causes visible surface compression over X, because
+ // the surface dims ratio is H x H*PI, instead of H x 2*H
+ // see bug #885
+
+#define DIFFUSE_BITS 16
+#define AA_WEIGHT_BITS 16
+
+#ifndef M_TWOPI
+ #ifndef M_PI
+ #define M_PI 3.14159265358979323846
+ #endif
+ #define M_TWOPI (M_PI * 2.0)
+#endif
+#ifndef M_DEG2RAD
+#define M_DEG2RAD (M_TWOPI / 360.0)
+#endif
+
+DWORD light_diff[DIAMETER][DIAMETER];
+
+typedef struct
+{
+ POINT p[4];
+ DWORD m[4];
+} MAP3D_POINT;
+
+MAP3D_POINT map_rotate[DIAMETER][DIAMETER];
+
+typedef struct
+{
+ double x, y, z;
+} POINT3;
+
+static void
+RenderTopography (FRAME DstFrame, SBYTE *pTopoData, int w, int h)
+{
+ FRAME OldFrame;
+
+ OldFrame = SetContextFGFrame (DstFrame);
+
+ if (pSolarSysState->XlatRef == 0)
+ {
+ // There is currently nothing we can do w/o an xlat table
+ // This is still called for Earth for 4x scaled topo, but we
+ // do not need it because we cannot land on Earth.
+ }
+ else
+ {
+ COUNT i;
+ BYTE AlgoType;
+ SIZE base, d;
+ const XLAT_DESC *xlatDesc;
+ POINT pt;
+ const PlanetFrame *PlanDataPtr;
+ PRIMITIVE BatchArray[NUM_BATCH_POINTS];
+ PRIMITIVE *pBatch;
+ SBYTE *pSrc;
+ const BYTE *xlat_tab;
+ BYTE *cbase;
+ POINT oldOrigin;
+ RECT ClipRect;
+
+ oldOrigin = SetContextOrigin (MAKE_POINT (0, 0));
+ GetContextClipRect (&ClipRect);
+ SetContextClipRect (NULL);
+
+ pBatch = &BatchArray[0];
+ for (i = 0; i < NUM_BATCH_POINTS; ++i, ++pBatch)
+ {
+ SetPrimNextLink (pBatch, i + 1);
+ SetPrimType (pBatch, POINT_PRIM);
+ }
+ SetPrimNextLink (&pBatch[-1], END_OF_LIST);
+
+ PlanDataPtr = &PlanData[
+ pSolarSysState->pOrbitalDesc->data_index & ~PLANET_SHIELDED
+ ];
+ AlgoType = PLANALGO (PlanDataPtr->Type);
+ base = PlanDataPtr->base_elevation;
+ xlatDesc = (const XLAT_DESC *) pSolarSysState->XlatPtr;
+ xlat_tab = (const BYTE *) xlatDesc->xlat_tab;
+ cbase = GetColorMapAddress (pSolarSysState->OrbitalCMap);
+
+ i = NUM_BATCH_POINTS;
+ pBatch = &BatchArray[i];
+ pSrc = pTopoData;
+ for (pt.y = 0; pt.y < h; ++pt.y)
+ {
+ for (pt.x = 0; pt.x < w; ++pt.x, ++pSrc)
+ {
+ BYTE *ctab;
+
+ d = *pSrc;
+ if (AlgoType == GAS_GIANT_ALGO)
+ { // make elevation value non-negative
+ d &= 255;
+ }
+ else
+ {
+ d += base;
+ if (d < 0)
+ d = 0;
+ else if (d > 255)
+ d = 255;
+ }
+
+ --pBatch;
+ pBatch->Object.Point.x = pt.x;
+ pBatch->Object.Point.y = pt.y;
+
+ d = xlat_tab[d] - cbase[0];
+ ctab = (cbase + 2) + d * 3;
+
+ // fixed planet surfaces being too dark
+ // ctab shifts were previously >> 3 .. -Mika
+ SetPrimColor (pBatch, BUILD_COLOR (MAKE_RGB15 (ctab[0] >> 1,
+ ctab[1] >> 1, ctab[2] >> 1), d));
+
+ if (--i == 0)
+ { // flush the batch and start the next one
+ DrawBatch (BatchArray, 0, 0);
+ i = NUM_BATCH_POINTS;
+ pBatch = &BatchArray[i];
+ }
+ }
+ }
+
+ if (i < NUM_BATCH_POINTS)
+ {
+ DrawBatch (BatchArray, i, 0);
+ }
+
+ SetContextClipRect (&ClipRect);
+ SetContextOrigin (oldOrigin);
+ }
+
+ SetContextFGFrame (OldFrame);
+}
+
+static inline void
+P3mult (POINT3 *res, POINT3 *vec, double cnst)
+{
+ res->x = vec->x * cnst;
+ res->y = vec->y * cnst;
+ res->z = vec->z * cnst;
+}
+
+static inline void
+P3sub (POINT3 *res, POINT3 *v1, POINT3 *v2)
+{
+ res->x = v1->x - v2->x;
+ res->y = v1->y - v2->y;
+ res->z = v1->z - v2->z;
+}
+
+static inline double
+P3dot (POINT3 *v1, POINT3 *v2)
+{
+ return (v1->x * v2->x + v1->y * v2->y + v1->z * v2->z);
+}
+
+static inline void
+P3norm (POINT3 *res, POINT3 *vec)
+{
+ double mag = sqrt (P3dot (vec, vec));
+ P3mult (res, vec, 1/mag);
+}
+
+// GenerateSphereMask builds a shadow map for the rotating planet
+// loc indicates the planet's position relative to the sun
+static void
+GenerateSphereMask (POINT loc)
+{
+ POINT pt;
+ POINT3 light;
+ double lrad;
+ const DWORD step = 1 << DIFFUSE_BITS;
+ int y, x;
+
+#define AMBIENT_LIGHT 0.1
+#define LIGHT_Z 1.2
+ // lrad is the distance from the sun to the planet
+ lrad = sqrt (loc.x * loc.x + loc.y * loc.y);
+ // light is the sun's position. the z-coordinate is whatever
+ // looks good
+ light.x = -((double)loc.x);
+ light.y = -((double)loc.y);
+ light.z = LIGHT_Z * lrad;
+ P3norm (&light, &light);
+
+ for (pt.y = 0, y = -RADIUS; pt.y <= TWORADIUS; ++pt.y, y++)
+ {
+ DWORD y_2 = y * y;
+
+ for (pt.x = 0, x = -RADIUS; pt.x <= TWORADIUS; ++pt.x, x++)
+ {
+ DWORD x_2 = x * x;
+ DWORD rad_2 = x_2 + y_2;
+ DWORD diff_int = 0;
+ POINT3 norm;
+ double diff;
+
+ if (rad_2 < RADIUS_THRES)
+ {
+ // norm is the sphere's surface normal.
+ norm.x = (double)x;
+ norm.y = (double)y;
+ norm.z = (sqrt (RADIUS_2 - x_2) * sqrt (RADIUS_2 - y_2)) /
+ RADIUS;
+ P3norm (&norm, &norm);
+ // diffuse component is norm dot light
+ diff = P3dot (&norm, &light);
+ // negative diffuse is bad
+ if (diff < 0)
+ diff = 0.0;
+#if 0
+ // Specular is not used in practice and is left here
+ // if someone decides to use it later for some reason.
+ // Specular highlight is only good for perfectly smooth
+ // surfaces, like balls (of which planets are not)
+ // This is the Phong equation
+#define LIGHT_INTENS 0.3
+#define MSHI 2
+ double fb, spec;
+ POINT3 rvec;
+ POINT3 view;
+
+ // always view along the z-axis
+ // ideally use a view point, and have the view change
+ // per pixel, but that is too much effort for now.
+ // the view MUST be normalized!
+ view.x = 0;
+ view.y = 0;
+ view.z = 1.0;
+
+ // specular highlight is the phong equation:
+ // (rvec dot view)^MSHI
+ // where rvec = (2*diff)*norm - light (reflection of light
+ // around norm)
+ P3mult (&rvec, &norm, 2 * diff);
+ P3sub (&rvec, &rvec, &light);
+ fb = P3dot (&rvec, &view);
+ if (fb > 0.0)
+ spec = LIGHT_INTENS * pow (fb, MSHI);
+ else
+ spec = 0;
+#endif
+ // adjust for the ambient light
+ if (diff < AMBIENT_LIGHT)
+ diff = AMBIENT_LIGHT;
+ // Now we antialias the edge of the spere to look nice
+ if (rad_2 > RADIUS_2)
+ {
+ diff *= 1 - (sqrt(rad_2) - RADIUS);
+ if (diff < 0)
+ diff = 0;
+ }
+ // diff_int allows us multiply by a ratio without using
+ // floating-point.
+ diff_int = (DWORD)(diff * step);
+ }
+
+ light_diff[pt.y][pt.x] = diff_int;
+ }
+ }
+}
+
+//create_aa_points creates weighted averages for
+// 4 points around the 'ideal' point at x,y
+// the concept is to compute the weight based on the
+// distance from the integer location points to the ideal point
+static void
+create_aa_points (MAP3D_POINT *ppt, double x, double y)
+{
+ double deltax, deltay, inv_deltax, inv_deltay;
+ COORD nextx, nexty;
+ COUNT i;
+ double d1, d2, d3, d4, m[4];
+
+ if (x < 0)
+ x = 0;
+ else if (x >= SPHERE_SPAN_X)
+ x = SPHERE_SPAN_X - 1;
+ if (y < 0)
+ y = 0;
+ else if (y >= MAP_HEIGHT)
+ y = MAP_HEIGHT - 1;
+
+ // get the integer value of this point
+ ppt->p[0].x = (COORD)x;
+ ppt->p[0].y = (COORD)y;
+ deltax = x - ppt->p[0].x;
+ deltay = y - ppt->p[0].y;
+
+ // if this point doesn't need modificaton, set m[0]=0
+ if (deltax == 0 && deltay == 0)
+ {
+ ppt->m[0] = 0;
+ return;
+ }
+
+ // get the neighboring points surrounding the 'ideal' point
+ if (deltax != 0)
+ nextx = ppt->p[0].x + 1;
+ else
+ nextx = ppt->p[0].x;
+ if (deltay != 0)
+ nexty = ppt->p[0].y + 1;
+ else
+ nexty = ppt->p[0].y;
+ //(x1,y)
+ ppt->p[1].x = nextx;
+ ppt->p[1].y = ppt->p[0].y;
+ //(x,y1)
+ ppt->p[2].x = ppt->p[0].x;
+ ppt->p[2].y = nexty;
+ //(x1y1)
+ ppt->p[3].x = nextx;
+ ppt->p[3].y = nexty;
+ //the square 1x1, so opposite poinnts are at 1-delta
+ inv_deltax = 1.0 - fabs (deltax);
+ inv_deltax *= inv_deltax;
+ inv_deltay = 1.0 - fabs (deltay);
+ inv_deltay *= inv_deltay;
+ deltax *= deltax;
+ deltay *= deltay;
+ //d1-d4 contain the distances from the poinnts to the ideal point
+ d1 = sqrt (deltax + deltay);
+ d2 = sqrt (inv_deltax + deltay);
+ d3 = sqrt (deltax + inv_deltay);
+ d4 = sqrt (inv_deltax + inv_deltay);
+ //compute the weights. the sum(ppt->m[])=65536
+ m[0] = 1 / (1 + d1 * (1 / d2 + 1 / d3 + 1 / d4));
+ m[1] = m[0] * d1 / d2;
+ m[2] = m[0] * d1 / d3;
+ m[3] = m[0] * d1 / d4;
+
+ for (i = 0; i < 4; i++)
+ ppt->m[i] = (DWORD)(m[i] * (1 << AA_WEIGHT_BITS) + 0.5);
+}
+
+static inline BYTE
+get_color_channel (Color c, int channel)
+{
+ switch (channel)
+ {
+ case 0:
+ return c.r;
+ case 1:
+ return c.g;
+ case 2:
+ return c.b;
+ default:
+ return 0;
+ }
+}
+
+// Creates either a red, green, or blue value by
+// computing the weighted averages of the 4 points in p
+static BYTE
+get_avg_channel (Color p[4], DWORD mult[4], int channel)
+{
+ COUNT j;
+ DWORD ci = 0;
+
+ //sum(mult[])==65536
+ //c is the red/green/blue value of this pixel
+ for (j = 0; j < 4; j++)
+ {
+ BYTE c = get_color_channel (p[j], channel);
+ ci += c * mult[j];
+ }
+ ci >>= AA_WEIGHT_BITS;
+ //check for overflow
+ if (ci > 255)
+ ci = 255;
+
+ return ((UBYTE)ci);
+}
+
+// CreateSphereTiltMap creates 'map_rotate' to map the topo data
+// for a tilted planet. It also does the sphere->plane mapping
+static void
+CreateSphereTiltMap (int angle)
+{
+ int x, y;
+ const double multx = ((double)SPHERE_SPAN_X / M_PI);
+ const double multy = ((double)MAP_HEIGHT / M_PI);
+ const double xadj = ((double)SPHERE_SPAN_X / 2.0);
+
+ for (y = -RADIUS; y <= RADIUS; y++)
+ {
+ int y_2 = y * y;
+
+ for (x = -RADIUS; x <= RADIUS; x++)
+ {
+ double dx, dy, newx, newy;
+ double da, rad, rad_2;
+ double xa, ya;
+ MAP3D_POINT *ppt = &map_rotate[y + RADIUS][x + RADIUS];
+
+ rad_2 = x * x + y_2;
+
+ if (rad_2 >= RADIUS_THRES)
+ { // pixel won't be present
+ ppt->p[0].x = x + RADIUS;
+ ppt->p[0].y = y + RADIUS;
+ ppt->m[0] = 0;
+
+ continue;
+ }
+
+ rad = sqrt (rad_2);
+ // antialiasing goes beyond the actual radius
+ if (rad >= RADIUS)
+ rad = (double)RADIUS - 0.1;
+
+ da = atan2 ((double)y, (double)x);
+ // compute the planet-tilt
+ da += M_DEG2RAD * angle;
+ dx = rad * cos (da);
+ dy = rad * sin (da);
+
+ // Map the sphere onto a plane
+ xa = acos (-dx / RADIUS);
+ ya = acos (-dy / RADIUS);
+ newx = multx * xa;
+ newy = multy * ya;
+ // Adjust for vertical curvature
+ if (ya <= 0.05 || ya >= 3.1 /* almost PI */)
+ newx = xadj; // exact centerline
+ else
+ newx = xadj + ((newx - xadj) / sin (ya));
+
+ create_aa_points (ppt, newx, newy);
+ }
+ }
+}
+
+//CreateShieldMask
+// The shield is created in two parts. This routine creates the Halo.
+// The red tint of the planet is currently applied in RenderPlanetSphere
+// This was done because the shield glows and needs to modify how the planet
+// gets lit. Currently, the planet area is transparent in the mask made by
+// this routine, but a filter can be applied if desired too.
+
+// HALO rim size
+#define SHIELD_HALO 7
+#define SHIELD_RADIUS (RADIUS + SHIELD_HALO)
+#define SHIELD_DIAM ((SHIELD_RADIUS << 1) + 1)
+#define SHIELD_RADIUS_2 (SHIELD_RADIUS * SHIELD_RADIUS)
+#define SHIELD_RADIUS_THRES ((SHIELD_RADIUS + 1) * (SHIELD_RADIUS + 1))
+#define SHIELD_HALO_GLOW (SHIELD_GLOW_COMP + SHIELD_REFLECT_COMP)
+#define SHIELD_HALO_GLOW_MIN (SHIELD_HALO_GLOW >> 2)
+
+static FRAME
+CreateShieldMask (void)
+{
+ Color clear;
+ Color *pix;
+ int x, y;
+ FRAME ShieldFrame;
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+
+ ShieldFrame = CaptureDrawable (
+ CreateDrawable (WANT_PIXMAP | WANT_ALPHA,
+ SHIELD_DIAM, SHIELD_DIAM, 1));
+
+ pix = Orbit->ScratchArray;
+ // This is 100% transparent.
+ clear = BUILD_COLOR_RGBA (0, 0, 0, 0);
+
+ for (y = -SHIELD_RADIUS; y <= SHIELD_RADIUS; y++)
+ {
+ for (x = -SHIELD_RADIUS; x <= SHIELD_RADIUS; ++x, ++pix)
+ {
+ int rad_2 = x * x + y * y;
+ // This is a non-transparent red for the halo
+ int red = SHIELD_HALO_GLOW;
+ int alpha = 255;
+ double rad;
+
+ if (rad_2 >= SHIELD_RADIUS_THRES)
+ { // outside all bounds
+ *pix = clear;
+ continue;
+ }
+ // Inside the halo
+ if (rad_2 <= RADIUS_2)
+ { // planet's pixels, ours transparent
+ *pix = clear;
+ continue;
+ }
+
+ // The halo itself
+ rad = sqrt (rad_2);
+
+ if (rad <= RADIUS + 0.8)
+ { // pixels common between the shield and planet
+ // do antialiasing using alpha
+ alpha = (int) (red * (rad - RADIUS));
+ red = 255;
+ }
+ else
+ { // shield pixels
+ red -= (int) ((red - SHIELD_HALO_GLOW_MIN) * (rad - RADIUS)
+ / SHIELD_HALO);
+ if (red < 0)
+ red = 0;
+ }
+
+ *pix = BUILD_COLOR_RGBA (red, 0, 0, alpha);
+ }
+ }
+
+ WriteFramePixelColors (ShieldFrame, Orbit->ScratchArray,
+ SHIELD_DIAM, SHIELD_DIAM);
+ SetFrameHot (ShieldFrame, MAKE_HOT_SPOT (SHIELD_RADIUS + 1,
+ SHIELD_RADIUS + 1));
+
+ return ShieldFrame;
+}
+
+// SetShieldThrobEffect adjusts the red levels in the shield glow graphic
+// the throbbing cycle is tied to the planet rotation cycle
+#define SHIELD_THROBS 12
+ // throb cycles per revolution
+#define THROB_CYCLE ((MAP_WIDTH << 8) / SHIELD_THROBS)
+#define THROB_HALF_CYCLE (THROB_CYCLE >> 1)
+
+#define THROB_MAX_LEVEL 256
+#define THROB_MIN_LEVEL 100
+#define THROB_D_LEVEL (THROB_MAX_LEVEL - THROB_MIN_LEVEL)
+
+static inline int
+shield_level (int offset)
+{
+ int level;
+
+ offset = (offset << 8) % THROB_CYCLE;
+ level = abs (offset - THROB_HALF_CYCLE);
+ level = THROB_MIN_LEVEL + level * THROB_D_LEVEL / THROB_HALF_CYCLE;
+
+ return level;
+}
+
+// See description above
+// offset is effectively the angle of rotation around the planet's axis
+void
+SetShieldThrobEffect (FRAME ShieldFrame, int offset, FRAME ThrobFrame)
+{
+ int i;
+ int width, height;
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+ Color *pix;
+ int level;
+
+ level = shield_level (offset);
+
+ width = GetFrameWidth (ShieldFrame);
+ height = GetFrameHeight (ShieldFrame);
+ ReadFramePixelColors (ShieldFrame, Orbit->ScratchArray, width, height);
+
+ for (i = 0, pix = Orbit->ScratchArray; i < width * height; ++i, ++pix)
+ {
+ Color p = *pix;
+
+ if (p.a == 255)
+ { // adjust color data for full-alpha pixels
+ p.r = p.r * level / THROB_MAX_LEVEL;
+ p.g = p.g * level / THROB_MAX_LEVEL;
+ p.b = p.b * level / THROB_MAX_LEVEL;
+ }
+ else if (p.a > 0)
+ { // adjust alpha for translucent pixels
+ p.a = p.a * level / THROB_MAX_LEVEL;
+ }
+
+ *pix = p;
+ }
+
+ WriteFramePixelColors (ThrobFrame, Orbit->ScratchArray, width, height);
+ SetFrameHot (ThrobFrame, GetFrameHot (ShieldFrame));
+}
+
+// Apply the shield to the topo image
+static void
+ApplyShieldTint (void)
+{
+ DrawMode mode, oldMode;
+ FRAME oldFrame;
+ Color tint;
+ RECT r;
+
+ // TopoFrame will be permanently changed
+ oldFrame = SetContextFGFrame (pSolarSysState->TopoFrame);
+ SetContextClipRect (NULL);
+ GetContextClipRect (&r);
+
+ tint = BUILD_COLOR_RGBA (0xff, 0x00, 0x00, 0xff);
+#ifdef USE_ALPHA_SHIELD
+ mode = MAKE_DRAW_MODE (DRAW_ALPHA, 150);
+#else
+ mode = MAKE_DRAW_MODE (DRAW_ADDITIVE, DRAW_FACTOR_1);
+#endif
+ oldMode = SetContextDrawMode (mode);
+ SetContextForeGroundColor (tint);
+ DrawFilledRectangle (&r);
+ SetContextDrawMode (oldMode);
+ SetContextFGFrame (oldFrame);
+}
+
+static inline UBYTE
+calc_map_light (UBYTE val, DWORD dif, int lvf)
+{
+ int i;
+
+ // apply diffusion
+ i = (dif * val) >> DIFFUSE_BITS;
+ // apply light variance for 3d lighting effect
+ i += (lvf * val) >> 7;
+
+ if (i < 0)
+ i = 0;
+ else if (i > 255)
+ i = 255;
+
+ return ((UBYTE)i);
+}
+
+static inline Color
+get_map_pixel (Color *pixels, int x, int y)
+{
+ return pixels[y * (MAP_WIDTH + SPHERE_SPAN_X) + x];
+}
+
+static inline int
+get_map_elev (SBYTE *elevs, int x, int y, int offset)
+{
+ return elevs[y * MAP_WIDTH + (offset + x) % MAP_WIDTH];
+}
+
+// RenderPlanetSphere builds a frame for the rotating planet view
+// offset is effectively the angle of rotation around the planet's axis
+// We use the SDL routines to directly write to the SDL_Surface to improve performance
+void
+RenderPlanetSphere (FRAME MaskFrame, int offset, BOOLEAN doThrob)
+{
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+ POINT pt;
+ Color *pix;
+ Color clear;
+ int x, y;
+ Color *pixels;
+ SBYTE *elevs;
+ int shLevel;
+
+#if PROFILE_ROTATION
+ static clock_t t = 0;
+ static int frames_done = 1;
+ clock_t t1;
+ t1 = clock ();
+#endif
+
+
+ shLevel = shield_level (offset);
+
+ pix = Orbit->ScratchArray;
+ clear = BUILD_COLOR_RGBA (0, 0, 0, 0);
+ pixels = Orbit->TopoColors + offset;
+ elevs = Orbit->lpTopoData;
+
+ for (pt.y = 0, y = -RADIUS; pt.y <= TWORADIUS; ++pt.y, ++y)
+ {
+ for (pt.x = 0, x = -RADIUS; pt.x <= TWORADIUS; ++pt.x, ++x, ++pix)
+ {
+ Color c;
+ DWORD diffus = light_diff[pt.y][pt.x];
+ int i;
+ MAP3D_POINT *ppt = &map_rotate[pt.y][pt.x];
+ int lvf; // light variance factor
+
+ if (diffus == 0)
+ { // full diffusion
+ *pix = clear;
+ continue;
+ }
+
+ // get pixel from topo map and factor from light variance map
+ if (ppt->m[0] == 0)
+ { // exact pixel from the topo map
+ c = get_map_pixel (pixels, ppt->p[0].x, ppt->p[0].y);
+ lvf = get_map_elev (elevs, ppt->p[0].x, ppt->p[0].y, offset);
+ }
+ else
+ { // fractional pixel -- blend from 4
+ Color p[4];
+ int lvsum;
+
+ // compute 'ideal' pixel
+ for (i = 0; i < 4; i++)
+ p[i] = get_map_pixel (pixels, ppt->p[i].x, ppt->p[i].y);
+
+ c.r = get_avg_channel (p, ppt->m, 0);
+ c.g = get_avg_channel (p, ppt->m, 1);
+ c.b = get_avg_channel (p, ppt->m, 2);
+
+ // compute 'ideal' light variance
+ for (i = 0, lvsum = 0; i < 4; i++)
+ lvsum += get_map_elev (elevs, ppt->p[0].x, ppt->p[0].y,
+ offset) * ppt->m[i];
+ lvf = lvsum >> AA_WEIGHT_BITS;
+ }
+
+ // Apply the lighting model. This also bounds the sphere
+ // to make it circular.
+ if (pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED)
+ {
+ int r;
+
+ // add lite red filter (3/4) component
+ c.g = (c.g >> 1) + (c.g >> 2);
+ c.b = (c.b >> 1) + (c.b >> 2);
+
+ c.r = calc_map_light (c.r, diffus, lvf);
+ c.g = calc_map_light (c.g, diffus, lvf);
+ c.b = calc_map_light (c.b, diffus, lvf);
+
+ // The shield is glow + reflect (+ filter for others)
+ r = calc_map_light (SHIELD_REFLECT_COMP, diffus, 0);
+ r += SHIELD_GLOW_COMP;
+
+ if (doThrob)
+ { // adjust red level for throbbing shield
+ r = r * shLevel / THROB_MAX_LEVEL;
+ }
+
+ r += c.r;
+ if (r > 255)
+ r = 255;
+ c.r = r;
+ }
+ else
+ {
+ c.r = calc_map_light (c.r, diffus, lvf);
+ c.g = calc_map_light (c.g, diffus, lvf);
+ c.b = calc_map_light (c.b, diffus, lvf);
+ }
+
+ c.a = 0xff;
+ *pix = c;
+ }
+ }
+
+ WriteFramePixelColors (MaskFrame, Orbit->ScratchArray, DIAMETER, DIAMETER);
+ SetFrameHot (MaskFrame, MAKE_HOT_SPOT (RADIUS + 1, RADIUS + 1));
+
+#if PROFILE_ROTATION
+ t += clock() - t1;
+ if (frames_done == MAP_WIDTH)
+ {
+ log_add (log_Debug, "Rotation frames/sec: %d/%ld(msec)=%f",
+ frames_done,
+ (long int) (((double)t / CLOCKS_PER_SEC) * 1000.0 + 0.5),
+ frames_done / ((double)t / CLOCKS_PER_SEC + 0.5));
+ frames_done = 1;
+ t = clock () - t1;
+ }
+ else
+ frames_done++;
+#endif
+}
+
+
+#define RANGE_SHIFT 6
+
+static void
+DitherMap (SBYTE *DepthArray)
+{
+#define DITHER_VARIANCE (1 << (RANGE_SHIFT - 3))
+ COUNT i;
+ SBYTE *elev;
+ DWORD rand_val = 0;
+
+ for (i = 0, elev = DepthArray; i < MAP_WIDTH * MAP_HEIGHT; ++i, ++elev)
+ {
+ // Use up the random value byte by byte
+ if ((i & 3) == 0)
+ rand_val = RandomContext_Random (SysGenRNG);
+ else
+ rand_val >>= 8;
+
+ // Bring the elevation point up or down
+ *elev += DITHER_VARIANCE / 2 - (rand_val & (DITHER_VARIANCE - 1));
+ }
+}
+
+static void
+MakeCrater (RECT *pRect, SBYTE *DepthArray, SIZE rim_delta, SIZE
+ crater_delta, BOOLEAN SetDepth)
+{
+ COORD x, y, lf_x, rt_x;
+ SIZE A, B;
+ long Asquared, TwoAsquared,
+ Bsquared, TwoBsquared;
+ long d, dx, dy;
+ COUNT TopIndex, BotIndex, rim_pixels;
+
+ A = pRect->extent.width >> 1;
+ B = pRect->extent.height >> 1;
+
+ x = 0;
+ y = B;
+
+ Asquared = (DWORD)A * A;
+ TwoAsquared = Asquared << 1;
+ Bsquared = (DWORD)B * B;
+ TwoBsquared = Bsquared << 1;
+
+ dx = 0;
+ dy = TwoAsquared * B;
+ d = Bsquared - (dy >> 1) + (Asquared >> 2);
+
+ A += pRect->corner.x;
+ B += pRect->corner.y;
+ TopIndex = (B - y) * MAP_WIDTH;
+ BotIndex = (B + y) * MAP_WIDTH;
+ rim_pixels = 1;
+ while (dx < dy)
+ {
+ if (d > 0)
+ {
+ lf_x = A - x;
+ rt_x = A + x;
+ if (SetDepth)
+ {
+ memset (&DepthArray[TopIndex + lf_x], 0, rt_x - lf_x + 1);
+ memset (&DepthArray[BotIndex + lf_x], 0, rt_x - lf_x + 1);
+ }
+ if (lf_x == rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += rim_delta;
+ DepthArray[BotIndex + lf_x] += rim_delta;
+ rim_pixels = 0;
+ }
+ else
+ {
+ do
+ {
+ DepthArray[TopIndex + lf_x] += rim_delta;
+ DepthArray[BotIndex + lf_x] += rim_delta;
+ if (lf_x != rt_x)
+ {
+ DepthArray[TopIndex + rt_x] += rim_delta;
+ DepthArray[BotIndex + rt_x] += rim_delta;
+ }
+ ++lf_x;
+ --rt_x;
+ } while (--rim_pixels);
+
+ while (lf_x < rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += crater_delta;
+ DepthArray[BotIndex + lf_x] += crater_delta;
+ DepthArray[TopIndex + rt_x] += crater_delta;
+ DepthArray[BotIndex + rt_x] += crater_delta;
+ ++lf_x;
+ --rt_x;
+ }
+
+ if (lf_x == rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += crater_delta;
+ DepthArray[BotIndex + lf_x] += crater_delta;
+ }
+ }
+
+ --y;
+ TopIndex += MAP_WIDTH;
+ BotIndex -= MAP_WIDTH;
+ dy -= TwoAsquared;
+ d -= dy;
+ }
+
+ ++rim_pixels;
+ ++x;
+ dx += TwoBsquared;
+ d += Bsquared + dx;
+ }
+
+ d += ((((Asquared - Bsquared) * 3) >> 1) - (dx + dy)) >> 1;
+
+ while (y > 0)
+ {
+ lf_x = A - x;
+ rt_x = A + x;
+ if (SetDepth)
+ {
+ memset (&DepthArray[TopIndex + lf_x], 0, rt_x - lf_x + 1);
+ memset (&DepthArray[BotIndex + lf_x], 0, rt_x - lf_x + 1);
+ }
+ if (lf_x == rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += rim_delta;
+ DepthArray[BotIndex + lf_x] += rim_delta;
+ }
+ else
+ {
+ do
+ {
+ DepthArray[TopIndex + lf_x] += rim_delta;
+ DepthArray[BotIndex + lf_x] += rim_delta;
+ if (lf_x != rt_x)
+ {
+ DepthArray[TopIndex + rt_x] += rim_delta;
+ DepthArray[BotIndex + rt_x] += rim_delta;
+ }
+ ++lf_x;
+ --rt_x;
+ } while (--rim_pixels);
+
+ while (lf_x < rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += crater_delta;
+ DepthArray[BotIndex + lf_x] += crater_delta;
+ DepthArray[TopIndex + rt_x] += crater_delta;
+ DepthArray[BotIndex + rt_x] += crater_delta;
+ ++lf_x;
+ --rt_x;
+ }
+
+ if (lf_x == rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += crater_delta;
+ DepthArray[BotIndex + lf_x] += crater_delta;
+ }
+ }
+
+ if (d < 0)
+ {
+ ++x;
+ dx += TwoBsquared;
+ d += dx;
+ }
+
+ rim_pixels = 1;
+ --y;
+ TopIndex += MAP_WIDTH;
+ BotIndex -= MAP_WIDTH;
+ dy -= TwoAsquared;
+ d += Asquared - dy;
+ }
+
+ lf_x = A - x;
+ rt_x = A + x;
+ if (SetDepth)
+ memset (&DepthArray[TopIndex + lf_x], 0, rt_x - lf_x + 1);
+ if (lf_x == rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += rim_delta;
+ }
+ else
+ {
+ do
+ {
+ DepthArray[TopIndex + lf_x] += rim_delta;
+ if (lf_x != rt_x)
+ DepthArray[TopIndex + rt_x] += rim_delta;
+ ++lf_x;
+ --rt_x;
+ } while (--rim_pixels);
+
+ while (lf_x < rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += crater_delta;
+ DepthArray[TopIndex + rt_x] += crater_delta;
+ ++lf_x;
+ --rt_x;
+ }
+
+ if (lf_x == rt_x)
+ {
+ DepthArray[TopIndex + lf_x] += crater_delta;
+ }
+ }
+}
+
+#define NUM_BAND_COLORS 4
+
+static void
+MakeStorms (COUNT storm_count, SBYTE *DepthArray)
+{
+#define MAX_STORMS 8
+ COUNT i;
+ RECT storm_r[MAX_STORMS];
+ RECT *pstorm_r;
+
+ pstorm_r = &storm_r[i = storm_count];
+ while (i--)
+ {
+ BOOLEAN intersect;
+ DWORD rand_val;
+ UWORD loword, hiword;
+ SIZE band_delta;
+
+ --pstorm_r;
+ do
+ {
+ COUNT j;
+
+ intersect = FALSE;
+
+ rand_val = RandomContext_Random (SysGenRNG);
+ loword = LOWORD (rand_val);
+ hiword = HIWORD (rand_val);
+ switch (HIBYTE (hiword) & 31)
+ {
+ case 0:
+ pstorm_r->extent.height =
+ (LOBYTE (hiword) % (MAP_HEIGHT >> 2))
+ + (MAP_HEIGHT >> 2);
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ pstorm_r->extent.height =
+ (LOBYTE (hiword) % (MAP_HEIGHT >> 3))
+ + (MAP_HEIGHT >> 3);
+ break;
+ default:
+ pstorm_r->extent.height =
+ (LOBYTE (hiword) % (MAP_HEIGHT >> 4))
+ + 4;
+ break;
+ }
+
+ if (pstorm_r->extent.height <= 4)
+ pstorm_r->extent.height += 4;
+
+ rand_val = RandomContext_Random (SysGenRNG);
+ loword = LOWORD (rand_val);
+ hiword = HIWORD (rand_val);
+
+ pstorm_r->extent.width = pstorm_r->extent.height
+ + (LOBYTE (loword) % pstorm_r->extent.height);
+
+ pstorm_r->corner.x = HIBYTE (loword)
+ % (MAP_WIDTH - pstorm_r->extent.width);
+ pstorm_r->corner.y = LOBYTE (loword)
+ % (MAP_HEIGHT - pstorm_r->extent.height);
+
+ for (j = i + 1; j < storm_count; ++j)
+ {
+ COORD x, y;
+ SIZE w, h;
+
+ x = storm_r[j].corner.x - pstorm_r->corner.x;
+ y = storm_r[j].corner.y - pstorm_r->corner.y;
+ w = x + storm_r[j].extent.width + 4;
+ h = y + storm_r[j].extent.height + 4;
+ intersect = (BOOLEAN) (w > 0 && h > 0
+ && x < pstorm_r->extent.width + 4
+ && y < pstorm_r->extent.height + 4);
+ if (intersect)
+ break;
+ }
+
+ } while (intersect);
+
+ MakeCrater (pstorm_r, DepthArray, 6, 6, FALSE);
+ ++pstorm_r->corner.x;
+ ++pstorm_r->corner.y;
+ pstorm_r->extent.width -= 2;
+ pstorm_r->extent.height -= 2;
+
+ band_delta = HIBYTE (loword) & ((3 << RANGE_SHIFT) + 20);
+
+ MakeCrater (pstorm_r, DepthArray,
+ band_delta, band_delta, TRUE);
+ ++pstorm_r->corner.x;
+ ++pstorm_r->corner.y;
+ pstorm_r->extent.width -= 2;
+ pstorm_r->extent.height -= 2;
+
+ band_delta += 2;
+ if (pstorm_r->extent.width > 2 && pstorm_r->extent.height > 2)
+ {
+ MakeCrater (pstorm_r, DepthArray,
+ band_delta, band_delta, TRUE);
+ ++pstorm_r->corner.x;
+ ++pstorm_r->corner.y;
+ pstorm_r->extent.width -= 2;
+ pstorm_r->extent.height -= 2;
+ }
+
+ band_delta += 2;
+ if (pstorm_r->extent.width > 2 && pstorm_r->extent.height > 2)
+ {
+ MakeCrater (pstorm_r, DepthArray,
+ band_delta, band_delta, TRUE);
+ ++pstorm_r->corner.x;
+ ++pstorm_r->corner.y;
+ pstorm_r->extent.width -= 2;
+ pstorm_r->extent.height -= 2;
+ }
+
+ band_delta += 4;
+ MakeCrater (pstorm_r, DepthArray,
+ band_delta, band_delta, TRUE);
+ }
+}
+
+static void
+MakeGasGiant (COUNT num_bands, SBYTE *DepthArray, RECT *pRect, SIZE
+ depth_delta)
+{
+ COORD last_y, next_y;
+ SIZE band_error, band_bump, band_delta;
+ COUNT i, j, band_height;
+ SBYTE *lpDst;
+ UWORD loword, hiword;
+ DWORD rand_val;
+
+ band_height = pRect->extent.height / num_bands;
+ band_bump = pRect->extent.height % num_bands;
+ band_error = num_bands >> 1;
+ lpDst = DepthArray;
+
+ band_delta = ((LOWORD (RandomContext_Random (SysGenRNG))
+ & (NUM_BAND_COLORS - 1)) << RANGE_SHIFT)
+ + (1 << (RANGE_SHIFT - 1));
+ last_y = next_y = 0;
+ for (i = num_bands; i > 0; --i)
+ {
+ COORD cur_y;
+
+ rand_val = RandomContext_Random (SysGenRNG);
+ loword = LOWORD (rand_val);
+ hiword = HIWORD (rand_val);
+
+ next_y += band_height;
+ if ((band_error -= band_bump) < 0)
+ {
+ ++next_y;
+ band_error += num_bands;
+ }
+ if (i == 1)
+ cur_y = pRect->extent.height;
+ else
+ {
+ RECT r;
+
+ cur_y = next_y
+ + ((band_height - 2) >> 1)
+ - ((LOBYTE (hiword) % (band_height - 2)) + 1);
+ r.corner.x = r.corner.y = 0;
+ r.extent.width = pRect->extent.width;
+ r.extent.height = 5;
+ DeltaTopography (50,
+ &DepthArray[(cur_y - 2) * r.extent.width],
+ &r, depth_delta);
+ }
+
+ for (j = cur_y - last_y; j > 0; --j)
+ {
+ COUNT k;
+
+ for (k = pRect->extent.width; k > 0; --k)
+ *lpDst++ += band_delta;
+ }
+
+ last_y = cur_y;
+ band_delta = (band_delta
+ + ((((LOBYTE (loword) & 1) << 1) - 1) << RANGE_SHIFT))
+ & (((1 << RANGE_SHIFT) * NUM_BAND_COLORS) - 1);
+ }
+
+ MakeStorms (4 + (RandomContext_Random (SysGenRNG) & 3) + 1, DepthArray);
+
+ DitherMap (DepthArray);
+}
+
+static void
+ValidateMap (SBYTE *DepthArray)
+{
+ BYTE state;
+ BYTE pixel_count[2], lb[2];
+ SBYTE last_byte;
+ COUNT i;
+ SBYTE *lpDst;
+
+ i = MAP_WIDTH - 1;
+ lpDst = DepthArray;
+ last_byte = *lpDst++;
+ state = pixel_count[0] = pixel_count[1] = 0;
+ do
+ {
+ if (pixel_count[state]++ == 0)
+ lb[state] = last_byte;
+
+ if (last_byte > *lpDst)
+ {
+ if (last_byte - *lpDst > 128)
+ state ^= 1;
+ }
+ else
+ {
+ if (*lpDst - last_byte > 128)
+ state ^= 1;
+ }
+ last_byte = *lpDst++;
+ } while (--i);
+
+ i = MAP_WIDTH * MAP_HEIGHT;
+ lpDst = DepthArray;
+ if (pixel_count[0] > pixel_count[1])
+ last_byte = lb[0];
+ else
+ last_byte = lb[1];
+ do
+ {
+ if (last_byte > *lpDst)
+ {
+ if (last_byte - *lpDst > 128)
+ *lpDst = last_byte;
+ }
+ else
+ {
+ if (*lpDst - last_byte > 128)
+ *lpDst = last_byte;
+ }
+ last_byte = *lpDst++;
+ } while (--i);
+}
+
+static void
+planet_orbit_init (void)
+{
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+
+ Orbit->SphereFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP | WANT_ALPHA, DIAMETER, DIAMETER, 2));
+ Orbit->TintFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP, MAP_WIDTH, MAP_HEIGHT, 1));
+ Orbit->ObjectFrame = 0;
+ Orbit->WorkFrame = 0;
+ Orbit->lpTopoData = HCalloc (MAP_WIDTH * MAP_HEIGHT);
+ Orbit->TopoZoomFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP, MAP_WIDTH << 2, MAP_HEIGHT << 2, 1));
+ Orbit->TopoColors = HMalloc (sizeof (Orbit->TopoColors[0])
+ * (MAP_HEIGHT * (MAP_WIDTH + SPHERE_SPAN_X)));
+ // always allocate the scratch array to largest needed size
+ Orbit->ScratchArray = HMalloc (sizeof (Orbit->ScratchArray[0])
+ * (SHIELD_DIAM) * (SHIELD_DIAM));
+}
+
+static unsigned
+frandom (void)
+{
+ static unsigned seed = 0x12345678;
+
+ if (seed == 0)
+ seed = 15807;
+ seed = (seed >> 4) * 227;
+
+ return seed;
+}
+
+static inline int
+TopoVarianceFactor (int step, int allowed, int min)
+{
+#define SCALE_SHIFT 8
+ return ((abs(step) * allowed) >> SCALE_SHIFT) + min;
+}
+
+static inline int
+TopoVarianceCalc (int factor)
+{
+ if (factor == 0)
+ return 0;
+ else
+ return (frandom () % factor) - (factor >> 1);
+}
+
+static void
+TopoScale4x (SBYTE *pDstTopo, SBYTE *pSrcTopo, int num_faults, int fault_var)
+{
+ // Interpolate the topographical data by connecting the elevations
+ // to their nearest neighboors using straight lines (in random
+ // direction) with random variance factors defined by
+ // num_faults and fault_var args
+#define AVG_VARIANCE 250
+ int x, y;
+ const int w = MAP_WIDTH, h = MAP_HEIGHT;
+ const int spitch = MAP_WIDTH, dpitch = MAP_WIDTH * 4;
+ SBYTE *pSrc;
+ SBYTE *pDst;
+ int* prevrow;
+ int* prow;
+ int elev[5][5];
+ int var_allow, var_min;
+ static const struct line_def_t
+ {
+ int x0, y0, x1, y1;
+ int dx, dy;
+ }
+ fill_lines[4][6] =
+ {
+ { // diag set 0
+ { 0, 2, 2, 0, 1, -1},
+ { 0, 3, 3, 0, 1, -1},
+ { 0, 4, 4, 0, 1, -1},
+ { 1, 4, 4, 1, 1, -1},
+ { 2, 4, 4, 2, 1, -1},
+ {-1, -1, -1, -1, 0, 0}, // term
+ },
+ { // diag set 1
+ { 0, 2, 2, 4, 1, 1},
+ { 0, 1, 3, 4, 1, 1},
+ { 0, 0, 4, 4, 1, 1},
+ { 1, 0, 4, 3, 1, 1},
+ { 2, 0, 4, 2, 1, 1},
+ {-1, -1, -1, -1, 0, 0}, // term
+ },
+ { // horizontal
+ { 0, 1, 4, 1, 1, 0},
+ { 0, 2, 4, 2, 1, 0},
+ { 0, 3, 4, 3, 1, 0},
+ {-1, -1, -1, -1, 0, 0}, // term
+ },
+ { // vertical
+ { 1, 0, 1, 4, 0, 1},
+ { 2, 0, 2, 4, 0, 1},
+ { 3, 0, 3, 4, 0, 1},
+ {-1, -1, -1, -1, 0, 0}, // term
+ },
+ };
+
+ prevrow = (int *) HMalloc ((MAP_WIDTH * 4 + 1) * sizeof(prevrow[0]));
+
+ var_allow = (num_faults << SCALE_SHIFT) / AVG_VARIANCE;
+ var_min = fault_var << SCALE_SHIFT;
+
+ //memset (pDstTopo, 0, MAP_WIDTH * MAP_HEIGHT * 16);
+
+ // init the first row in advance
+ pSrc = pSrcTopo;
+ prow = prevrow;
+#define STEP_RANGE (4 - 1)
+ prow[0] = ((int)pSrc[0]) << SCALE_SHIFT;;
+ for (x = 0; x < w; ++x, ++pSrc, prow += 4)
+ {
+ int x2;
+ int val, step, rndfact;
+
+ // next point in row
+ if (x < w - 1)
+ // one right
+ prow[4] = ((int)pSrc[1]) << SCALE_SHIFT;
+ else
+ // wrap around
+ prow[4] = ((int)pSrc[1 - spitch]) << SCALE_SHIFT;
+
+ // compute elevations between 2 points
+ val = prow[0];
+ step = (prow[4] - val) / STEP_RANGE;
+ rndfact = TopoVarianceFactor (step, var_allow, var_min);
+ for (x2 = 1, val += step; x2 < 4; ++x2, val += step)
+ prow[x2] = val + TopoVarianceCalc (rndfact);
+ }
+
+ pSrc = pSrcTopo;
+ pDst = pDstTopo;
+ for (y = 0; y < h; ++y, pDst += dpitch * 3)
+ {
+ int x2, y2;
+ SBYTE *p;
+ int val, step, rndfact;
+ const struct line_def_t* pld;
+
+ prow = prevrow;
+ // prime the first interpolated column
+ elev[4][0] = prow[0];
+ if (y < h - 1)
+ elev[4][4] = ((int)pSrc[spitch]) << SCALE_SHIFT;
+ else
+ elev[4][4] = elev[4][0];
+ // compute elevations for interpolated column
+ val = elev[4][0];
+ step = (elev[4][4] - val) / STEP_RANGE;
+ rndfact = TopoVarianceFactor (step, var_allow, var_min);
+ for (y2 = 1, val += step; y2 < 4; ++y2, val += step)
+ elev[4][y2] = val + TopoVarianceCalc (rndfact);
+
+ for (x = 0; x < w; ++x, ++pSrc, pDst += 4, prow += 4)
+ {
+ // recall the first interpolated row from prevrow
+ for (x2 = 0; x2 <= 4; ++x2)
+ elev[x2][0] = prow[x2];
+ // recall the first interpolated column
+ for (y2 = 1; y2 <= 4; ++y2)
+ elev[0][y2] = elev[4][y2];
+
+ if (y < h - 1)
+ {
+ if (x < w - 1)
+ // one right, one down
+ elev[4][4] = ((int)pSrc[1 + spitch]) << SCALE_SHIFT;
+ else
+ // wrap around, one down
+ elev[4][4] = ((int)pSrc[1]) << SCALE_SHIFT;
+ }
+ else
+ {
+ elev[4][4] = elev[4][0];
+ }
+
+ // compute elevations for the rest of square borders first
+ val = elev[0][4];
+ step = (elev[4][4] - val) / STEP_RANGE;
+ rndfact = TopoVarianceFactor (step, var_allow, var_min);
+ for (x2 = 1, val += step; x2 < 4; ++x2, val += step)
+ elev[x2][4] = val + TopoVarianceCalc (rndfact);
+
+ val = elev[4][0];
+ step = (elev[4][4] - val) / STEP_RANGE;
+ rndfact = TopoVarianceFactor (step, var_allow, var_min);
+ for (y2 = 1, val += step; y2 < 4; ++y2, val += step)
+ elev[4][y2] = val + TopoVarianceCalc (rndfact);
+
+ // fill in the rest by connecting opposing elevations
+ // some randomness to determine which elevations to connect
+ for (pld = fill_lines[frandom () & 3]; pld->x0 >= 0; ++pld)
+ {
+ int num_steps;
+
+ x2 = pld->x0;
+ y2 = pld->y0;
+ val = elev[x2][y2];
+ num_steps = pld->x1 - pld->x0;
+ if (num_steps == 0)
+ num_steps = pld->y1 - pld->y0;
+ step = (elev[pld->x1][pld->y1] - val) / num_steps;
+ rndfact = TopoVarianceFactor (step, var_allow, var_min);
+
+ for (x2 += pld->dx, y2 += pld->dy, val += step;
+ x2 != pld->x1 || y2 != pld->y1;
+ x2 += pld->dx, y2 += pld->dy, val += step)
+ {
+ elev[x2][y2] = val + TopoVarianceCalc (rndfact);
+ }
+ }
+
+ // output the interpolated topography
+ for (y2 = 0; y2 < 4; ++y2)
+ {
+ p = pDst + y2 * dpitch;
+ for (x2 = 0; x2 < 4; ++x2, ++p)
+ {
+ int e = elev[x2][y2] >> SCALE_SHIFT;
+ if (e > 127)
+ e = 127;
+ else if (e < -128)
+ e = -128;
+ *p = (SBYTE)e;
+ }
+ }
+
+ // save last interpolated row to prevrow for later
+ for (x2 = 0; x2 < 4; ++x2)
+ prow[x2] = elev[x2][4];
+ }
+ // save last row point
+ prow[0] = elev[4][4];
+ }
+
+ HFree (prevrow);
+}
+
+
+// GenerateLightMap produces a surface light variance map for the
+// rotating planet by, first, transforming absolute elevation data
+// into normalized relative and then applying a weighted
+// average-median of surrounding points
+// Lots of pure Voodoo here ;)
+// the goal is a 3D illusion, not mathematically correct lighting
+
+#define LMAP_AVG_BLOCK ((MAP_HEIGHT + 4) / 5)
+#define LMAP_MAX_DIST ((LMAP_AVG_BLOCK + 1) >> 1)
+#define LMAP_WEIGHT_THRES (LMAP_MAX_DIST * 2 / 3)
+
+typedef struct
+{
+ int min;
+ int max;
+ int avg;
+
+} elev_block_t;
+
+static inline void
+get_vblock_avg (elev_block_t *pblk, SBYTE *pTopo, int x, int y)
+{
+ SBYTE *elev = pTopo;
+ int y0, y1, i;
+ int min = 127, max = -127;
+ int avg = 0, total_weight = 0;
+
+ // surface wraps around along x
+ x = (x + MAP_WIDTH) % MAP_WIDTH;
+
+ y0 = y - LMAP_MAX_DIST;
+ y1 = y + LMAP_MAX_DIST;
+ if (y0 < 0)
+ y0 = 0;
+ if (y1 > MAP_HEIGHT)
+ y1 = MAP_HEIGHT;
+
+ elev = pTopo + y0 * MAP_WIDTH + x;
+ for (i = y0; i < y1; ++i, elev += MAP_WIDTH)
+ {
+ int delta = abs (i - y);
+ int weight = 255; // full weight
+ int v = *elev;
+
+ if (delta >= LMAP_WEIGHT_THRES)
+ { // too far -- progressively reduced weight
+ weight = weight * (LMAP_MAX_DIST - delta + 1)
+ / (LMAP_MAX_DIST - LMAP_WEIGHT_THRES + 2);
+ }
+
+ if (v > max)
+ max = v;
+ if (v < min)
+ min = v;
+ avg += v * weight;
+ total_weight += weight;
+ }
+ avg /= total_weight;
+
+ pblk->min = min;
+ pblk->max = max;
+ pblk->avg = avg / (y1 - y0);
+}
+
+// See description above
+static void
+GenerateLightMap (SBYTE *pTopo, int w, int h)
+{
+#define LMAP_BLOCKS (2 * LMAP_MAX_DIST + 1)
+ int x, y;
+ elev_block_t vblocks[LMAP_BLOCKS];
+ // we use a running block average to reduce the amount of work
+ // where a block is a vertical line of map points
+ SBYTE *elev;
+ int min, max, med;
+ int sfact, spread;
+
+ // normalize the topo data
+ min = 127;
+ max = -128;
+ for (x = 0, elev = pTopo; x < w * h; ++x, ++elev)
+ {
+ int v = *elev;
+ if (v > max)
+ max = v;
+ if (v < min)
+ min = v;
+ }
+ med = (min + max) / 2;
+ spread = max - med;
+
+ if (spread == 0)
+ { // perfectly smooth surface -- nothing to do but
+ // level it out completely
+ if (max != 0)
+ memset (pTopo, 0, w * h);
+ return;
+ }
+
+ // these are whatever looks right
+ if (spread < 10)
+ sfact = 30; // minimal spread
+ else if (spread < 30)
+ sfact = 60;
+ else
+ sfact = 100; // full spread
+
+ // apply spread
+ for (x = 0, elev = pTopo; x < w * h; ++x, ++elev)
+ {
+ int v = *elev;
+ v = (v - med) * sfact / spread;
+ *elev = v;
+ }
+
+ // compute and apply weighted averages of surrounding points
+ for (y = 0, elev = pTopo; y < h; ++y)
+ {
+ elev_block_t *pblk;
+ int i;
+
+ // prime the running block average
+ // get the minimum, maximum and avg elevation for each block
+ for (i = -LMAP_MAX_DIST; i < LMAP_MAX_DIST; ++i)
+ {
+ // blocks wrap around on both sides
+ pblk = vblocks + ((i + LMAP_BLOCKS) % LMAP_BLOCKS);
+
+ get_vblock_avg (pblk, pTopo, i, y);
+ }
+
+ for (x = 0; x < w; ++x, ++elev)
+ {
+ int avg = 0, total_weight = 0;
+
+ min = 127;
+ max = -127;
+
+ // prepare next block as we move along x
+ pblk = vblocks + ((x + LMAP_MAX_DIST) % LMAP_BLOCKS);
+ get_vblock_avg (pblk, pTopo, x + LMAP_MAX_DIST, y);
+
+ // compute the min, max and weighted avg of blocks
+ for (i = x - LMAP_MAX_DIST; i <= x + LMAP_MAX_DIST; ++i)
+ {
+ int delta = abs (i - x);
+ int weight = 255; // full weight
+
+ pblk = vblocks + ((i + LMAP_BLOCKS) % LMAP_BLOCKS);
+
+ if (delta >= LMAP_WEIGHT_THRES)
+ { // too far -- progressively reduced weight
+ weight = weight * (LMAP_MAX_DIST - delta + 1)
+ / (LMAP_MAX_DIST - LMAP_WEIGHT_THRES + 2);
+ }
+
+ if (pblk->max > max)
+ max = pblk->max;
+ if (pblk->min < min)
+ min = pblk->min;
+
+ avg += pblk->avg * weight;
+ total_weight += weight;
+ }
+ avg /= total_weight;
+
+ // This is mostly Voodoo
+ // figure out what kind of relative lighting factor
+ // to assign to this point
+#if 0
+ // relative to median
+ med = (min + max) / 2; // median
+ *elev = (int)*elev - med;
+#else
+ // relative to median of (average, median)
+ med = (min + max) / 2; // median
+ med = (med + avg) / 2;
+ *elev = (int)*elev - med;
+#endif
+ }
+ }
+}
+
+// Sets the SysGenRNG to the required state first.
+void
+GeneratePlanetSurface (PLANET_DESC *pPlanetDesc, FRAME SurfDefFrame)
+{
+ RECT r;
+ const PlanetFrame *PlanDataPtr;
+ PLANET_INFO *PlanetInfo = &pSolarSysState->SysInfo.PlanetInfo;
+ COUNT i, y;
+ POINT loc;
+ CONTEXT OldContext;
+ CONTEXT TopoContext;
+ PLANET_ORBIT *Orbit = &pSolarSysState->Orbit;
+ BOOLEAN shielded = (pPlanetDesc->data_index & PLANET_SHIELDED) != 0;
+
+ RandomContext_SeedRandom (SysGenRNG, pPlanetDesc->rand_seed);
+
+ TopoContext = CreateContext ("Plangen.TopoContext");
+ OldContext = SetContext (TopoContext);
+ planet_orbit_init ();
+
+ PlanDataPtr = &PlanData[pPlanetDesc->data_index & ~PLANET_SHIELDED];
+
+ if (SurfDefFrame)
+ { // This is a defined planet; pixmap for the topography and
+ // elevation data is supplied in Surface Definition frame
+ BOOLEAN DeleteDef = FALSE;
+ FRAME ElevFrame;
+
+ // surface pixmap
+ SurfDefFrame = SetAbsFrameIndex (SurfDefFrame, 0);
+ if (GetFrameWidth (SurfDefFrame) != MAP_WIDTH
+ || GetFrameHeight (SurfDefFrame) != MAP_HEIGHT)
+ {
+ pSolarSysState->TopoFrame = CaptureDrawable (RescaleFrame (
+ SurfDefFrame, MAP_WIDTH, MAP_HEIGHT));
+ // will not need the passed FRAME anymore
+ DeleteDef = TRUE;
+ }
+ else
+ pSolarSysState->TopoFrame = SurfDefFrame;
+
+ if (GetFrameCount (SurfDefFrame) > 1)
+ { // 2nd frame is elevation data
+ int i;
+ SBYTE* elev;
+
+ ElevFrame = SetAbsFrameIndex (SurfDefFrame, 1);
+ if (GetFrameWidth (ElevFrame) != MAP_WIDTH
+ || GetFrameHeight (ElevFrame) != MAP_HEIGHT)
+ {
+ ElevFrame = CaptureDrawable (RescaleFrame (ElevFrame,
+ MAP_WIDTH, MAP_HEIGHT));
+ }
+
+ // grab the elevation data in 1 byte per pixel format
+ ReadFramePixelIndexes (ElevFrame, (BYTE *)Orbit->lpTopoData,
+ MAP_WIDTH, MAP_HEIGHT);
+ // the supplied data is in unsigned format, must convert
+ for (i = 0, elev = Orbit->lpTopoData;
+ i < MAP_WIDTH * MAP_HEIGHT;
+ ++i, ++elev)
+ {
+ *elev = *(BYTE *)elev - 128;
+ }
+ }
+ else
+ { // no elevation data -- planet flat as a pancake
+ memset (Orbit->lpTopoData, 0, MAP_WIDTH * MAP_HEIGHT);
+ }
+
+ if (DeleteDef)
+ DestroyDrawable (ReleaseDrawable (SurfDefFrame));
+ }
+ else
+ { // Generate planet surface elevation data and look
+
+ r.corner.x = r.corner.y = 0;
+ r.extent.width = MAP_WIDTH;
+ r.extent.height = MAP_HEIGHT;
+ {
+ memset (Orbit->lpTopoData, 0, MAP_WIDTH * MAP_HEIGHT);
+ switch (PLANALGO (PlanDataPtr->Type))
+ {
+ case GAS_GIANT_ALGO:
+ MakeGasGiant (PlanDataPtr->num_faults,
+ Orbit->lpTopoData, &r, PlanDataPtr->fault_depth);
+ break;
+ case TOPO_ALGO:
+ case CRATERED_ALGO:
+ if (PlanDataPtr->num_faults)
+ DeltaTopography (PlanDataPtr->num_faults,
+ Orbit->lpTopoData, &r,
+ PlanDataPtr->fault_depth);
+
+ for (i = 0; i < PlanDataPtr->num_blemishes; ++i)
+ {
+ RECT crater_r;
+ UWORD loword;
+
+ loword = LOWORD (RandomContext_Random (SysGenRNG));
+ switch (HIBYTE (loword) & 31)
+ {
+ case 0:
+ crater_r.extent.width =
+ (LOBYTE (loword) % (MAP_HEIGHT >> 2))
+ + (MAP_HEIGHT >> 2);
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ crater_r.extent.width =
+ (LOBYTE (loword) % (MAP_HEIGHT >> 3))
+ + (MAP_HEIGHT >> 3);
+ break;
+ default:
+ crater_r.extent.width =
+ (LOBYTE (loword) % (MAP_HEIGHT >> 4))
+ + 4;
+ break;
+ }
+
+ loword = LOWORD (RandomContext_Random (SysGenRNG));
+ crater_r.extent.height = crater_r.extent.width;
+ crater_r.corner.x = HIBYTE (loword)
+ % (MAP_WIDTH - crater_r.extent.width);
+ crater_r.corner.y = LOBYTE (loword)
+ % (MAP_HEIGHT - crater_r.extent.height);
+ MakeCrater (&crater_r, Orbit->lpTopoData,
+ PlanDataPtr->fault_depth << 2,
+ -(PlanDataPtr->fault_depth << 2),
+ FALSE);
+ }
+
+ if (PLANALGO (PlanDataPtr->Type) == CRATERED_ALGO)
+ DitherMap (Orbit->lpTopoData);
+ ValidateMap (Orbit->lpTopoData);
+ break;
+ }
+ }
+ pSolarSysState->TopoFrame = CaptureDrawable (
+ CreateDrawable (WANT_PIXMAP, (SIZE)MAP_WIDTH,
+ (SIZE)MAP_HEIGHT, 1));
+ pSolarSysState->OrbitalCMap = CaptureColorMap (
+ LoadColorMap (PlanDataPtr->CMapInstance));
+ pSolarSysState->XlatRef = CaptureStringTable (
+ LoadStringTable (PlanDataPtr->XlatTabInstance));
+
+ if (PlanetInfo->SurfaceTemperature > HOT_THRESHOLD)
+ {
+ pSolarSysState->OrbitalCMap = SetAbsColorMapIndex (
+ pSolarSysState->OrbitalCMap, 2);
+ pSolarSysState->XlatRef = SetAbsStringTableIndex (
+ pSolarSysState->XlatRef, 2);
+ }
+ else if (PlanetInfo->SurfaceTemperature > COLD_THRESHOLD)
+ {
+ pSolarSysState->OrbitalCMap = SetAbsColorMapIndex (
+ pSolarSysState->OrbitalCMap, 1);
+ pSolarSysState->XlatRef = SetAbsStringTableIndex (
+ pSolarSysState->XlatRef, 1);
+ }
+ pSolarSysState->XlatPtr = GetStringAddress (pSolarSysState->XlatRef);
+ RenderTopography (pSolarSysState->TopoFrame,
+ Orbit->lpTopoData, MAP_WIDTH, MAP_HEIGHT);
+
+ }
+
+ if (!shielded && PlanetInfo->AtmoDensity != GAS_GIANT_ATMOSPHERE)
+ { // produce 4x scaled topo image for Planetside
+ // for the planets that we can land on
+ SBYTE *pScaledTopo = HMalloc (MAP_WIDTH * 4 * MAP_HEIGHT * 4);
+ if (pScaledTopo)
+ {
+ TopoScale4x (pScaledTopo, Orbit->lpTopoData,
+ PlanDataPtr->num_faults, PlanDataPtr->fault_depth
+ * (PLANALGO (PlanDataPtr->Type) == CRATERED_ALGO ? 2 : 1 ));
+ RenderTopography (Orbit->TopoZoomFrame, pScaledTopo,
+ MAP_WIDTH * 4, MAP_HEIGHT * 4);
+
+ HFree (pScaledTopo);
+ }
+ }
+
+ // Generate a pixel array from the Topography map.
+ // We use this instead of lpTopoData because it needs to be
+ // WAP_WIDTH+SPHERE_SPAN_X wide and we need this method for Earth anyway.
+ // It may be more efficient to build it from lpTopoData instead of the
+ // FRAMPTR though.
+ ReadFramePixelColors (pSolarSysState->TopoFrame, Orbit->TopoColors,
+ MAP_WIDTH + SPHERE_SPAN_X, MAP_HEIGHT);
+ // Extend the width from MAP_WIDTH to MAP_WIDTH+SPHERE_SPAN_X
+ for (y = 0; y < MAP_HEIGHT * (MAP_WIDTH + SPHERE_SPAN_X);
+ y += MAP_WIDTH + SPHERE_SPAN_X)
+ memcpy (Orbit->TopoColors + y + MAP_WIDTH, Orbit->TopoColors + y,
+ SPHERE_SPAN_X * sizeof (Orbit->TopoColors[0]));
+
+ if (PLANALGO (PlanDataPtr->Type) != GAS_GIANT_ALGO)
+ { // convert topo data to a light map, based on relative
+ // map point elevations
+ GenerateLightMap (Orbit->lpTopoData, MAP_WIDTH, MAP_HEIGHT);
+ }
+ else
+ { // gas giants are pretty much flat
+ memset (Orbit->lpTopoData, 0, MAP_WIDTH * MAP_HEIGHT);
+ }
+
+ if (pSolarSysState->pOrbitalDesc->pPrevDesc ==
+ &pSolarSysState->SunDesc[0])
+ { // this is a planet -- get its location
+ loc = pSolarSysState->pOrbitalDesc->location;
+ }
+ else
+ { // this is a moon -- get its planet's location
+ loc = pSolarSysState->pOrbitalDesc->pPrevDesc->location;
+ }
+
+ // Rotating planet sphere initialization
+ GenerateSphereMask (loc);
+ CreateSphereTiltMap (PlanetInfo->AxialTilt);
+ if (shielded)
+ Orbit->ObjectFrame = CreateShieldMask ();
+ InitSphereRotation (1 - 2 * (PlanetInfo->AxialTilt & 1), shielded);
+
+ if (shielded)
+ { // This overwrites pSolarSysState->TopoFrame, so everything that
+ // needs it has to come before
+ ApplyShieldTint ();
+ }
+
+ SetContext (OldContext);
+ DestroyContext (TopoContext);
+}
+
diff --git a/src/uqm/planets/pstarmap.c b/src/uqm/planets/pstarmap.c
new file mode 100644
index 0000000..cd33858
--- /dev/null
+++ b/src/uqm/planets/pstarmap.c
@@ -0,0 +1,1631 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "scan.h"
+#include "../colors.h"
+#include "../controls.h"
+#include "../menustat.h"
+#include "../starmap.h"
+#include "../races.h"
+#include "../gameopt.h"
+#include "../gamestr.h"
+#include "../globdata.h"
+#include "../shipcont.h"
+#include "../units.h"
+#include "../hyper.h"
+#include "../sis.h"
+ // for DrawHyperCoords(), DrawStatusMessage()
+#include "../settings.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "../state.h"
+#include "../uqmdebug.h"
+#include "options.h"
+#include "libs/inplib.h"
+#include "libs/strlib.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/mathlib.h"
+#include "libs/memlib.h"
+
+#include <stdlib.h>
+
+
+static POINT cursorLoc;
+static POINT mapOrigin;
+static int zoomLevel;
+static FRAME StarMapFrame;
+
+
+static inline long
+signedDivWithError (long val, long divisor)
+{
+ int invert = 0;
+ if (val < 0)
+ {
+ invert = 1;
+ val = -val;
+ }
+ val = (val + ROUNDING_ERROR (divisor)) / divisor;
+ return invert ? -val : val;
+}
+
+#define MAP_FIT_X ((MAX_X_UNIVERSE + 1) / SIS_SCREEN_WIDTH + 1)
+
+static inline COORD
+universeToDispx (long ux)
+{
+ return signedDivWithError (((ux - mapOrigin.x) << zoomLevel)
+ * SIS_SCREEN_WIDTH, MAX_X_UNIVERSE + MAP_FIT_X)
+ + ((SIS_SCREEN_WIDTH - 1) >> 1);
+}
+#define UNIVERSE_TO_DISPX(ux) universeToDispx(ux)
+
+static inline COORD
+universeToDispy (long uy)
+{
+ return signedDivWithError (((mapOrigin.y - uy) << zoomLevel)
+ * SIS_SCREEN_HEIGHT, MAX_Y_UNIVERSE + 2)
+ + ((SIS_SCREEN_HEIGHT - 1) >> 1);
+}
+#define UNIVERSE_TO_DISPY(uy) universeToDispy(uy)
+
+static inline COORD
+dispxToUniverse (COORD dx)
+{
+ return (((long)(dx - ((SIS_SCREEN_WIDTH - 1) >> 1))
+ * (MAX_X_UNIVERSE + MAP_FIT_X)) >> zoomLevel)
+ / SIS_SCREEN_WIDTH + mapOrigin.x;
+}
+#define DISP_TO_UNIVERSEX(dx) dispxToUniverse(dx)
+
+static inline COORD
+dispyToUniverse (COORD dy)
+{
+ return (((long)(((SIS_SCREEN_HEIGHT - 1) >> 1) - dy)
+ * (MAX_Y_UNIVERSE + 2)) >> zoomLevel)
+ / SIS_SCREEN_HEIGHT + mapOrigin.y;
+}
+#define DISP_TO_UNIVERSEY(dy) dispyToUniverse(dy)
+
+static BOOLEAN transition_pending;
+
+static void
+flashCurrentLocation (POINT *where)
+{
+ static BYTE c = 0;
+ static int val = -2;
+ static POINT universe;
+ static TimeCount NextTime = 0;
+
+ if (where)
+ universe = *where;
+
+ if (GetTimeCounter () >= NextTime)
+ {
+ Color OldColor;
+ CONTEXT OldContext;
+ STAMP s;
+
+ NextTime = GetTimeCounter () + (ONE_SECOND / 16);
+
+ OldContext = SetContext (SpaceContext);
+
+ if (c == 0x00 || c == 0x1A)
+ val = -val;
+ c += val;
+ OldColor = SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (c, c, c), c));
+ s.origin.x = UNIVERSE_TO_DISPX (universe.x);
+ s.origin.y = UNIVERSE_TO_DISPY (universe.y);
+ s.frame = IncFrameIndex (StarMapFrame);
+ DrawFilledStamp (&s);
+ SetContextForeGroundColor (OldColor);
+
+ SetContext (OldContext);
+ }
+}
+
+static void
+DrawCursor (COORD curs_x, COORD curs_y)
+{
+ STAMP s;
+
+ s.origin.x = curs_x;
+ s.origin.y = curs_y;
+ s.frame = StarMapFrame;
+
+ DrawStamp (&s);
+}
+
+static void
+DrawAutoPilot (POINT *pDstPt)
+{
+ SIZE dx, dy,
+ xincr, yincr,
+ xerror, yerror,
+ cycle, delta;
+ POINT pt;
+
+ if (!inHQSpace ())
+ pt = CurStarDescPtr->star_pt;
+ else
+ {
+ pt.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ pt.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+ }
+ pt.x = UNIVERSE_TO_DISPX (pt.x);
+ pt.y = UNIVERSE_TO_DISPY (pt.y);
+
+ dx = UNIVERSE_TO_DISPX (pDstPt->x) - pt.x;
+ if (dx >= 0)
+ xincr = 1;
+ else
+ {
+ xincr = -1;
+ dx = -dx;
+ }
+ dx <<= 1;
+
+ dy = UNIVERSE_TO_DISPY (pDstPt->y) - pt.y;
+ if (dy >= 0)
+ yincr = 1;
+ else
+ {
+ yincr = -1;
+ dy = -dy;
+ }
+ dy <<= 1;
+
+ if (dx >= dy)
+ cycle = dx;
+ else
+ cycle = dy;
+ delta = xerror = yerror = cycle >> 1;
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x04, 0x04, 0x1F), 0x01));
+
+ delta &= ~1;
+ while (delta--)
+ {
+ if (!(delta & 1))
+ DrawPoint (&pt);
+
+ if ((xerror -= dx) <= 0)
+ {
+ pt.x += xincr;
+ xerror += cycle;
+ }
+ if ((yerror -= dy) <= 0)
+ {
+ pt.y += yincr;
+ yerror += cycle;
+ }
+ }
+}
+
+static void
+GetSphereRect (FLEET_INFO *FleetPtr, RECT *pRect, RECT *pRepairRect)
+{
+ long diameter;
+
+ diameter = (long)(FleetPtr->known_strength * SPHERE_RADIUS_INCREMENT);
+ pRect->extent.width = UNIVERSE_TO_DISPX (diameter)
+ - UNIVERSE_TO_DISPX (0);
+ if (pRect->extent.width < 0)
+ pRect->extent.width = -pRect->extent.width;
+ else if (pRect->extent.width == 0)
+ pRect->extent.width = 1;
+ pRect->extent.height = UNIVERSE_TO_DISPY (diameter)
+ - UNIVERSE_TO_DISPY (0);
+ if (pRect->extent.height < 0)
+ pRect->extent.height = -pRect->extent.height;
+ else if (pRect->extent.height == 0)
+ pRect->extent.height = 1;
+
+ pRect->corner.x = UNIVERSE_TO_DISPX (FleetPtr->known_loc.x);
+ pRect->corner.y = UNIVERSE_TO_DISPY (FleetPtr->known_loc.y);
+ pRect->corner.x -= pRect->extent.width >> 1;
+ pRect->corner.y -= pRect->extent.height >> 1;
+
+ {
+ TEXT t;
+ STRING locString;
+
+ SetContextFont (TinyFont);
+
+ t.baseline.x = pRect->corner.x + (pRect->extent.width >> 1);
+ t.baseline.y = pRect->corner.y + (pRect->extent.height >> 1) - 1;
+ t.align = ALIGN_CENTER;
+ locString = SetAbsStringTableIndex (FleetPtr->race_strings, 1);
+ t.CharCount = GetStringLength (locString);
+ t.pStr = (UNICODE *)GetStringAddress (locString);
+ TextRect (&t, pRepairRect, NULL);
+
+ if (pRepairRect->corner.x <= 0)
+ pRepairRect->corner.x = 1;
+ else if (pRepairRect->corner.x + pRepairRect->extent.width >=
+ SIS_SCREEN_WIDTH)
+ pRepairRect->corner.x =
+ SIS_SCREEN_WIDTH - pRepairRect->extent.width - 1;
+ if (pRepairRect->corner.y <= 0)
+ pRepairRect->corner.y = 1;
+ else if (pRepairRect->corner.y + pRepairRect->extent.height >=
+ SIS_SCREEN_HEIGHT)
+ pRepairRect->corner.y =
+ SIS_SCREEN_HEIGHT - pRepairRect->extent.height - 1;
+
+ BoxUnion (pRepairRect, pRect, pRepairRect);
+ pRepairRect->extent.width++;
+ pRepairRect->extent.height++;
+ }
+}
+
+static void
+DrawStarMap (COUNT race_update, RECT *pClipRect)
+{
+#define GRID_DELTA 500
+ SIZE i;
+ COUNT which_space;
+ long diameter;
+ RECT r, old_r;
+ POINT oldOrigin = {0, 0};
+ STAMP s;
+ FRAME star_frame;
+ STAR_DESC *SDPtr;
+ BOOLEAN draw_cursor;
+
+ if (pClipRect == (RECT*)-1)
+ {
+ pClipRect = 0;
+ draw_cursor = FALSE;
+ }
+ else
+ {
+ draw_cursor = TRUE;
+ }
+
+ SetContext (SpaceContext);
+ if (pClipRect)
+ {
+ GetContextClipRect (&old_r);
+ pClipRect->corner.x += old_r.corner.x;
+ pClipRect->corner.y += old_r.corner.y;
+ SetContextClipRect (pClipRect);
+ pClipRect->corner.x -= old_r.corner.x;
+ pClipRect->corner.y -= old_r.corner.y;
+ // Offset the origin so that we draw the correct gfx in the cliprect
+ oldOrigin = SetContextOrigin (MAKE_POINT (-pClipRect->corner.x,
+ -pClipRect->corner.y));
+ }
+
+ if (transition_pending)
+ {
+ SetTransitionSource (NULL);
+ }
+ BatchGraphics ();
+
+ which_space = GET_GAME_STATE (ARILOU_SPACE_SIDE);
+
+ if (which_space <= 1)
+ {
+ SDPtr = &star_array[0];
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x07), 0x57));
+ SetContextBackGroundColor (BLACK_COLOR);
+ }
+ else
+ {
+ SDPtr = &star_array[NUM_SOLAR_SYSTEMS + 1];
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x0B, 0x00), 0x6D));
+ SetContextBackGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x08, 0x00), 0x6E));
+ }
+ ClearDrawable ();
+
+ // Draw the fuel range circle
+ if (race_update == 0
+ && which_space < 2
+ && (diameter = (long)GLOBAL_SIS (FuelOnBoard) << 1))
+ {
+ Color OldColor;
+
+ if (!inHQSpace ())
+ r.corner = CurStarDescPtr->star_pt;
+ else
+ {
+ r.corner.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ r.corner.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+ }
+
+ // Cap the diameter to a sane range
+ if (diameter > MAX_X_UNIVERSE * 4)
+ diameter = MAX_X_UNIVERSE * 4;
+
+ r.extent.width = UNIVERSE_TO_DISPX (diameter)
+ - UNIVERSE_TO_DISPX (0);
+ if (r.extent.width < 0)
+ r.extent.width = -r.extent.width;
+ r.extent.height = UNIVERSE_TO_DISPY (diameter)
+ - UNIVERSE_TO_DISPY (0);
+ if (r.extent.height < 0)
+ r.extent.height = -r.extent.height;
+
+ r.corner.x = UNIVERSE_TO_DISPX (r.corner.x)
+ - (r.extent.width >> 1);
+ r.corner.y = UNIVERSE_TO_DISPY (r.corner.y)
+ - (r.extent.height >> 1);
+
+ OldColor = SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x03, 0x03, 0x03), 0x22));
+ DrawFilledOval (&r);
+ SetContextForeGroundColor (OldColor);
+ }
+
+ for (i = MAX_Y_UNIVERSE + 1; i >= 0; i -= GRID_DELTA)
+ {
+ SIZE j;
+
+ r.corner.x = UNIVERSE_TO_DISPX (0);
+ r.corner.y = UNIVERSE_TO_DISPY (i);
+ r.extent.width = SIS_SCREEN_WIDTH << zoomLevel;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+
+ r.corner.y = UNIVERSE_TO_DISPY (MAX_Y_UNIVERSE + 1);
+ r.extent.width = 1;
+ r.extent.height = SIS_SCREEN_HEIGHT << zoomLevel;
+ for (j = MAX_X_UNIVERSE + 1; j >= 0; j -= GRID_DELTA)
+ {
+ r.corner.x = UNIVERSE_TO_DISPX (j);
+ DrawFilledRectangle (&r);
+ }
+ }
+
+ star_frame = SetRelFrameIndex (StarMapFrame, 2);
+ if (which_space <= 1)
+ {
+ COUNT index;
+ HFLEETINFO hStarShip, hNextShip;
+ static const Color race_colors[] =
+ {
+ RACE_COLORS
+ };
+
+ for (index = 0,
+ hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip != 0; ++index, hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ if (FleetPtr->known_strength)
+ {
+ RECT repair_r;
+
+ GetSphereRect (FleetPtr, &r, &repair_r);
+ if (r.corner.x < SIS_SCREEN_WIDTH
+ && r.corner.y < SIS_SCREEN_HEIGHT
+ && r.corner.x + r.extent.width > 0
+ && r.corner.y + r.extent.height > 0
+ && (pClipRect == 0
+ || (repair_r.corner.x < pClipRect->corner.x + pClipRect->extent.width
+ && repair_r.corner.y < pClipRect->corner.y + pClipRect->extent.height
+ && repair_r.corner.x + repair_r.extent.width > pClipRect->corner.x
+ && repair_r.corner.y + repair_r.extent.height > pClipRect->corner.y)))
+ {
+ Color c;
+ TEXT t;
+ STRING locString;
+
+ c = race_colors[index];
+ if (index + 1 == race_update)
+ SetContextForeGroundColor (WHITE_COLOR);
+ else
+ SetContextForeGroundColor (c);
+ DrawOval (&r, 0);
+
+ SetContextFont (TinyFont);
+
+ t.baseline.x = r.corner.x + (r.extent.width >> 1);
+ t.baseline.y = r.corner.y + (r.extent.height >> 1) - 1;
+ t.align = ALIGN_CENTER;
+ locString = SetAbsStringTableIndex (
+ FleetPtr->race_strings, 1);
+ t.CharCount = GetStringLength (locString);
+ t.pStr = (UNICODE *)GetStringAddress (locString);
+ TextRect (&t, &r, NULL);
+
+ if (r.corner.x <= 0)
+ t.baseline.x -= r.corner.x - 1;
+ else if (r.corner.x + r.extent.width >= SIS_SCREEN_WIDTH)
+ t.baseline.x -= (r.corner.x + r.extent.width)
+ - SIS_SCREEN_WIDTH + 1;
+ if (r.corner.y <= 0)
+ t.baseline.y -= r.corner.y - 1;
+ else if (r.corner.y + r.extent.height >= SIS_SCREEN_HEIGHT)
+ t.baseline.y -= (r.corner.y + r.extent.height)
+ - SIS_SCREEN_HEIGHT + 1;
+
+ // The text color is slightly lighter than the color of
+ // the SoI.
+ c.r = (c.r >= 0xff - CC5TO8 (0x03)) ?
+ 0xff : c.r + CC5TO8 (0x03);
+ c.g = (c.g >= 0xff - CC5TO8 (0x03)) ?
+ 0xff : c.g + CC5TO8 (0x03);
+ c.b = (c.b >= 0xff - CC5TO8 (0x03)) ?
+ 0xff : c.b + CC5TO8 (0x03);
+
+ SetContextForeGroundColor (c);
+ font_DrawText (&t);
+ }
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+ }
+
+ do
+ {
+ BYTE star_type;
+
+ star_type = SDPtr->Type;
+
+ s.origin.x = UNIVERSE_TO_DISPX (SDPtr->star_pt.x);
+ s.origin.y = UNIVERSE_TO_DISPY (SDPtr->star_pt.y);
+ if (which_space <= 1)
+ s.frame = SetRelFrameIndex (star_frame,
+ STAR_TYPE (star_type)
+ * NUM_STAR_COLORS
+ + STAR_COLOR (star_type));
+ else if (SDPtr->star_pt.x == ARILOU_HOME_X
+ && SDPtr->star_pt.y == ARILOU_HOME_Y)
+ s.frame = SetRelFrameIndex (star_frame,
+ SUPER_GIANT_STAR * NUM_STAR_COLORS + GREEN_BODY);
+ else
+ s.frame = SetRelFrameIndex (star_frame,
+ GIANT_STAR * NUM_STAR_COLORS + GREEN_BODY);
+ DrawStamp (&s);
+
+ ++SDPtr;
+ } while (SDPtr->star_pt.x <= MAX_X_UNIVERSE
+ && SDPtr->star_pt.y <= MAX_Y_UNIVERSE);
+
+ if (GET_GAME_STATE (ARILOU_SPACE))
+ {
+ if (which_space <= 1)
+ {
+ s.origin.x = UNIVERSE_TO_DISPX (ARILOU_SPACE_X);
+ s.origin.y = UNIVERSE_TO_DISPY (ARILOU_SPACE_Y);
+ }
+ else
+ {
+ s.origin.x = UNIVERSE_TO_DISPX (QUASI_SPACE_X);
+ s.origin.y = UNIVERSE_TO_DISPY (QUASI_SPACE_Y);
+ }
+ s.frame = SetRelFrameIndex (star_frame,
+ GIANT_STAR * NUM_STAR_COLORS + GREEN_BODY);
+ DrawStamp (&s);
+ }
+
+ if (race_update == 0
+ && GLOBAL (autopilot.x) != ~0
+ && GLOBAL (autopilot.y) != ~0)
+ DrawAutoPilot (&GLOBAL (autopilot));
+
+ if (transition_pending)
+ {
+ GetContextClipRect (&r);
+ ScreenTransition (3, &r);
+ transition_pending = FALSE;
+ }
+
+ UnbatchGraphics ();
+
+ if (pClipRect)
+ {
+ SetContextClipRect (&old_r);
+ SetContextOrigin (oldOrigin);
+ }
+
+ if (race_update == 0)
+ {
+ if (draw_cursor)
+ {
+ GetContextClipRect (&r);
+ LoadIntoExtraScreen (&r);
+ DrawCursor (UNIVERSE_TO_DISPX (cursorLoc.x),
+ UNIVERSE_TO_DISPY (cursorLoc.y));
+ }
+ }
+}
+
+static void
+EraseCursor (COORD curs_x, COORD curs_y)
+{
+ RECT r;
+
+ GetFrameRect (StarMapFrame, &r);
+
+ if ((r.corner.x += curs_x) < 0)
+ {
+ r.extent.width += r.corner.x;
+ r.corner.x = 0;
+ }
+ else if (r.corner.x + r.extent.width >= SIS_SCREEN_WIDTH)
+ r.extent.width = SIS_SCREEN_WIDTH - r.corner.x;
+ if ((r.corner.y += curs_y) < 0)
+ {
+ r.extent.height += r.corner.y;
+ r.corner.y = 0;
+ }
+ else if (r.corner.y + r.extent.height >= SIS_SCREEN_HEIGHT)
+ r.extent.height = SIS_SCREEN_HEIGHT - r.corner.y;
+
+#ifndef OLD
+ RepairBackRect (&r);
+#else /* NEW */
+ r.extent.height += r.corner.y & 1;
+ r.corner.y &= ~1;
+ DrawStarMap (0, &r);
+#endif /* OLD */
+}
+
+static void
+ZoomStarMap (SIZE dir)
+{
+#define MAX_ZOOM_SHIFT 4
+ if (dir > 0)
+ {
+ if (zoomLevel < MAX_ZOOM_SHIFT)
+ {
+ ++zoomLevel;
+ mapOrigin = cursorLoc;
+
+ DrawStarMap (0, NULL);
+ SleepThread (ONE_SECOND / 8);
+ }
+ }
+ else if (dir < 0)
+ {
+ if (zoomLevel > 0)
+ {
+ if (zoomLevel > 1)
+ mapOrigin = cursorLoc;
+ else
+ {
+ mapOrigin.x = MAX_X_UNIVERSE >> 1;
+ mapOrigin.y = MAX_Y_UNIVERSE >> 1;
+ }
+ --zoomLevel;
+
+ DrawStarMap (0, NULL);
+ SleepThread (ONE_SECOND / 8);
+ }
+ }
+}
+
+static void
+UpdateCursorLocation (int sx, int sy, const POINT *newpt)
+{
+ STAMP s;
+ POINT pt;
+
+ pt.x = UNIVERSE_TO_DISPX (cursorLoc.x);
+ pt.y = UNIVERSE_TO_DISPY (cursorLoc.y);
+
+ if (newpt)
+ { // absolute move
+ sx = sy = 0;
+ s.origin.x = UNIVERSE_TO_DISPX (newpt->x);
+ s.origin.y = UNIVERSE_TO_DISPY (newpt->y);
+ cursorLoc = *newpt;
+ }
+ else
+ { // incremental move
+ s.origin.x = pt.x + sx;
+ s.origin.y = pt.y + sy;
+ }
+
+ if (sx)
+ {
+ cursorLoc.x = DISP_TO_UNIVERSEX (s.origin.x) - sx;
+ while (UNIVERSE_TO_DISPX (cursorLoc.x) == pt.x)
+ cursorLoc.x += sx;
+
+ if (cursorLoc.x < 0)
+ cursorLoc.x = 0;
+ else if (cursorLoc.x > MAX_X_UNIVERSE)
+ cursorLoc.x = MAX_X_UNIVERSE;
+
+ s.origin.x = UNIVERSE_TO_DISPX (cursorLoc.x);
+ }
+
+ if (sy)
+ {
+ cursorLoc.y = DISP_TO_UNIVERSEY (s.origin.y) + sy;
+ while (UNIVERSE_TO_DISPY (cursorLoc.y) == pt.y)
+ cursorLoc.y -= sy;
+
+ if (cursorLoc.y < 0)
+ cursorLoc.y = 0;
+ else if (cursorLoc.y > MAX_Y_UNIVERSE)
+ cursorLoc.y = MAX_Y_UNIVERSE;
+
+ s.origin.y = UNIVERSE_TO_DISPY (cursorLoc.y);
+ }
+
+ if (s.origin.x < 0 || s.origin.y < 0
+ || s.origin.x >= SIS_SCREEN_WIDTH
+ || s.origin.y >= SIS_SCREEN_HEIGHT)
+ {
+ mapOrigin = cursorLoc;
+ DrawStarMap (0, NULL);
+
+ s.origin.x = UNIVERSE_TO_DISPX (cursorLoc.x);
+ s.origin.y = UNIVERSE_TO_DISPY (cursorLoc.y);
+ }
+ else
+ {
+ EraseCursor (pt.x, pt.y);
+ // ClearDrawable ();
+ DrawCursor (s.origin.x, s.origin.y);
+ }
+}
+
+#define CURSOR_INFO_BUFSIZE 256
+
+static void
+UpdateCursorInfo (UNICODE *prevbuf)
+{
+ UNICODE buf[CURSOR_INFO_BUFSIZE] = "";
+ POINT pt;
+ STAR_DESC *SDPtr;
+ STAR_DESC *BestSDPtr;
+
+ pt.x = UNIVERSE_TO_DISPX (cursorLoc.x);
+ pt.y = UNIVERSE_TO_DISPY (cursorLoc.y);
+
+ SDPtr = BestSDPtr = 0;
+ while ((SDPtr = FindStar (SDPtr, &cursorLoc, 75, 75)))
+ {
+ if (UNIVERSE_TO_DISPX (SDPtr->star_pt.x) == pt.x
+ && UNIVERSE_TO_DISPY (SDPtr->star_pt.y) == pt.y
+ && (BestSDPtr == 0
+ || STAR_TYPE (SDPtr->Type) >= STAR_TYPE (BestSDPtr->Type)))
+ BestSDPtr = SDPtr;
+ }
+
+ if (BestSDPtr)
+ {
+ cursorLoc = BestSDPtr->star_pt;
+ GetClusterName (BestSDPtr, buf);
+ }
+ else
+ { // No star found. Reset the coordinates to the cursor's location
+ cursorLoc.x = DISP_TO_UNIVERSEX (pt.x);
+ if (cursorLoc.x < 0)
+ cursorLoc.x = 0;
+ else if (cursorLoc.x > MAX_X_UNIVERSE)
+ cursorLoc.x = MAX_X_UNIVERSE;
+ cursorLoc.y = DISP_TO_UNIVERSEY (pt.y);
+ if (cursorLoc.y < 0)
+ cursorLoc.y = 0;
+ else if (cursorLoc.y > MAX_Y_UNIVERSE)
+ cursorLoc.y = MAX_Y_UNIVERSE;
+ }
+
+ if (GET_GAME_STATE (ARILOU_SPACE))
+ {
+ POINT ari_pt;
+
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ ari_pt.x = ARILOU_SPACE_X;
+ ari_pt.y = ARILOU_SPACE_Y;
+ }
+ else
+ {
+ ari_pt.x = QUASI_SPACE_X;
+ ari_pt.y = QUASI_SPACE_Y;
+ }
+
+ if (UNIVERSE_TO_DISPX (ari_pt.x) == pt.x
+ && UNIVERSE_TO_DISPY (ari_pt.y) == pt.y)
+ {
+ cursorLoc = ari_pt;
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (STAR_STRING_BASE + 132));
+ }
+ }
+
+ DrawHyperCoords (cursorLoc);
+ if (strcmp (buf, prevbuf) != 0)
+ {
+ strcpy (prevbuf, buf);
+ DrawSISMessage (buf);
+ }
+}
+
+static void
+UpdateFuelRequirement (void)
+{
+ UNICODE buf[80];
+ COUNT fuel_required;
+ DWORD f;
+ POINT pt;
+
+ if (!inHQSpace ())
+ pt = CurStarDescPtr->star_pt;
+ else
+ {
+ pt.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ pt.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+ }
+ pt.x -= cursorLoc.x;
+ pt.y -= cursorLoc.y;
+
+ f = (DWORD)((long)pt.x * pt.x + (long)pt.y * pt.y);
+ if (f == 0 || GET_GAME_STATE (ARILOU_SPACE_SIDE) > 1)
+ fuel_required = 0;
+ else
+ fuel_required = square_root (f) + (FUEL_TANK_SCALE / 20);
+
+ sprintf (buf, "%s %u.%u",
+ GAME_STRING (NAVIGATION_STRING_BASE + 4),
+ fuel_required / FUEL_TANK_SCALE,
+ (fuel_required % FUEL_TANK_SCALE) / 10);
+
+ DrawStatusMessage (buf);
+}
+
+#define STAR_SEARCH_BUFSIZE 256
+
+typedef struct starsearch_state
+{
+ // TODO: pMS field is probably not needed anymore
+ MENU_STATE *pMS;
+ UNICODE Text[STAR_SEARCH_BUFSIZE];
+ UNICODE LastText[STAR_SEARCH_BUFSIZE];
+ DWORD LastChangeTime;
+ int FirstIndex;
+ int CurIndex;
+ int LastIndex;
+ BOOLEAN SingleClust;
+ BOOLEAN SingleMatch;
+ UNICODE Buffer[STAR_SEARCH_BUFSIZE];
+ const UNICODE *Prefix;
+ const UNICODE *Cluster;
+ int PrefixLen;
+ int ClusterLen;
+ int ClusterPos;
+ int SortedStars[NUM_SOLAR_SYSTEMS];
+} STAR_SEARCH_STATE;
+
+static int
+compStarName (const void *ptr1, const void *ptr2)
+{
+ int index1;
+ int index2;
+
+ index1 = *(const int *) ptr1;
+ index2 = *(const int *) ptr2;
+ if (star_array[index1].Postfix != star_array[index2].Postfix)
+ {
+ return utf8StringCompare (GAME_STRING (star_array[index1].Postfix),
+ GAME_STRING (star_array[index2].Postfix));
+ }
+
+ if (star_array[index1].Prefix < star_array[index2].Prefix)
+ return -1;
+
+ if (star_array[index1].Prefix > star_array[index2].Prefix)
+ return 1;
+
+ return 0;
+}
+
+static void
+SortStarsOnName (STAR_SEARCH_STATE *pSS)
+{
+ int i;
+ int *sorted = pSS->SortedStars;
+
+ for (i = 0; i < NUM_SOLAR_SYSTEMS; i++)
+ sorted[i] = i;
+
+ qsort (sorted, NUM_SOLAR_SYSTEMS, sizeof (int), compStarName);
+}
+
+static void
+SplitStarName (STAR_SEARCH_STATE *pSS)
+{
+ UNICODE *buf = pSS->Buffer;
+ UNICODE *next;
+ UNICODE *sep = NULL;
+
+ pSS->Prefix = 0;
+ pSS->PrefixLen = 0;
+ pSS->Cluster = 0;
+ pSS->ClusterLen = 0;
+ pSS->ClusterPos = 0;
+
+ // skip leading space
+ for (next = buf; *next != '\0' &&
+ getCharFromString ((const UNICODE **)&next) == ' ';
+ buf = next)
+ ;
+ if (*buf == '\0')
+ { // no text
+ return;
+ }
+
+ pSS->Prefix = buf;
+
+ // See if player gave a prefix
+ for (buf = next; *next != '\0' &&
+ getCharFromString ((const UNICODE **)&next) != ' ';
+ buf = next)
+ ;
+ if (*buf != '\0')
+ { // found possibly separating ' '
+ sep = buf;
+ // skip separating space
+ for (buf = next; *next != '\0' &&
+ getCharFromString ((const UNICODE **)&next) == ' ';
+ buf = next)
+ ;
+ }
+
+ if (*buf == '\0')
+ { // reached the end -- cluster only
+ pSS->Cluster = pSS->Prefix;
+ pSS->ClusterLen = utf8StringCount (pSS->Cluster);
+ pSS->ClusterPos = utf8StringCountN (pSS->Buffer, pSS->Cluster);
+ pSS->Prefix = 0;
+ return;
+ }
+
+ // consider the rest cluster name (whatever there is)
+ pSS->Cluster = buf;
+ pSS->ClusterLen = utf8StringCount (pSS->Cluster);
+ pSS->ClusterPos = utf8StringCountN (pSS->Buffer, pSS->Cluster);
+ *sep = '\0'; // split
+ pSS->PrefixLen = utf8StringCount (pSS->Prefix);
+}
+
+static inline int
+SkipStarCluster (int *sortedStars, int istar)
+{
+ int Postfix = star_array[sortedStars[istar]].Postfix;
+
+ for (++istar; istar < NUM_SOLAR_SYSTEMS &&
+ star_array[sortedStars[istar]].Postfix == Postfix;
+ ++istar)
+ ;
+ return istar;
+}
+
+static int
+FindNextStarIndex (STAR_SEARCH_STATE *pSS, int from, BOOLEAN WithinClust)
+{
+ int i;
+
+ if (!pSS->Cluster)
+ return -1; // nothing to search for
+
+ for (i = from; i < NUM_SOLAR_SYSTEMS; ++i)
+ {
+ STAR_DESC *SDPtr = &star_array[pSS->SortedStars[i]];
+ UNICODE FullName[STAR_SEARCH_BUFSIZE];
+ UNICODE *ClusterName = GAME_STRING (SDPtr->Postfix);
+ const UNICODE *sptr;
+ const UNICODE *dptr;
+ int dlen;
+ int c;
+
+ dlen = utf8StringCount (ClusterName);
+ if (pSS->ClusterLen > dlen)
+ { // no match, skip the rest of cluster
+ i = SkipStarCluster (pSS->SortedStars, i) - 1;
+ continue;
+ }
+
+ for (c = 0, sptr = pSS->Cluster, dptr = ClusterName;
+ c < pSS->ClusterLen; ++c)
+ {
+ UniChar sc = getCharFromString (&sptr);
+ UniChar dc = getCharFromString (&dptr);
+
+ if (UniChar_toUpper (sc) != UniChar_toUpper (dc))
+ break;
+ }
+
+ if (c < pSS->ClusterLen)
+ { // no match here, skip the rest of cluster
+ i = SkipStarCluster (pSS->SortedStars, i) - 1;
+ continue;
+ }
+
+ if (pSS->Prefix && !SDPtr->Prefix)
+ // we were given a prefix but found a singular star;
+ // that is a no match
+ continue;
+
+ if (WithinClust)
+ // searching within clusters; any prefix is a match
+ break;
+
+ if (!pSS->Prefix)
+ { // searching for cluster name only
+ // return only the first stars in a cluster
+ if (i == 0 || SDPtr->Postfix !=
+ star_array[pSS->SortedStars[i - 1]].Postfix)
+ { // found one
+ break;
+ }
+ else
+ { // another star in the same cluster, skip cluster
+ i = SkipStarCluster (pSS->SortedStars, i) - 1;
+ continue;
+ }
+ }
+
+ // check prefix
+ GetClusterName (SDPtr, FullName);
+ dlen = utf8StringCount (FullName);
+ if (pSS->PrefixLen > dlen)
+ continue;
+
+ for (c = 0, sptr = pSS->Prefix, dptr = FullName;
+ c < pSS->PrefixLen; ++c)
+ {
+ UniChar sc = getCharFromString (&sptr);
+ UniChar dc = getCharFromString (&dptr);
+
+ if (UniChar_toUpper (sc) != UniChar_toUpper (dc))
+ break;
+ }
+
+ if (c >= pSS->PrefixLen)
+ break; // found one
+ }
+
+ return (i < NUM_SOLAR_SYSTEMS) ? i : -1;
+}
+
+static void
+DrawMatchedStarName (TEXTENTRY_STATE *pTES)
+{
+ STAR_SEARCH_STATE *pSS = (STAR_SEARCH_STATE *) pTES->CbParam;
+ UNICODE buf[STAR_SEARCH_BUFSIZE] = "";
+ SIZE ExPos = 0;
+ SIZE CurPos = -1;
+ STAR_DESC *SDPtr = &star_array[pSS->SortedStars[pSS->CurIndex]];
+ COUNT flags;
+
+ if (pSS->SingleClust || pSS->SingleMatch)
+ { // draw full star name
+ GetClusterName (SDPtr, buf);
+ ExPos = -1;
+ flags = DSME_SETFR;
+ }
+ else
+ { // draw substring match
+ UNICODE *pstr = buf;
+
+ strcpy (pstr, pSS->Text);
+ ExPos = pSS->ClusterPos;
+ pstr = skipUTF8Chars (pstr, pSS->ClusterPos);
+
+ strcpy (pstr, GAME_STRING (SDPtr->Postfix));
+ ExPos += pSS->ClusterLen;
+ CurPos = pTES->CursorPos;
+
+ flags = DSME_CLEARFR;
+ if (pTES->JoystickMode)
+ flags |= DSME_BLOCKCUR;
+ }
+
+ DrawSISMessageEx (buf, CurPos, ExPos, flags);
+ DrawHyperCoords (cursorLoc);
+}
+
+static void
+MatchNextStar (STAR_SEARCH_STATE *pSS, BOOLEAN Reset)
+{
+ if (Reset)
+ pSS->FirstIndex = -1; // reset cache
+
+ if (pSS->FirstIndex < 0)
+ { // first time after changes
+ pSS->CurIndex = -1;
+ pSS->LastIndex = -1;
+ pSS->SingleClust = FALSE;
+ pSS->SingleMatch = FALSE;
+ strcpy (pSS->Buffer, pSS->Text);
+ SplitStarName (pSS);
+ }
+
+ pSS->CurIndex = FindNextStarIndex (pSS, pSS->CurIndex + 1,
+ pSS->SingleClust);
+ if (pSS->FirstIndex < 0) // first search
+ pSS->FirstIndex = pSS->CurIndex;
+
+ if (pSS->CurIndex >= 0)
+ { // remember as last (searching forward-only)
+ pSS->LastIndex = pSS->CurIndex;
+ }
+ else
+ { // wrap around
+ pSS->CurIndex = pSS->FirstIndex;
+
+ if (pSS->FirstIndex == pSS->LastIndex && pSS->FirstIndex != -1)
+ {
+ if (!pSS->Prefix)
+ { // only one cluster matching
+ pSS->SingleClust = TRUE;
+ }
+ else
+ { // exact match
+ pSS->SingleMatch = TRUE;
+ }
+ }
+ }
+}
+
+static BOOLEAN
+OnStarNameChange (TEXTENTRY_STATE *pTES)
+{
+ STAR_SEARCH_STATE *pSS = (STAR_SEARCH_STATE *) pTES->CbParam;
+ COUNT flags;
+ BOOLEAN ret = TRUE;
+
+ if (strcmp (pSS->Text, pSS->LastText) != 0)
+ { // string changed
+ pSS->LastChangeTime = GetTimeCounter ();
+ strcpy (pSS->LastText, pSS->Text);
+
+ // reset the search
+ MatchNextStar (pSS, TRUE);
+ }
+
+ if (pSS->CurIndex < 0)
+ { // nothing found
+ if (pSS->Text[0] == '\0')
+ flags = DSME_SETFR;
+ else
+ flags = DSME_CLEARFR;
+ if (pTES->JoystickMode)
+ flags |= DSME_BLOCKCUR;
+
+ ret = DrawSISMessageEx (pSS->Text, pTES->CursorPos, -1, flags);
+ }
+ else
+ {
+ STAR_DESC *SDPtr;
+
+ // move the cursor to the found star
+ SDPtr = &star_array[pSS->SortedStars[pSS->CurIndex]];
+ UpdateCursorLocation (0, 0, &SDPtr->star_pt);
+
+ DrawMatchedStarName (pTES);
+ UpdateFuelRequirement ();
+ }
+
+ return ret;
+}
+
+static BOOLEAN
+OnStarNameFrame (TEXTENTRY_STATE *pTES)
+{
+ STAR_SEARCH_STATE *pSS = (STAR_SEARCH_STATE *) pTES->CbParam;
+
+ if (PulsedInputState.menu[KEY_MENU_NEXT])
+ { // search for next match
+ STAR_DESC *SDPtr;
+
+ MatchNextStar (pSS, FALSE);
+
+ if (pSS->CurIndex < 0)
+ { // nothing found
+ if (PulsedInputState.menu[KEY_MENU_NEXT])
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return TRUE;
+ }
+
+ // move the cursor to the found star
+ SDPtr = &star_array[pSS->SortedStars[pSS->CurIndex]];
+ UpdateCursorLocation (0, 0, &SDPtr->star_pt);
+
+ DrawMatchedStarName (pTES);
+ UpdateFuelRequirement ();
+ }
+
+ flashCurrentLocation (NULL);
+
+ SleepThread (ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+static BOOLEAN
+DoStarSearch (MENU_STATE *pMS)
+{
+ TEXTENTRY_STATE tes;
+ STAR_SEARCH_STATE *pss;
+ BOOLEAN success;
+
+ pss = HMalloc (sizeof (*pss));
+ if (!pss)
+ return FALSE;
+
+ DrawSISMessageEx ("", 0, 0, DSME_SETFR);
+
+ pss->pMS = pMS;
+ pss->LastChangeTime = 0;
+ pss->Text[0] = '\0';
+ pss->LastText[0] = '\0';
+ pss->FirstIndex = -1;
+ SortStarsOnName (pss);
+
+ // text entry setup
+ tes.Initialized = FALSE;
+ tes.BaseStr = pss->Text;
+ tes.MaxSize = sizeof (pss->Text);
+ tes.CursorPos = 0;
+ tes.CbParam = pss;
+ tes.ChangeCallback = OnStarNameChange;
+ tes.FrameCallback = OnStarNameFrame;
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ SetDefaultMenuRepeatDelay ();
+ success = DoTextEntry (&tes);
+
+ DrawSISMessageEx (pss->Text, -1, -1, DSME_CLEARFR);
+
+ HFree (pss);
+
+ return success;
+}
+
+static BOOLEAN
+DoMoveCursor (MENU_STATE *pMS)
+{
+#define MIN_ACCEL_DELAY (ONE_SECOND / 60)
+#define MAX_ACCEL_DELAY (ONE_SECOND / 8)
+#define STEP_ACCEL_DELAY (ONE_SECOND / 120)
+ static UNICODE last_buf[CURSOR_INFO_BUFSIZE];
+ DWORD TimeIn = GetTimeCounter ();
+
+ if (!pMS->Initialized)
+ {
+ POINT universe;
+
+ pMS->Initialized = TRUE;
+ pMS->InputFunc = DoMoveCursor;
+
+ if (!inHQSpace ())
+ universe = CurStarDescPtr->star_pt;
+ else
+ {
+ universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+ }
+ flashCurrentLocation (&universe);
+
+ last_buf[0] = '\0';
+ UpdateCursorInfo (last_buf);
+ UpdateFuelRequirement ();
+
+ return TRUE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ GLOBAL (autopilot) = cursorLoc;
+#ifdef DEBUG
+ if (instantMove)
+ {
+ PlayMenuSound (MENU_SOUND_INVOKED);
+
+ if (inHQSpace ())
+ {
+ // Move to the new location immediately.
+ doInstantMove ();
+ }
+ else if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY)
+ {
+ // We're in a solar system; exit it.
+ GLOBAL (CurrentActivity) |= END_INTERPLANETARY;
+
+ // Set a hook to move to the new location:
+ debugHook = doInstantMove;
+ }
+
+ return FALSE;
+ }
+#endif
+ DrawStarMap (0, NULL);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SEARCH])
+ {
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ { // HyperSpace search
+ POINT oldpt = cursorLoc;
+
+ if (!DoStarSearch (pMS))
+ { // search failed or canceled - return cursor
+ UpdateCursorLocation (0, 0, &oldpt);
+ }
+ // make sure cmp fails
+ strcpy (last_buf, " <random garbage> ");
+ UpdateCursorInfo (last_buf);
+ UpdateFuelRequirement ();
+
+ SetMenuRepeatDelay (MIN_ACCEL_DELAY, MAX_ACCEL_DELAY,
+ STEP_ACCEL_DELAY, TRUE);
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ }
+ else
+ { // no search in QuasiSpace
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ }
+ else
+ {
+ SBYTE sx, sy;
+ SIZE ZoomIn, ZoomOut;
+
+ ZoomIn = ZoomOut = 0;
+ if (PulsedInputState.menu[KEY_MENU_ZOOM_IN])
+ ZoomIn = 1;
+ else if (PulsedInputState.menu[KEY_MENU_ZOOM_OUT])
+ ZoomOut = 1;
+
+ ZoomStarMap (ZoomIn - ZoomOut);
+
+ sx = sy = 0;
+ if (PulsedInputState.menu[KEY_MENU_LEFT]) sx = -1;
+ if (PulsedInputState.menu[KEY_MENU_RIGHT]) sx = 1;
+ if (PulsedInputState.menu[KEY_MENU_UP]) sy = -1;
+ if (PulsedInputState.menu[KEY_MENU_DOWN]) sy = 1;
+
+ if (sx != 0 || sy != 0)
+ {
+ UpdateCursorLocation (sx, sy, NULL);
+ UpdateCursorInfo (last_buf);
+ UpdateFuelRequirement ();
+ }
+
+ SleepThreadUntil (TimeIn + MIN_ACCEL_DELAY);
+ }
+
+ flashCurrentLocation (NULL);
+
+ return !(GLOBAL (CurrentActivity) & CHECK_ABORT);
+}
+
+static void
+RepairMap (COUNT update_race, RECT *pLastRect, RECT *pNextRect)
+{
+ RECT r;
+
+ /* make a rect big enough for text */
+ r.extent.width = 50;
+ r.corner.x = (pNextRect->corner.x + (pNextRect->extent.width >> 1))
+ - (r.extent.width >> 1);
+ if (r.corner.x < 0)
+ r.corner.x = 0;
+ else if (r.corner.x + r.extent.width >= SIS_SCREEN_WIDTH)
+ r.corner.x = SIS_SCREEN_WIDTH - r.extent.width;
+ r.extent.height = 9;
+ r.corner.y = (pNextRect->corner.y + (pNextRect->extent.height >> 1))
+ - r.extent.height;
+ if (r.corner.y < 0)
+ r.corner.y = 0;
+ else if (r.corner.y + r.extent.height >= SIS_SCREEN_HEIGHT)
+ r.corner.y = SIS_SCREEN_HEIGHT - r.extent.height;
+ BoxUnion (pLastRect, &r, &r);
+ BoxUnion (pNextRect, &r, &r);
+ *pLastRect = *pNextRect;
+
+ if (r.corner.x < 0)
+ {
+ r.extent.width += r.corner.x;
+ r.corner.x = 0;
+ }
+ if (r.corner.x + r.extent.width > SIS_SCREEN_WIDTH)
+ r.extent.width = SIS_SCREEN_WIDTH - r.corner.x;
+ if (r.corner.y < 0)
+ {
+ r.extent.height += r.corner.y;
+ r.corner.y = 0;
+ }
+ if (r.corner.y + r.extent.height > SIS_SCREEN_HEIGHT)
+ r.extent.height = SIS_SCREEN_HEIGHT - r.corner.y;
+
+ r.extent.height += r.corner.y & 1;
+ r.corner.y &= ~1;
+
+ DrawStarMap (update_race, &r);
+}
+
+static void
+UpdateMap (void)
+{
+ BYTE ButtonState, VisibleChange;
+ BOOLEAN MapDrawn, Interrupted;
+ COUNT index;
+ HFLEETINFO hStarShip, hNextShip;
+
+ FlushInput ();
+ ButtonState = 1; /* assume a button down */
+
+ MapDrawn = Interrupted = FALSE;
+ for (index = 1,
+ hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip; ++index, hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ if (ButtonState)
+ {
+ if (!AnyButtonPress (TRUE))
+ ButtonState = 0;
+ }
+ else if ((Interrupted = (BOOLEAN)(
+ Interrupted || AnyButtonPress (TRUE)
+ )))
+ MapDrawn = TRUE;
+
+ if (FleetPtr->known_strength)
+ {
+ SIZE dx, dy, delta;
+ RECT r, last_r, temp_r0, temp_r1;
+
+ dx = FleetPtr->loc.x - FleetPtr->known_loc.x;
+ dy = FleetPtr->loc.y - FleetPtr->known_loc.y;
+ if (dx || dy)
+ {
+ SIZE xincr, yincr,
+ xerror, yerror,
+ cycle;
+
+ if (dx >= 0)
+ xincr = 1;
+ else
+ {
+ xincr = -1;
+ dx = -dx;
+ }
+ dx <<= 1;
+
+ if (dy >= 0)
+ yincr = 1;
+ else
+ {
+ yincr = -1;
+ dy = -dy;
+ }
+ dy <<= 1;
+
+ if (dx >= dy)
+ cycle = dx;
+ else
+ cycle = dy;
+ delta = xerror = yerror = cycle >> 1;
+
+ if (!MapDrawn)
+ {
+ DrawStarMap ((COUNT)~0, NULL);
+ MapDrawn = TRUE;
+ }
+
+ GetSphereRect (FleetPtr, &temp_r0, &last_r);
+ ++last_r.extent.width;
+ ++last_r.extent.height;
+ VisibleChange = FALSE;
+ do
+ {
+ do
+ {
+ if ((xerror -= dx) <= 0)
+ {
+ FleetPtr->known_loc.x += xincr;
+ xerror += cycle;
+ }
+ if ((yerror -= dy) <= 0)
+ {
+ FleetPtr->known_loc.y += yincr;
+ yerror += cycle;
+ }
+ GetSphereRect (FleetPtr, &temp_r1, &r);
+ } while (delta--
+ && ((delta & 0x1F)
+ || (temp_r0.corner.x == temp_r1.corner.x
+ && temp_r0.corner.y == temp_r1.corner.y)));
+
+ if (ButtonState)
+ {
+ if (!AnyButtonPress (TRUE))
+ ButtonState = 0;
+ }
+ else if ((Interrupted = (BOOLEAN)(
+ Interrupted || AnyButtonPress (TRUE)
+ )))
+ {
+ MapDrawn = TRUE;
+ goto DoneSphereMove;
+ }
+
+ ++r.extent.width;
+ ++r.extent.height;
+ if (temp_r0.corner.x != temp_r1.corner.x
+ || temp_r0.corner.y != temp_r1.corner.y)
+ {
+ VisibleChange = TRUE;
+ RepairMap (index, &last_r, &r);
+ }
+ } while (delta >= 0);
+ if (VisibleChange)
+ RepairMap ((COUNT)~0, &last_r, &r);
+
+DoneSphereMove:
+ FleetPtr->known_loc = FleetPtr->loc;
+ }
+
+ delta = FleetPtr->actual_strength - FleetPtr->known_strength;
+ if (delta)
+ {
+ if (!MapDrawn)
+ {
+ DrawStarMap ((COUNT)~0, NULL);
+ MapDrawn = TRUE;
+ }
+
+ if (delta > 0)
+ dx = 1;
+ else
+ {
+ delta = -delta;
+ dx = -1;
+ }
+ --delta;
+
+ GetSphereRect (FleetPtr, &temp_r0, &last_r);
+ ++last_r.extent.width;
+ ++last_r.extent.height;
+ VisibleChange = FALSE;
+ do
+ {
+ do
+ {
+ FleetPtr->known_strength += dx;
+ GetSphereRect (FleetPtr, &temp_r1, &r);
+ } while (delta--
+ && ((delta & 0xF)
+ || temp_r0.extent.height == temp_r1.extent.height));
+
+ if (ButtonState)
+ {
+ if (!AnyButtonPress (TRUE))
+ ButtonState = 0;
+ }
+ else if ((Interrupted = (BOOLEAN)(
+ Interrupted || AnyButtonPress (TRUE)
+ )))
+ {
+ MapDrawn = TRUE;
+ goto DoneSphereGrowth;
+ }
+ ++r.extent.width;
+ ++r.extent.height;
+ if (temp_r0.extent.height != temp_r1.extent.height)
+ {
+ VisibleChange = TRUE;
+ RepairMap (index, &last_r, &r);
+ }
+ } while (delta >= 0);
+ if (VisibleChange || temp_r0.extent.width != temp_r1.extent.width)
+ RepairMap ((COUNT)~0, &last_r, &r);
+
+DoneSphereGrowth:
+ FleetPtr->known_strength = FleetPtr->actual_strength;
+ }
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+}
+
+BOOLEAN
+StarMap (void)
+{
+ MENU_STATE MenuState;
+ POINT universe;
+ //FRAME OldFrame;
+ RECT clip_r;
+ CONTEXT OldContext;
+
+ memset (&MenuState, 0, sizeof (MenuState));
+
+ zoomLevel = 0;
+ mapOrigin.x = MAX_X_UNIVERSE >> 1;
+ mapOrigin.y = MAX_Y_UNIVERSE >> 1;
+ StarMapFrame = SetAbsFrameIndex (MiscDataFrame, 48);
+
+ if (!inHQSpace ())
+ universe = CurStarDescPtr->star_pt;
+ else
+ {
+ universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+ }
+
+ cursorLoc = GLOBAL (autopilot);
+ if (cursorLoc.x == ~0 && cursorLoc.y == ~0)
+ cursorLoc = universe;
+
+ MenuState.InputFunc = DoMoveCursor;
+ MenuState.Initialized = FALSE;
+
+ transition_pending = TRUE;
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ UpdateMap ();
+
+ DrawStarMap (0, (RECT*)-1);
+ transition_pending = FALSE;
+
+ BatchGraphics ();
+ OldContext = SetContext (SpaceContext);
+ GetContextClipRect (&clip_r);
+ SetContext (OldContext);
+ LoadIntoExtraScreen (&clip_r);
+ DrawCursor (UNIVERSE_TO_DISPX (cursorLoc.x),
+ UNIVERSE_TO_DISPY (cursorLoc.y));
+ UnbatchGraphics ();
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ SetMenuRepeatDelay (MIN_ACCEL_DELAY, MAX_ACCEL_DELAY, STEP_ACCEL_DELAY,
+ TRUE);
+ DoInput (&MenuState, FALSE);
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ SetDefaultMenuRepeatDelay ();
+
+ DrawHyperCoords (universe);
+ DrawSISMessage (NULL);
+ DrawStatusMessage (NULL);
+
+ if (GLOBAL (autopilot.x) == universe.x
+ && GLOBAL (autopilot.y) == universe.y)
+ GLOBAL (autopilot.x) = GLOBAL (autopilot.y) = ~0;
+
+ return (GLOBAL (autopilot.x) != ~0
+ && GLOBAL (autopilot.y) != ~0);
+}
+
diff --git a/src/uqm/planets/report.c b/src/uqm/planets/report.c
new file mode 100644
index 0000000..5defbe7
--- /dev/null
+++ b/src/uqm/planets/report.c
@@ -0,0 +1,271 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "lander.h"
+#include "scan.h"
+#include "planets.h"
+#include "../colors.h"
+#include "../controls.h"
+#include "../gamestr.h"
+#include "../setup.h"
+#include "../util.h"
+#include "../sounds.h"
+#include "../uqmdebug.h"
+#include "options.h"
+#include "libs/inplib.h"
+
+#include <ctype.h>
+#include <string.h>
+
+
+#define NUM_CELL_COLS MAP_WIDTH / 6
+#define NUM_CELL_ROWS MAP_HEIGHT / 6
+#define MAX_CELL_COLS 40
+
+extern FRAME SpaceJunkFrame;
+
+static void
+ClearReportArea (void)
+{
+ COUNT x, y;
+ RECT r;
+ STAMP s;
+ COORD startx;
+
+ if (optWhichFonts == OPT_PC)
+ s.frame = SetAbsFrameIndex (SpaceJunkFrame, 21);
+ else
+ s.frame = SetAbsFrameIndex (SpaceJunkFrame, 18);
+ GetFrameRect (s.frame, &r);
+
+ BatchGraphics ();
+
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x07, 0x00), 0x57));
+
+ startx = 1 + (r.extent.width >> 1) - 1;
+ s.origin.y = 1;
+ for (y = 0; y < NUM_CELL_ROWS; ++y)
+ {
+ s.origin.x = startx;
+ for (x = 0; x < NUM_CELL_COLS; ++x)
+ {
+ if (optWhichFonts == OPT_PC)
+ DrawStamp (&s);
+ else
+ DrawFilledStamp (&s);
+
+ s.origin.x += r.extent.width + 1;
+ }
+ s.origin.y += r.extent.height + 1;
+ }
+
+ UnbatchGraphics ();
+}
+
+static void
+MakeReport (SOUND ReadOutSounds, UNICODE *pStr, COUNT StrLen)
+{
+ BYTE ButtonState;
+ int end_page_len;
+ UNICODE end_page_buf[200];
+ UniChar last_c = 0;
+ COUNT row_cells;
+ BOOLEAN Sleepy;
+ RECT r;
+ TEXT t;
+
+ sprintf (end_page_buf, "%s\n", GAME_STRING (SCAN_STRING_BASE + NUM_SCAN_TYPES));
+ end_page_len = utf8StringCount (end_page_buf);
+
+ GetFrameRect (SetAbsFrameIndex (SpaceJunkFrame, 18), &r);
+
+ t.align = ALIGN_LEFT;
+ t.CharCount = 1;
+ t.pStr = pStr;
+
+ Sleepy = TRUE;
+
+ FlushInput ();
+ // XXX: this is a pretty ugly goto
+ goto InitPageCell;
+
+ while (StrLen)
+ {
+ COUNT col_cells;
+ const UNICODE *pLastStr;
+ const UNICODE *pNextStr;
+ COUNT lf_pos;
+
+ pLastStr = t.pStr;
+
+ // scan for LFs in the remaining string
+ // trailing LF will be ignored
+ for (lf_pos = StrLen, pNextStr = t.pStr;
+ lf_pos && getCharFromString (&pNextStr) != '\n';
+ --lf_pos)
+ ;
+
+ col_cells = 0;
+ // check if the remaining text fits on current screen
+ if (row_cells == NUM_CELL_ROWS - 1
+ && (StrLen > NUM_CELL_COLS || lf_pos > 1))
+ {
+ col_cells = (NUM_CELL_COLS >> 1) - (end_page_len >> 1);
+ t.pStr = end_page_buf;
+ StrLen += end_page_len;
+ }
+ t.baseline.x = 1 + (r.extent.width >> 1)
+ + (col_cells * (r.extent.width + 1)) - 1;
+ do
+ {
+ COUNT word_chars;
+ const UNICODE *pStr;
+ UniChar c;
+
+ pStr = t.pStr;
+ pNextStr = t.pStr;
+ while (UniChar_isGraph (getCharFromString (&pNextStr)))
+ pStr = pNextStr;
+
+ word_chars = utf8StringCountN (t.pStr, pStr);
+ if ((col_cells += word_chars) <= NUM_CELL_COLS)
+ {
+ TimeCount TimeOut;
+
+ if (StrLen -= word_chars)
+ --StrLen;
+ TimeOut = GetTimeCounter ();
+ while (word_chars--)
+ {
+ pNextStr = t.pStr;
+ c = getCharFromString (&pNextStr);
+
+ if (!Sleepy || (GLOBAL (CurrentActivity) & CHECK_ABORT))
+ font_DrawText (&t);
+ else
+ {
+ font_DrawText (&t);
+
+ PlaySound (ReadOutSounds, NotPositional (), NULL,
+ GAME_SOUND_PRIORITY);
+
+ if (c == ',')
+ TimeOut += ONE_SECOND / 4;
+ if (c == '.' || c == '!' || c == '?')
+ TimeOut += ONE_SECOND / 2;
+ else
+ TimeOut += ONE_SECOND / 20;
+ if (word_chars == 0)
+ TimeOut += ONE_SECOND / 20;
+
+ if (WaitForAnyButtonUntil (TRUE, TimeOut, FALSE))
+ {
+ Sleepy = FALSE;
+ // We draw the whole thing at once after this
+ BatchGraphics ();
+ }
+ }
+ t.pStr = pNextStr;
+ t.baseline.x += r.extent.width + 1;
+ }
+
+ ++col_cells;
+ last_c = getCharFromString (&t.pStr);
+ t.baseline.x += r.extent.width + 1;
+ }
+ } while (col_cells <= NUM_CELL_COLS && last_c != '\n' && StrLen);
+
+ t.baseline.y += r.extent.height + 1;
+ if (++row_cells == NUM_CELL_ROWS || StrLen == 0)
+ {
+ t.pStr = pLastStr;
+ if (!Sleepy)
+ {
+ UnbatchGraphics ();
+ }
+
+ if (!WaitForAnyButton (TRUE, WAIT_INFINITE, FALSE))
+ break;
+
+InitPageCell:
+ ButtonState = 1;
+ t.baseline.y = r.extent.height + 1;
+ row_cells = 0;
+ if (StrLen)
+ {
+ if (!Sleepy)
+ BatchGraphics ();
+ ClearReportArea();
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x1F, 0x00), 0xFF));
+ }
+ }
+ }
+}
+
+void
+DoDiscoveryReport (SOUND ReadOutSounds)
+{
+ CONTEXT OldContext;
+ CONTEXT context;
+ BOOLEAN ownContext;
+ STAMP saveStamp;
+
+#ifdef DEBUG
+ if (disableInteractivity)
+ return;
+#endif
+
+ context = GetScanContext (&ownContext);
+ OldContext = SetContext (context);
+ saveStamp = SaveContextFrame (NULL);
+ {
+ FONT OldFont;
+ FRAME OldFontEffect;
+
+ OldFont = SetContextFont (
+ pSolarSysState->SysInfo.PlanetInfo.LanderFont);
+ if (optWhichFonts == OPT_PC)
+ OldFontEffect = SetContextFontEffect (
+ pSolarSysState->SysInfo.PlanetInfo.LanderFontEff);
+ else
+ OldFontEffect = SetContextFontEffect (NULL);
+
+ MakeReport (ReadOutSounds,
+ (UNICODE *)GetStringAddress (pSolarSysState->SysInfo.PlanetInfo.DiscoveryString),
+ GetStringLength (pSolarSysState->SysInfo.PlanetInfo.DiscoveryString));
+
+ SetContextFontEffect (OldFontEffect);
+ SetContextFont (OldFont);
+ }
+ // Restore previous screen
+ DrawStamp (&saveStamp);
+ SetContext (OldContext);
+ // TODO: Make CONTEXT ref-counted
+ if (ownContext)
+ DestroyScanContext ();
+
+ DestroyDrawable (ReleaseDrawable (saveStamp.frame));
+
+ WaitForNoInput (WAIT_INFINITE, TRUE);
+}
+
+
diff --git a/src/uqm/planets/roster.c b/src/uqm/planets/roster.c
new file mode 100644
index 0000000..663ac28
--- /dev/null
+++ b/src/uqm/planets/roster.c
@@ -0,0 +1,428 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../build.h"
+#include "../colors.h"
+#include "../controls.h"
+#include "../races.h"
+#include "../units.h"
+#include "../sis.h"
+#include "../shipcont.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "port.h"
+#include "libs/gfxlib.h"
+#include "libs/tasklib.h"
+
+#include <stdlib.h>
+
+// Ship icon positions in status display around the flagship
+static const POINT ship_pos[MAX_BUILT_SHIPS] =
+{
+ SUPPORT_SHIP_PTS
+};
+
+typedef struct
+{
+ // Ship icon positions split into (lower half) left and right (upper)
+ // and sorted in the Y coord. These are used for navigation around the
+ // escort positions.
+ POINT shipPos[MAX_BUILT_SHIPS];
+ COUNT count;
+ // Number of ships
+
+ POINT curShipPt;
+ // Location of the currently selected escort
+ FRAME curShipFrame;
+ // Icon of the currently selected escort
+ bool modifyingCrew;
+ // true when in crew modification "sub-menu". This is simple
+ // enough that it does not require a real sub-menu.
+} ROSTER_STATE;
+
+static SHIP_FRAGMENT* LockSupportShip (ROSTER_STATE *, HSHIPFRAG *phFrag);
+
+static void
+drawSupportShip (ROSTER_STATE *rosterState, bool filled)
+{
+ STAMP s;
+
+ if (!rosterState->curShipFrame)
+ return;
+
+ s.origin = rosterState->curShipPt;
+ s.frame = rosterState->curShipFrame;
+
+ if (filled)
+ DrawFilledStamp (&s);
+ else
+ DrawStamp (&s);
+}
+
+static void
+getSupportShipIcon (ROSTER_STATE *rosterState)
+{
+ HSHIPFRAG hShipFrag;
+ SHIP_FRAGMENT *ShipFragPtr;
+
+ rosterState->curShipFrame = NULL;
+ ShipFragPtr = LockSupportShip (rosterState, &hShipFrag);
+ if (!ShipFragPtr)
+ return;
+
+ rosterState->curShipFrame = ShipFragPtr->icons;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hShipFrag);
+}
+
+static void
+flashSupportShip (ROSTER_STATE *rosterState)
+{
+ static Color c = BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x24);
+ static TimeCount NextTime = 0;
+
+ if (GetTimeCounter () >= NextTime)
+ {
+ NextTime = GetTimeCounter () + (ONE_SECOND / 15);
+
+ /* The commented code out code is the old code before the switch
+ * to 24-bits colors. The current code produces very slightly
+ * different colors due to rounding errors, but the old code wasn't
+ * original anyhow, and you can't tell the difference visually.
+ * - SvdB
+ if (c >= BUILD_COLOR (MAKE_RGB15 (0x1F, 0x19, 0x19), 0x24))
+ c = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x24);
+ else
+ c += BUILD_COLOR (MAKE_RGB15 (0x00, 0x02, 0x02), 0x00);
+ */
+
+ if (c.g >= CC5TO8 (0x19))
+ {
+ c = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x24);
+ }
+ else
+ {
+ c.g += CC5TO8 (0x02);
+ c.b += CC5TO8 (0x02);
+ }
+ SetContextForeGroundColor (c);
+
+ drawSupportShip (rosterState, TRUE);
+ }
+}
+
+static SHIP_FRAGMENT *
+LockSupportShip (ROSTER_STATE *rosterState, HSHIPFRAG *phFrag)
+{
+ const POINT *pship_pos;
+ HSHIPFRAG hStarShip, hNextShip;
+
+ // Lookup the current escort's location in the unsorted points list
+ // to find the original escort index
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)),
+ pship_pos = ship_pos;
+ hStarShip; hStarShip = hNextShip, ++pship_pos)
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ if (pointsEqual (*pship_pos, rosterState->curShipPt))
+ {
+ *phFrag = hStarShip;
+ return StarShipPtr;
+ }
+
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+
+ return NULL;
+}
+
+static void
+flashSupportShipCrew (void)
+{
+ RECT r;
+
+ SetContext (StatusContext);
+ GetStatusMessageRect (&r);
+ SetFlashRect (&r);
+}
+
+static BOOLEAN
+DeltaSupportCrew (ROSTER_STATE *rosterState, SIZE crew_delta)
+{
+ BOOLEAN ret = FALSE;
+ UNICODE buf[40];
+ HFLEETINFO hTemplate;
+ HSHIPFRAG hShipFrag;
+ SHIP_FRAGMENT *StarShipPtr;
+ FLEET_INFO *TemplatePtr;
+
+ StarShipPtr = LockSupportShip (rosterState, &hShipFrag);
+ if (!StarShipPtr)
+ return FALSE;
+
+ hTemplate = GetStarShipFromIndex (&GLOBAL (avail_race_q),
+ StarShipPtr->race_id);
+ TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hTemplate);
+
+ StarShipPtr->crew_level += crew_delta;
+
+ if (StarShipPtr->crew_level == 0)
+ StarShipPtr->crew_level = 1;
+ else if (StarShipPtr->crew_level > TemplatePtr->crew_level &&
+ crew_delta > 0)
+ StarShipPtr->crew_level -= crew_delta;
+ else
+ {
+ if (StarShipPtr->crew_level >= TemplatePtr->crew_level)
+ sprintf (buf, "%u", StarShipPtr->crew_level);
+ else
+ sprintf (buf, "%u/%u",
+ StarShipPtr->crew_level,
+ TemplatePtr->crew_level);
+
+ PreUpdateFlashRect ();
+ DrawStatusMessage (buf);
+ PostUpdateFlashRect ();
+ DeltaSISGauges (-crew_delta, 0, 0);
+ if (crew_delta)
+ {
+ flashSupportShipCrew ();
+ }
+ ret = TRUE;
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hTemplate);
+ UnlockShipFrag (&GLOBAL (built_ship_q), hShipFrag);
+
+ return ret;
+}
+
+static void
+drawModifiedSupportShip (ROSTER_STATE *rosterState)
+{
+ SetContext (StatusContext);
+ SetContextForeGroundColor (ROSTER_MODIFY_SHIP_COLOR);
+ drawSupportShip (rosterState, TRUE);
+}
+
+static void
+selectSupportShip (ROSTER_STATE *rosterState, COUNT shipIndex)
+{
+ rosterState->curShipPt = rosterState->shipPos[shipIndex];
+ getSupportShipIcon (rosterState);
+ DeltaSupportCrew (rosterState, 0);
+}
+
+static BOOLEAN
+DoModifyRoster (MENU_STATE *pMS)
+{
+ ROSTER_STATE *rosterState = pMS->privData;
+ BOOLEAN select, cancel, up, down, horiz;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ select = PulsedInputState.menu[KEY_MENU_SELECT];
+ cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
+ up = PulsedInputState.menu[KEY_MENU_UP];
+ down = PulsedInputState.menu[KEY_MENU_DOWN];
+ // Left or right produces the same effect because there are 2 columns
+ horiz = PulsedInputState.menu[KEY_MENU_LEFT] ||
+ PulsedInputState.menu[KEY_MENU_RIGHT];
+
+ if (cancel && !rosterState->modifyingCrew)
+ {
+ return FALSE;
+ }
+ else if (select || cancel)
+ {
+ rosterState->modifyingCrew ^= true;
+ if (!rosterState->modifyingCrew)
+ {
+ SetFlashRect (NULL);
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ }
+ else
+ {
+ drawModifiedSupportShip (rosterState);
+ flashSupportShipCrew ();
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN,
+ MENU_SOUND_SELECT | MENU_SOUND_CANCEL);
+ }
+ }
+ else if (rosterState->modifyingCrew)
+ {
+ SIZE delta = 0;
+ BOOLEAN failed = FALSE;
+
+ if (up)
+ {
+ if (GLOBAL_SIS (CrewEnlisted))
+ delta = 1;
+ else
+ failed = TRUE;
+ }
+ else if (down)
+ {
+ if (GLOBAL_SIS (CrewEnlisted) < GetCrewPodCapacity ())
+ delta = -1;
+ else
+ failed = TRUE;
+ }
+
+ if (delta != 0)
+ {
+ failed = !DeltaSupportCrew (rosterState, delta);
+ }
+
+ if (failed)
+ { // not enough room or crew
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ }
+ else
+ {
+ COUNT NewState;
+ POINT *pship_pos = rosterState->shipPos;
+ COUNT top_right = (rosterState->count + 1) >> 1;
+
+ NewState = pMS->CurState;
+
+ if (rosterState->count < 2)
+ {
+ // no navigation allowed
+ }
+ else if (horiz)
+ {
+ if (NewState == top_right - 1)
+ NewState = rosterState->count - 1;
+ else if (NewState >= top_right)
+ {
+ NewState -= top_right;
+ if (pship_pos[NewState].y < pship_pos[pMS->CurState].y)
+ ++NewState;
+ }
+ else
+ {
+ NewState += top_right;
+ if (NewState != top_right
+ && pship_pos[NewState].y > pship_pos[pMS->CurState].y)
+ --NewState;
+ }
+ }
+ else if (down)
+ {
+ ++NewState;
+ if (NewState == rosterState->count)
+ NewState = top_right;
+ else if (NewState == top_right)
+ NewState = 0;
+ }
+ else if (up)
+ {
+ if (NewState == 0)
+ NewState = top_right - 1;
+ else if (NewState == top_right)
+ NewState = rosterState->count - 1;
+ else
+ --NewState;
+ }
+
+ BatchGraphics ();
+ SetContext (StatusContext);
+
+ if (NewState != pMS->CurState)
+ {
+ // Draw the previous escort in unselected state
+ drawSupportShip (rosterState, FALSE);
+ // Select the new one
+ selectSupportShip (rosterState, NewState);
+ pMS->CurState = NewState;
+ }
+
+ flashSupportShip (rosterState);
+
+ UnbatchGraphics ();
+ }
+
+ SleepThread (ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+static int
+compShipPos (const void *ptr1, const void *ptr2)
+{
+ const POINT *pt1 = (const POINT *) ptr1;
+ const POINT *pt2 = (const POINT *) ptr2;
+
+ // Ships on the left in the lower half
+ if (pt1->x < pt2->x)
+ return -1;
+ else if (pt1->x > pt2->x)
+ return 1;
+
+ // and ordered on Y
+ if (pt1->y < pt2->y)
+ return -1;
+ else if (pt1->y > pt2->y)
+ return 1;
+ else
+ return 0;
+}
+
+BOOLEAN
+RosterMenu (void)
+{
+ MENU_STATE MenuState;
+ ROSTER_STATE RosterState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+ MenuState.privData = &RosterState;
+
+ memset (&RosterState, 0, sizeof RosterState);
+
+ RosterState.count = CountLinks (&GLOBAL (built_ship_q));
+ if (!RosterState.count)
+ return FALSE;
+
+ // Get the escort positions we will use and sort on X then Y
+ assert (sizeof (RosterState.shipPos) == sizeof (ship_pos));
+ memcpy (RosterState.shipPos, ship_pos, sizeof (ship_pos));
+ qsort (RosterState.shipPos, RosterState.count,
+ sizeof (RosterState.shipPos[0]), compShipPos);
+
+ SetContext (StatusContext);
+ selectSupportShip (&RosterState, MenuState.CurState);
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ MenuState.InputFunc = DoModifyRoster;
+ DoInput (&MenuState, TRUE);
+
+ SetContext (StatusContext);
+ // unselect the last ship
+ drawSupportShip (&RosterState, FALSE);
+ DrawStatusMessage (NULL);
+
+ return TRUE;
+}
+
diff --git a/src/uqm/planets/scan.c b/src/uqm/planets/scan.c
new file mode 100644
index 0000000..3d5d9fd
--- /dev/null
+++ b/src/uqm/planets/scan.c
@@ -0,0 +1,1385 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "lander.h"
+#include "lifeform.h"
+#include "scan.h"
+#include "../build.h"
+#include "../colors.h"
+#include "../cons_res.h"
+#include "../controls.h"
+#include "../menustat.h"
+#include "../encount.h"
+ // for EncounterGroup
+#include "../gamestr.h"
+#include "../nameref.h"
+#include "../resinst.h"
+#include "../settings.h"
+#include "../util.h"
+#include "../process.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "../state.h"
+#include "../sis.h"
+#include "../save.h"
+#include "options.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/drawable.h"
+#include "libs/inplib.h"
+#include "libs/mathlib.h"
+
+extern FRAME SpaceJunkFrame;
+
+// define SPIN_ON_SCAN to allow the planet to spin
+// while scaning is going on
+#undef SPIN_ON_SCAN
+
+#define FLASH_INDEX 105
+
+static CONTEXT ScanContext;
+
+static POINT planetLoc;
+static RECT cursorRect;
+static FRAME eraseFrame;
+
+// ScanSystem() menu items
+// The first three are from enum PlanetScanTypes in planets.h
+enum ScanMenuItems
+{
+ EXIT_SCAN = NUM_SCAN_TYPES,
+ AUTO_SCAN,
+ DISPATCH_SHUTTLE,
+};
+
+
+void
+RepairBackRect (RECT *pRect)
+{
+ RECT new_r, old_r;
+
+ GetContextClipRect (&old_r);
+ new_r.corner.x = pRect->corner.x + old_r.corner.x;
+ new_r.corner.y = pRect->corner.y + old_r.corner.y;
+ new_r.extent = pRect->extent;
+
+ new_r.extent.height += new_r.corner.y & 1;
+ new_r.corner.y &= ~1;
+ DrawFromExtraScreen (&new_r);
+}
+
+static void
+EraseCoarseScan (void)
+{
+ SetContext (PlanetContext);
+
+ BatchGraphics ();
+ DrawStarBackGround ();
+ DrawDefaultPlanetSphere ();
+ UnbatchGraphics ();
+}
+
+static void
+PrintScanTitlePC (TEXT *t, RECT *r, const char *txt, int xpos)
+{
+ t->baseline.x = xpos;
+ SetContextForeGroundColor (SCAN_PC_TITLE_COLOR);
+ t->pStr = txt;
+ t->CharCount = (COUNT)~0;
+ font_DrawText (t);
+ TextRect (t, r, NULL);
+ t->baseline.x += r->extent.width;
+ SetContextForeGroundColor (SCAN_INFO_COLOR);
+}
+
+static void
+MakeScanValue (UNICODE *buf, long val, const UNICODE *extra)
+{
+ if (val >= 10 * 100)
+ { // 1 decimal place
+ sprintf (buf, "%ld.%ld%s", val / 100, (val / 10) % 10, extra);
+ }
+ else
+ { // 2 decimal places
+ sprintf (buf, "%ld.%02ld%s", val / 100, val % 100, extra);
+ }
+}
+
+static void
+GetPlanetTitle (UNICODE *buf, COUNT bufsize)
+{
+ int val;
+ UNICODE *named = GetNamedPlanetaryBody ();
+ if (named)
+ {
+ utf8StringCopy (buf, bufsize, named);
+ return;
+ }
+
+ // Unnamed body, use world type
+ val = pSolarSysState->pOrbitalDesc->data_index & ~PLANET_SHIELDED;
+ if (val >= FIRST_GAS_GIANT)
+ {
+ sprintf (buf, "%s", GAME_STRING (SCAN_STRING_BASE + 4 + 51));
+ // Gas Giant
+ }
+ else
+ {
+ sprintf (buf, "%s %s",
+ GAME_STRING (SCAN_STRING_BASE + 4 + val),
+ GAME_STRING (SCAN_STRING_BASE + 4 + 50));
+ // World
+ }
+}
+
+static void
+PrintCoarseScanPC (void)
+{
+#define SCAN_LEADING_PC 14
+ SDWORD val;
+ TEXT t;
+ RECT r;
+ UNICODE buf[200];
+
+ GetPlanetTitle (buf, sizeof (buf));
+
+ SetContext (PlanetContext);
+
+ t.align = ALIGN_CENTER;
+ t.baseline.x = SIS_SCREEN_WIDTH >> 1;
+ t.baseline.y = 13;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+
+ SetContextForeGroundColor (SCAN_PC_TITLE_COLOR);
+ SetContextFont (MicroFont);
+ font_DrawText (&t);
+
+ SetContextFont (TinyFont);
+
+#define LEFT_SIDE_BASELINE_X_PC 5
+#define RIGHT_SIDE_BASELINE_X_PC (SIS_SCREEN_WIDTH - 75)
+#define SCAN_BASELINE_Y_PC 40
+
+ t.baseline.y = SCAN_BASELINE_Y_PC;
+ t.align = ALIGN_LEFT;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE),
+ LEFT_SIDE_BASELINE_X_PC); // "Orbit: "
+ val = ((pSolarSysState->SysInfo.PlanetInfo.PlanetToSunDist * 100L
+ + (EARTH_RADIUS >> 1)) / EARTH_RADIUS);
+ MakeScanValue (buf, val,
+ GAME_STRING (ORBITSCAN_STRING_BASE + 1)); // " a.u."
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 2),
+ LEFT_SIDE_BASELINE_X_PC); // "Atmo: "
+ if (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == GAS_GIANT_ATMOSPHERE)
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (ORBITSCAN_STRING_BASE + 3)); // "Super Thick"
+ else if (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == 0)
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (ORBITSCAN_STRING_BASE + 4)); // "Vacuum"
+ else
+ {
+ val = (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity * 100
+ + (EARTH_ATMOSPHERE >> 1)) / EARTH_ATMOSPHERE;
+ MakeScanValue (buf, val,
+ GAME_STRING (ORBITSCAN_STRING_BASE + 5)); // " atm"
+ }
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 6),
+ LEFT_SIDE_BASELINE_X_PC); // "Temp: "
+ sprintf (buf, "%d" STR_DEGREE_SIGN " c",
+ pSolarSysState->SysInfo.PlanetInfo.SurfaceTemperature);
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 7),
+ LEFT_SIDE_BASELINE_X_PC); // "Weather: "
+ if (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == 0)
+ t.pStr = GAME_STRING (ORBITSCAN_STRING_BASE + 8); // "None"
+ else
+ {
+ sprintf (buf, "%s %u",
+ GAME_STRING (ORBITSCAN_STRING_BASE + 9), // "Class"
+ pSolarSysState->SysInfo.PlanetInfo.Weather + 1);
+ t.pStr = buf;
+ }
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 10),
+ LEFT_SIDE_BASELINE_X_PC); // "Tectonics: "
+ if (PLANSIZE (pSolarSysState->SysInfo.PlanetInfo.PlanDataPtr->Type) ==
+ GAS_GIANT)
+ t.pStr = GAME_STRING (ORBITSCAN_STRING_BASE + 8); // "None"
+ else
+ {
+ sprintf (buf, "%s %u",
+ GAME_STRING (ORBITSCAN_STRING_BASE + 9), // "Class"
+ pSolarSysState->SysInfo.PlanetInfo.Tectonics + 1);
+ t.pStr = buf;
+ }
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ t.baseline.y = SCAN_BASELINE_Y_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 11),
+ RIGHT_SIDE_BASELINE_X_PC); // "Mass: "
+ val = pSolarSysState->SysInfo.PlanetInfo.PlanetRadius;
+ val = ((DWORD) val * (DWORD) val * (DWORD) val / 100L
+ * pSolarSysState->SysInfo.PlanetInfo.PlanetDensity
+ + ((100L * 100L) >> 1)) / (100L * 100L);
+ if (val == 0)
+ val = 1;
+ MakeScanValue (buf, val,
+ GAME_STRING (ORBITSCAN_STRING_BASE + 12)); // " e.s."
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 13),
+ RIGHT_SIDE_BASELINE_X_PC); // "Radius: "
+ val = pSolarSysState->SysInfo.PlanetInfo.PlanetRadius;
+ MakeScanValue (buf, val,
+ GAME_STRING (ORBITSCAN_STRING_BASE + 12)); // " e.s."
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 14),
+ RIGHT_SIDE_BASELINE_X_PC); // "Gravity: "
+ val = pSolarSysState->SysInfo.PlanetInfo.SurfaceGravity;
+ if (val == 0)
+ val = 1;
+ MakeScanValue (buf, val,
+ GAME_STRING (ORBITSCAN_STRING_BASE + 15)); // " g."
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 16),
+ RIGHT_SIDE_BASELINE_X_PC); // "Day: "
+ val = (SDWORD)pSolarSysState->SysInfo.PlanetInfo.RotationPeriod
+ * 10 / 24;
+ MakeScanValue (buf, val,
+ GAME_STRING (ORBITSCAN_STRING_BASE + 17)); // " days"
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING_PC;
+
+ PrintScanTitlePC (&t, &r, GAME_STRING (ORBITSCAN_STRING_BASE + 18),
+ RIGHT_SIDE_BASELINE_X_PC); // "Tilt: "
+ val = pSolarSysState->SysInfo.PlanetInfo.AxialTilt;
+ if (val < 0)
+ val = -val;
+ t.pStr = buf;
+ sprintf (buf, "%d" STR_DEGREE_SIGN, val);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+}
+
+static void
+PrintCoarseScan3DO (void)
+{
+#define SCAN_LEADING 19
+ SDWORD val;
+ TEXT t;
+ STAMP s;
+ UNICODE buf[200];
+
+ GetPlanetTitle (buf, sizeof (buf));
+
+ SetContext (PlanetContext);
+
+ t.align = ALIGN_CENTER;
+ t.baseline.x = SIS_SCREEN_WIDTH >> 1;
+ t.baseline.y = 13;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+
+ SetContextForeGroundColor (SCAN_INFO_COLOR);
+ SetContextFont (MicroFont);
+ font_DrawText (&t);
+
+ s.origin.x = s.origin.y = 0;
+ s.origin.x = 16 - SAFE_X;
+ s.frame = SetAbsFrameIndex (SpaceJunkFrame, 20);
+ DrawStamp (&s);
+
+#define LEFT_SIDE_BASELINE_X (27 + (16 - SAFE_X))
+#define RIGHT_SIDE_BASELINE_X (SIS_SCREEN_WIDTH - LEFT_SIDE_BASELINE_X)
+#define SCAN_BASELINE_Y 25
+
+ t.baseline.x = LEFT_SIDE_BASELINE_X;
+ t.baseline.y = SCAN_BASELINE_Y;
+ t.align = ALIGN_LEFT;
+
+ t.pStr = buf;
+ val = ((pSolarSysState->SysInfo.PlanetInfo.PlanetToSunDist * 100L
+ + (EARTH_RADIUS >> 1)) / EARTH_RADIUS);
+ MakeScanValue (buf, val, STR_EARTH_SIGN);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING;
+
+ t.pStr = buf;
+ if (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == GAS_GIANT_ATMOSPHERE)
+ strcpy (buf, STR_INFINITY_SIGN);
+ else
+ {
+ val = (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity * 100
+ + (EARTH_ATMOSPHERE >> 1)) / EARTH_ATMOSPHERE;
+ MakeScanValue (buf, val, STR_EARTH_SIGN);
+ }
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING;
+
+ t.pStr = buf;
+ sprintf (buf, "%d" STR_DEGREE_SIGN,
+ pSolarSysState->SysInfo.PlanetInfo.SurfaceTemperature);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING;
+
+ t.pStr = buf;
+ sprintf (buf, "<%u>", pSolarSysState->SysInfo.PlanetInfo.AtmoDensity == 0
+ ? 0 : (pSolarSysState->SysInfo.PlanetInfo.Weather + 1));
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING;
+
+ t.pStr = buf;
+ sprintf (buf, "<%u>",
+ PLANSIZE (
+ pSolarSysState->SysInfo.PlanetInfo.PlanDataPtr->Type
+ ) == GAS_GIANT
+ ? 0 : (pSolarSysState->SysInfo.PlanetInfo.Tectonics + 1));
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ t.baseline.x = RIGHT_SIDE_BASELINE_X;
+ t.baseline.y = SCAN_BASELINE_Y;
+ t.align = ALIGN_RIGHT;
+
+ t.pStr = buf;
+ val = pSolarSysState->SysInfo.PlanetInfo.PlanetRadius;
+ val = ((DWORD) val * (DWORD) val * (DWORD) val / 100L
+ * pSolarSysState->SysInfo.PlanetInfo.PlanetDensity
+ + ((100L * 100L) >> 1)) / (100L * 100L);
+ if (val == 0)
+ val = 1;
+ MakeScanValue (buf, val, STR_EARTH_SIGN);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING;
+
+ t.pStr = buf;
+ val = pSolarSysState->SysInfo.PlanetInfo.PlanetRadius;
+ MakeScanValue (buf, val, STR_EARTH_SIGN);
+
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING;
+
+ t.pStr = buf;
+ val = pSolarSysState->SysInfo.PlanetInfo.SurfaceGravity;
+ if (val == 0)
+ val = 1;
+ MakeScanValue (buf, val, STR_EARTH_SIGN);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING;
+
+ t.pStr = buf;
+ val = pSolarSysState->SysInfo.PlanetInfo.AxialTilt;
+ if (val < 0)
+ val = -val;
+ sprintf (buf, "%d" STR_DEGREE_SIGN, val);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += SCAN_LEADING;
+
+ t.pStr = buf;
+ val = (SDWORD)pSolarSysState->SysInfo.PlanetInfo.RotationPeriod
+ * 10 / 24;
+ MakeScanValue (buf, val, STR_EARTH_SIGN);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+}
+
+static void
+initPlanetLocationImage (void)
+{
+ FRAME cursorFrame;
+
+ // Get the cursor image
+ cursorFrame = SetAbsFrameIndex (MiscDataFrame, FLASH_INDEX);
+ cursorRect.extent = GetFrameBounds (cursorFrame);
+}
+
+static void
+savePlanetLocationImage (void)
+{
+ RECT r;
+ FRAME cursorFrame = SetAbsFrameIndex (MiscDataFrame, FLASH_INDEX);
+ HOT_SPOT hs = GetFrameHot (cursorFrame);
+
+ DestroyDrawable (ReleaseDrawable (eraseFrame));
+
+ r = cursorRect;
+ r.corner.x -= hs.x;
+ r.corner.y -= hs.y;
+ eraseFrame = CaptureDrawable (CopyContextRect (&r));
+ SetFrameHot (eraseFrame, hs);
+}
+
+static void
+restorePlanetLocationImage (void)
+{
+ STAMP s;
+
+ s.origin = cursorRect.corner;
+ s.frame = eraseFrame; // saved image
+ DrawStamp (&s);
+}
+
+static void
+drawPlanetCursor (BOOLEAN filled)
+{
+ STAMP s;
+
+ s.origin = cursorRect.corner;
+ s.frame = SetAbsFrameIndex (MiscDataFrame, FLASH_INDEX);
+ if (filled)
+ DrawFilledStamp (&s);
+ else
+ DrawStamp (&s);
+}
+
+static void
+setPlanetCursorLoc (POINT new_pt)
+{
+ new_pt.x >>= MAG_SHIFT;
+ new_pt.y >>= MAG_SHIFT;
+ cursorRect.corner = new_pt;
+}
+
+static void
+setPlanetLoc (POINT new_pt, BOOLEAN restoreOld)
+{
+ planetLoc = new_pt;
+
+ SetContext (ScanContext);
+ if (restoreOld)
+ restorePlanetLocationImage ();
+ setPlanetCursorLoc (new_pt);
+ savePlanetLocationImage ();
+}
+
+static void
+flashPlanetLocation (void)
+{
+#define FLASH_FRAME_DELAY (ONE_SECOND / 16)
+ static BYTE c = 0x00;
+ static int val = -2;
+ static POINT prevPt;
+ static TimeCount NextTime = 0;
+ BOOLEAN locChanged;
+ TimeCount Now = GetTimeCounter ();
+
+ locChanged = prevPt.x != cursorRect.corner.x
+ || prevPt.y != cursorRect.corner.y;
+
+ if (!locChanged && Now < NextTime)
+ return; // nothing to do
+
+ if (locChanged)
+ { // Reset the flashing cycle
+ c = 0x00;
+ val = -2;
+ prevPt = cursorRect.corner;
+
+ NextTime = Now + FLASH_FRAME_DELAY;
+ }
+ else
+ { // Continue the flashing cycle
+ if (c == 0x00 || c == 0x1A)
+ val = -val;
+ c += val;
+
+ if (Now - NextTime > FLASH_FRAME_DELAY)
+ NextTime = Now + FLASH_FRAME_DELAY; // missed timing by too much
+ else
+ NextTime += FLASH_FRAME_DELAY; // stable frame rate
+ }
+
+ SetContext (ScanContext);
+ SetContextForeGroundColor (BUILD_COLOR (MAKE_RGB15 (c, c, c), c));
+ drawPlanetCursor (TRUE);
+}
+
+void
+RedrawSurfaceScan (const POINT *newLoc)
+{
+ CONTEXT OldContext;
+
+ OldContext = SetContext (ScanContext);
+
+ BatchGraphics ();
+ DrawPlanet (0, BLACK_COLOR);
+ DrawScannedObjects (TRUE);
+ if (newLoc)
+ {
+ setPlanetLoc (*newLoc, FALSE);
+ drawPlanetCursor (FALSE);
+ }
+ UnbatchGraphics ();
+
+ SetContext (OldContext);
+}
+
+static COUNT
+getLandingFuelNeeded (void)
+{
+ COUNT fuel;
+
+ fuel = pSolarSysState->SysInfo.PlanetInfo.SurfaceGravity << 1;
+ if (fuel > 3 * FUEL_TANK_SCALE)
+ fuel = 3 * FUEL_TANK_SCALE;
+
+ return fuel;
+}
+
+static void
+spawnFwiffo (void)
+{
+ HSHIPFRAG hStarShip;
+
+ EncounterGroup = 0;
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+ ReinitQueue (&GLOBAL (ip_group_q));
+ assert (CountLinks (&GLOBAL (npc_built_ship_q)) == 0);
+
+ hStarShip = CloneShipFragment (SPATHI_SHIP,
+ &GLOBAL (npc_built_ship_q), 1);
+ if (hStarShip)
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (npc_built_ship_q),
+ hStarShip);
+ // Name Fwiffo
+ StarShipPtr->captains_name_index = NAME_OFFSET +
+ NUM_CAPTAINS_NAMES;
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ }
+}
+
+// Returns TRUE if the parent menu should remain
+static BOOLEAN
+DispatchLander (void)
+{
+ InputFrameCallback *oldCallback;
+ SIZE landingFuel = getLandingFuelNeeded ();
+
+ EraseCoarseScan ();
+
+ // Deactivate planet rotation callback
+ oldCallback = SetInputCallback (NULL);
+
+ DeltaSISGauges (0, -landingFuel, 0);
+ SetContext (ScanContext);
+ drawPlanetCursor (FALSE);
+
+ PlanetSide (planetLoc);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (GET_GAME_STATE (FOUND_PLUTO_SPATHI) == 1)
+ {
+ /* Create Fwiffo group and go into comm with it */
+ spawnFwiffo ();
+
+ NextActivity |= CHECK_LOAD; /* fake a load game */
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ SaveSolarSysLocation ();
+
+ return FALSE;
+ }
+
+ if (optWhichCoarseScan == OPT_PC)
+ PrintCoarseScanPC ();
+ else
+ PrintCoarseScan3DO ();
+
+ // Reactivate planet rotation callback
+ SetInputCallback (oldCallback);
+
+ return TRUE;
+}
+
+typedef struct
+{
+ bool success;
+ // true when player selected a location
+} PICK_PLANET_STATE;
+
+static BOOLEAN
+DoPickPlanetSide (MENU_STATE *pMS)
+{
+ PICK_PLANET_STATE *pickState = pMS->privData;
+ DWORD TimeIn = GetTimeCounter ();
+ BOOLEAN select, cancel;
+
+ select = PulsedInputState.menu[KEY_MENU_SELECT];
+ cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ pickState->success = false;
+ return FALSE;
+ }
+
+ if (cancel)
+ {
+ pickState->success = false;
+ return FALSE;
+ }
+ else if (select)
+ {
+ pickState->success = true;
+ return FALSE;
+ }
+ else
+ {
+ SIZE dx = 0;
+ SIZE dy = 0;
+ POINT new_pt;
+
+ new_pt = planetLoc;
+
+ if (CurrentInputState.menu[KEY_MENU_LEFT])
+ dx = -1;
+ if (CurrentInputState.menu[KEY_MENU_RIGHT])
+ dx = 1;
+ if (CurrentInputState.menu[KEY_MENU_UP])
+ dy = -1;
+ if (CurrentInputState.menu[KEY_MENU_DOWN])
+ dy = 1;
+
+ BatchGraphics ();
+
+ dx = dx << MAG_SHIFT;
+ if (dx)
+ {
+ new_pt.x += dx;
+ if (new_pt.x < 0)
+ new_pt.x += (MAP_WIDTH << MAG_SHIFT);
+ else if (new_pt.x >= (MAP_WIDTH << MAG_SHIFT))
+ new_pt.x -= (MAP_WIDTH << MAG_SHIFT);
+ }
+ dy = dy << MAG_SHIFT;
+ if (dy)
+ {
+ new_pt.y += dy;
+ if (new_pt.y < 0 || new_pt.y >= (MAP_HEIGHT << MAG_SHIFT))
+ new_pt.y = planetLoc.y;
+ }
+
+ if (!pointsEqual (new_pt, planetLoc))
+ {
+ setPlanetLoc (new_pt, TRUE);
+ }
+
+ flashPlanetLocation ();
+
+ UnbatchGraphics ();
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 40);
+ }
+
+ return TRUE;
+}
+
+static void
+drawLandingFuelUsage (COUNT fuel)
+{
+ UNICODE buf[100];
+
+ sprintf (buf, "%s%1.1f",
+ GAME_STRING (NAVIGATION_STRING_BASE + 5),
+ (float) fuel / FUEL_TANK_SCALE);
+ DrawStatusMessage (buf);
+}
+
+static void
+eraseLandingFuelUsage (void)
+{
+ DrawStatusMessage (NULL);
+}
+
+static BOOLEAN
+PickPlanetSide (void)
+{
+ MENU_STATE MenuState;
+ PICK_PLANET_STATE PickState;
+ COUNT fuel = getLandingFuelNeeded ();
+ BOOLEAN retval = TRUE;
+
+ memset (&MenuState, 0, sizeof MenuState);
+ MenuState.privData = &PickState;
+
+ ClearSISRect (CLEAR_SIS_RADAR);
+ SetContext (ScanContext);
+ BatchGraphics ();
+ DrawPlanet (0, BLACK_COLOR);
+ DrawScannedObjects (FALSE);
+ UnbatchGraphics ();
+
+ drawLandingFuelUsage (fuel);
+ // Set the current flash location
+ setPlanetCursorLoc (planetLoc);
+ savePlanetLocationImage ();
+
+ InitLander (0);
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_SELECT);
+
+ PickState.success = false;
+ MenuState.InputFunc = DoPickPlanetSide;
+ DoInput (&MenuState, TRUE);
+
+ eraseLandingFuelUsage ();
+ if (PickState.success)
+ { // player chose a location
+ retval = DispatchLander ();
+ }
+ else
+ { // player bailed out
+ restorePlanetLocationImage ();
+ }
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ return retval;
+}
+
+#define NUM_FLASH_COLORS 8
+
+static void
+DrawScannedStuff (COUNT y, COUNT scan)
+{
+ HELEMENT hElement, hNextElement;
+ Color OldColor;
+
+ OldColor = SetContextForeGroundColor (BLACK_COLOR);
+
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+ SIZE dy;
+ STAMP s;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = GetSuccElement (ElementPtr);
+
+ dy = y - ElementPtr->current.location.y;
+ if (LOBYTE (ElementPtr->scan_node) != scan || dy < 0)
+ { // node of wrong type, or not time for it yet
+ UnlockElement (hElement);
+ continue;
+ }
+
+ // XXX: flag this as 'found' scanned object
+ ElementPtr->state_flags |= APPEARING;
+
+ s.origin = ElementPtr->current.location;
+
+ if (dy >= NUM_FLASH_COLORS)
+ { // flashing done for this node, draw normal
+ s.frame = ElementPtr->next.image.frame;
+ DrawStamp (&s);
+ }
+ else
+ {
+ BYTE grad;
+ Color c = WHITE_COLOR;
+ COUNT nodeSize;
+
+ // mineral -- white --> turquoise?? (contrasts with red)
+ // energy -- white --> red (contrasts with white)
+ // bio -- white --> violet (contrasts with green)
+ grad = 0xff - 0xff * dy / (NUM_FLASH_COLORS - 1);
+ switch (scan)
+ {
+ case MINERAL_SCAN:
+ c.r = grad;
+ break;
+ case ENERGY_SCAN:
+ c.g = grad;
+ c.b = grad;
+ break;
+ case BIOLOGICAL_SCAN:
+ c.g = grad;
+ break;
+ }
+
+ SetContextForeGroundColor (c);
+
+ // flash the node from the smallest size to node size
+ // Get the node size for mineral, or number of transitions
+ // for other scan types (was set by GeneratePlanetSide())
+ nodeSize = GetFrameIndex (ElementPtr->next.image.frame)
+ - GetFrameIndex (ElementPtr->current.image.frame);
+ if (dy > nodeSize)
+ dy = nodeSize;
+
+ s.frame = SetRelFrameIndex (ElementPtr->current.image.frame, dy);
+ DrawFilledStamp (&s);
+ }
+
+ UnlockElement (hElement);
+ }
+
+ SetContextForeGroundColor (OldColor);
+}
+
+COUNT
+callGenerateForScanType (const SOLARSYS_STATE *solarSys,
+ const PLANET_DESC *world, COUNT node, BYTE scanType, NODE_INFO *info)
+{
+ switch (scanType)
+ {
+ case MINERAL_SCAN:
+ return (*solarSys->genFuncs->generateMinerals) (
+ solarSys, world, node, info);
+ case ENERGY_SCAN:
+ return (*solarSys->genFuncs->generateEnergy) (
+ solarSys, world, node, info);
+ case BIOLOGICAL_SCAN:
+ return (*solarSys->genFuncs->generateLife) (
+ solarSys, world, node, info);
+ }
+
+ assert (false);
+ return 0;
+}
+
+bool
+callPickupForScanType (SOLARSYS_STATE *solarSys, PLANET_DESC *world,
+ COUNT node, BYTE scanType)
+{
+ switch (scanType)
+ {
+ case MINERAL_SCAN:
+ return (*solarSys->genFuncs->pickupMinerals) (
+ solarSys, world, node);
+ case ENERGY_SCAN:
+ return (*solarSys->genFuncs->pickupEnergy) (
+ solarSys, world, node);
+ case BIOLOGICAL_SCAN:
+ return (*solarSys->genFuncs->pickupLife) (
+ solarSys, world, node);
+ }
+
+ assert (false);
+ return false;
+}
+
+static void
+ScanPlanet (COUNT scanType)
+{
+#define SCAN_DURATION (ONE_SECOND * 7 / 4)
+// NUM_FLASH_COLORS for flashing blips; 1 for the final frame
+#define SCAN_LINES (MAP_HEIGHT + NUM_FLASH_COLORS + 1)
+#define SCAN_LINE_WAIT (SCAN_DURATION / SCAN_LINES)
+
+ COUNT startScan, endScan;
+ COUNT scan;
+ RECT r;
+ static const Color textColors[] =
+ {
+ SCAN_MINERAL_TEXT_COLOR,
+ SCAN_ENERGY_TEXT_COLOR,
+ SCAN_BIOLOGICAL_TEXT_COLOR,
+ };
+ static const Color tintColors[] =
+ {
+ SCAN_MINERAL_TINT_COLOR,
+ SCAN_ENERGY_TINT_COLOR,
+ SCAN_BIOLOGICAL_TINT_COLOR,
+ };
+
+ if (scanType == AUTO_SCAN)
+ {
+ startScan = MINERAL_SCAN;
+ endScan = BIOLOGICAL_SCAN;
+ }
+ else
+ {
+ startScan = scanType;
+ endScan = scanType;
+ }
+
+ for (scan = startScan; scan <= endScan; ++scan)
+ {
+ TEXT t;
+ SWORD i;
+ Color tintColor;
+ // Alpha value will be ignored.
+ TimeCount TimeOut;
+
+ t.baseline.x = SIS_SCREEN_WIDTH >> 1;
+ t.baseline.y = SIS_SCREEN_HEIGHT - MAP_HEIGHT - 7;
+ t.align = ALIGN_CENTER;
+ t.CharCount = (COUNT)~0;
+
+ t.pStr = GAME_STRING (SCAN_STRING_BASE + scan);
+
+ SetContext (PlanetContext);
+ r.corner.x = 0;
+ r.corner.y = t.baseline.y - 10;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = t.baseline.y - r.corner.y + 1;
+ // XXX: I do not know why we are repairing it here, as there
+ // should not be anything drawn over the stars at the moment
+ RepairBackRect (&r);
+
+ SetContextFont (MicroFont);
+ SetContextForeGroundColor (textColors[scan]);
+ font_DrawText (&t);
+
+ SetContext (ScanContext);
+
+ // Draw a virgin surface
+ BatchGraphics ();
+ DrawPlanet (0, BLACK_COLOR);
+ UnbatchGraphics ();
+
+ tintColor = tintColors[scan];
+
+ // Draw the scan slowly line by line
+ TimeOut = GetTimeCounter ();
+ for (i = 0; i < SCAN_LINES; i++)
+ {
+ TimeOut += SCAN_LINE_WAIT;
+ if (WaitForAnyButtonUntil (TRUE, TimeOut, FALSE))
+ break;
+
+ BatchGraphics ();
+ DrawPlanet (i, tintColor);
+ DrawScannedStuff (i, scan);
+ UnbatchGraphics ();
+#ifdef SPIN_ON_SCAN
+ RotatePlanetSphere (TRUE);
+#endif
+ }
+
+ if (i < SCAN_LINES)
+ { // Aborted by a keypress; draw in finished state
+ BatchGraphics ();
+ DrawPlanet (SCAN_LINES - 1, tintColor);
+ DrawScannedStuff (SCAN_LINES - 1, scan);
+ UnbatchGraphics ();
+ }
+ }
+
+ SetContext (PlanetContext);
+ RepairBackRect (&r);
+
+ SetContext (ScanContext);
+ if (scanType == AUTO_SCAN)
+ { // clear the last scan
+ DrawPlanet (0, BLACK_COLOR);
+ DrawScannedObjects (FALSE);
+ }
+
+ FlushInput ();
+}
+
+static BOOLEAN
+DoScan (MENU_STATE *pMS)
+{
+ BOOLEAN select, cancel;
+
+ select = PulsedInputState.menu[KEY_MENU_SELECT];
+ cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (cancel || (select && pMS->CurState == EXIT_SCAN))
+ {
+ return FALSE;
+ }
+ else if (select)
+ {
+ if (pMS->CurState == DISPATCH_SHUTTLE)
+ {
+ COUNT fuel_required;
+
+ if ((pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED)
+ || (pSolarSysState->SysInfo.PlanetInfo.AtmoDensity ==
+ GAS_GIANT_ATMOSPHERE))
+ { // cannot dispatch to shielded planets or gas giants
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return TRUE;
+ }
+
+ fuel_required = getLandingFuelNeeded ();
+ if (GLOBAL_SIS (FuelOnBoard) < fuel_required
+ || GLOBAL_SIS (NumLanders) == 0
+ || GLOBAL_SIS (CrewEnlisted) == 0)
+ {
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return TRUE;
+ }
+
+ SetFlashRect (NULL);
+
+ if (!PickPlanetSide ())
+ return FALSE;
+
+ DrawMenuStateStrings (PM_MIN_SCAN, pMS->CurState);
+ SetFlashRect (SFR_MENU_3DO);
+
+ return TRUE;
+ }
+
+ // Various scans
+ if (pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED)
+ { // cannot scan shielded planets
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return TRUE;
+ }
+
+ ScanPlanet (pMS->CurState);
+ if (pMS->CurState == AUTO_SCAN)
+ {
+ pMS->CurState = DISPATCH_SHUTTLE;
+ DrawMenuStateStrings (PM_MIN_SCAN, pMS->CurState);
+ }
+ }
+ else if (optWhichMenu == OPT_PC ||
+ (!(pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED)
+ && pSolarSysState->SysInfo.PlanetInfo.AtmoDensity !=
+ GAS_GIANT_ATMOSPHERE))
+ {
+ DoMenuChooser (pMS, PM_MIN_SCAN);
+ }
+
+ return TRUE;
+}
+
+static CONTEXT
+CreateScanContext (void)
+{
+ CONTEXT oldContext;
+ CONTEXT context;
+ RECT r;
+
+ // ScanContext rect is relative to SpaceContext
+ oldContext = SetContext (SpaceContext);
+ GetContextClipRect (&r);
+
+ context = CreateContext ("ScanContext");
+ SetContext (context);
+ SetContextFGFrame (Screen);
+ r.corner.x += r.extent.width - MAP_WIDTH;
+ r.corner.y += r.extent.height - MAP_HEIGHT;
+ r.extent.width = MAP_WIDTH;
+ r.extent.height = MAP_HEIGHT;
+ SetContextClipRect (&r);
+
+ SetContext (oldContext);
+
+ return context;
+}
+
+CONTEXT
+GetScanContext (BOOLEAN *owner)
+{
+ // TODO: Make CONTEXT ref-counted
+ if (ScanContext)
+ {
+ if (owner)
+ *owner = FALSE;
+ }
+ else
+ {
+ if (owner)
+ *owner = TRUE;
+ ScanContext = CreateScanContext ();
+ }
+ return ScanContext;
+}
+
+void
+DestroyScanContext (void)
+{
+ if (ScanContext)
+ {
+ DestroyContext (ScanContext);
+ ScanContext = NULL;
+ }
+}
+
+void
+ScanSystem (void)
+{
+ MENU_STATE MenuState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+
+ GetScanContext (NULL);
+
+ if (optWhichMenu == OPT_3DO &&
+ ((pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED)
+ || pSolarSysState->SysInfo.PlanetInfo.AtmoDensity ==
+ GAS_GIANT_ATMOSPHERE))
+ {
+ MenuState.CurState = EXIT_SCAN;
+ }
+ else
+ {
+ MenuState.CurState = AUTO_SCAN;
+ planetLoc.x = (MAP_WIDTH >> 1) << MAG_SHIFT;
+ planetLoc.y = (MAP_HEIGHT >> 1) << MAG_SHIFT;
+
+ initPlanetLocationImage ();
+ SetContext (ScanContext);
+ DrawScannedObjects (FALSE);
+ }
+
+ DrawMenuStateStrings (PM_MIN_SCAN, MenuState.CurState);
+ SetFlashRect (SFR_MENU_3DO);
+
+ if (optWhichCoarseScan == OPT_PC)
+ PrintCoarseScanPC ();
+ else
+ PrintCoarseScan3DO ();
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ MenuState.InputFunc = DoScan;
+ DoInput (&MenuState, FALSE);
+
+ SetFlashRect (NULL);
+
+ // cleanup scan graphics
+ BatchGraphics ();
+ SetContext (ScanContext);
+ DrawPlanet (0, BLACK_COLOR);
+ EraseCoarseScan ();
+ UnbatchGraphics ();
+
+ DestroyDrawable (ReleaseDrawable (eraseFrame));
+ eraseFrame = NULL;
+}
+
+static void
+generateBioNode (SOLARSYS_STATE *system, ELEMENT *NodeElementPtr,
+ BYTE *life_init_tab, COUNT creatureType)
+{
+ COUNT i;
+
+ // NOTE: TFB_Random() calls here are NOT part of the deterministic planet
+ // generation PRNG flow.
+ if (CreatureData[creatureType].Attributes & SPEED_MASK)
+ {
+ // Place moving creatures at a random location.
+ i = TFB_Random ();
+ NodeElementPtr->current.location.x =
+ (LOBYTE (i) % (MAP_WIDTH - (8 << 1))) + 8;
+ NodeElementPtr->current.location.y =
+ (HIBYTE (i) % (MAP_HEIGHT - (8 << 1))) + 8;
+ }
+
+ if (system->PlanetSideFrame[0] == 0)
+ system->PlanetSideFrame[0] =
+ CaptureDrawable (LoadGraphic (CANNISTER_MASK_PMAP_ANIM));
+
+ for (i = 0; i < MAX_LIFE_VARIATION
+ && life_init_tab[i] != (BYTE)(creatureType + 1);
+ ++i)
+ {
+ if (life_init_tab[i] != 0)
+ continue;
+
+ life_init_tab[i] = (BYTE)creatureType + 1;
+
+ system->PlanetSideFrame[i + 3] = load_life_form (creatureType);
+ break;
+ }
+
+ NodeElementPtr->mass_points = (BYTE)creatureType;
+ NodeElementPtr->hit_points = HINIBBLE (
+ CreatureData[creatureType].ValueAndHitPoints);
+ DisplayArray[NodeElementPtr->PrimIndex].
+ Object.Stamp.frame = SetAbsFrameIndex (
+ system->PlanetSideFrame[i + 3], (COUNT)TFB_Random ());
+}
+
+void
+GeneratePlanetSide (void)
+{
+ SIZE scan;
+ BYTE life_init_tab[MAX_LIFE_VARIATION];
+ // life_init_tab is filled with the creature types of already
+ // selected creatures. If an entry is 0, none has been selected
+ // yet, otherwise, it is 1 more than the creature type.
+
+ InitDisplayList ();
+ if (pSolarSysState->pOrbitalDesc->data_index & PLANET_SHIELDED)
+ return;
+
+ memset (life_init_tab, 0, sizeof life_init_tab);
+
+ for (scan = BIOLOGICAL_SCAN; scan >= MINERAL_SCAN; --scan)
+ {
+ COUNT num_nodes;
+ FRAME f;
+
+ f = SetAbsFrameIndex (MiscDataFrame,
+ NUM_SCANDOT_TRANSITIONS * (scan - ENERGY_SCAN));
+
+ num_nodes = callGenerateForScanType (pSolarSysState,
+ pSolarSysState->pOrbitalDesc, GENERATE_ALL, scan, NULL);
+
+ while (num_nodes--)
+ {
+ HELEMENT hNodeElement;
+ ELEMENT *NodeElementPtr;
+ NODE_INFO info;
+
+ if (isNodeRetrieved (&pSolarSysState->SysInfo.PlanetInfo,
+ scan, num_nodes))
+ continue;
+
+ hNodeElement = AllocElement ();
+ if (!hNodeElement)
+ continue;
+
+ LockElement (hNodeElement, &NodeElementPtr);
+
+ callGenerateForScanType (pSolarSysState,
+ pSolarSysState->pOrbitalDesc, num_nodes,
+ scan, &info);
+
+ NodeElementPtr->scan_node = MAKE_WORD (scan, num_nodes + 1);
+ NodeElementPtr->playerNr = PS_NON_PLAYER;
+ NodeElementPtr->current.location.x = info.loc_pt.x;
+ NodeElementPtr->current.location.y = info.loc_pt.y;
+
+ SetPrimType (&DisplayArray[NodeElementPtr->PrimIndex], STAMP_PRIM);
+ if (scan == MINERAL_SCAN)
+ {
+ NodeElementPtr->turn_wait = info.type;
+ NodeElementPtr->mass_points = HIBYTE (info.density);
+ NodeElementPtr->current.image.frame = SetAbsFrameIndex (
+ MiscDataFrame, (NUM_SCANDOT_TRANSITIONS * 2)
+ + ElementCategory (info.type) * 5);
+ NodeElementPtr->next.image.frame = SetRelFrameIndex (
+ NodeElementPtr->current.image.frame,
+ LOBYTE (info.density) + 1);
+ DisplayArray[NodeElementPtr->PrimIndex].Object.Stamp.frame =
+ IncFrameIndex (NodeElementPtr->next.image.frame);
+ }
+ else /* (scan == BIOLOGICAL_SCAN || scan == ENERGY_SCAN) */
+ {
+ NodeElementPtr->current.image.frame = f;
+ NodeElementPtr->next.image.frame = SetRelFrameIndex (
+ f, NUM_SCANDOT_TRANSITIONS - 1);
+ NodeElementPtr->turn_wait = MAKE_BYTE (4, 4);
+ NodeElementPtr->preprocess_func = object_animation;
+ if (scan == ENERGY_SCAN)
+ {
+ NodeElementPtr->mass_points = MAX_SCROUNGED;
+ DisplayArray[NodeElementPtr->PrimIndex].Object.Stamp.frame =
+ pSolarSysState->PlanetSideFrame[1];
+ }
+ else /* (scan == BIOLOGICAL_SCAN) */
+ {
+ generateBioNode (pSolarSysState, NodeElementPtr,
+ life_init_tab, info.type);
+ }
+ }
+
+ NodeElementPtr->next.location.x =
+ NodeElementPtr->current.location.x << MAG_SHIFT;
+ NodeElementPtr->next.location.y =
+ NodeElementPtr->current.location.y << MAG_SHIFT;
+ UnlockElement (hNodeElement);
+
+ PutElement (hNodeElement);
+ }
+ }
+}
+
+bool
+isNodeRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr)
+{
+ return (planetInfo->ScanRetrieveMask[scanType] & ((DWORD) 1 << nodeNr))
+ != 0;
+}
+
+COUNT
+countNodesRetrieved (PLANET_INFO *planetInfo, BYTE scanType)
+{
+ COUNT count;
+ DWORD mask = planetInfo->ScanRetrieveMask[scanType];
+
+ // count the number of bits set
+ // Caution: 'mask' must be unsigned
+ for (count = 0; mask != 0; mask >>= 1)
+ {
+ if (mask & 1)
+ ++count;
+ }
+ return count;
+}
+
+void
+setNodeRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr)
+{
+ planetInfo->ScanRetrieveMask[scanType] |= ((DWORD) 1 << nodeNr);
+}
+
+void
+setNodeNotRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr)
+{
+ planetInfo->ScanRetrieveMask[scanType] &= ~((DWORD) 1 << nodeNr);
+}
+
diff --git a/src/uqm/planets/scan.h b/src/uqm/planets/scan.h
new file mode 100644
index 0000000..f66fb0e
--- /dev/null
+++ b/src/uqm/planets/scan.h
@@ -0,0 +1,72 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_PLANETS_SCAN_H_
+#define UQM_PLANETS_SCAN_H_
+
+typedef struct scan_desc SCAN_DESC;
+typedef struct scan_block SCAN_BLOCK;
+
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+#include "planets.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct scan_desc
+{
+ POINT start;
+ COUNT start_dot;
+ COUNT num_dots;
+ COUNT dots_per_semi;
+};
+
+struct scan_block
+{
+ POINT *line_base;
+ COUNT num_scans;
+ COUNT num_same_scans;
+ SCAN_DESC *scan_base;
+};
+
+extern void ScanSystem (void);
+
+extern void RepairBackRect (RECT *pRect);
+extern void GeneratePlanetSide (void);
+extern COUNT callGenerateForScanType (const SOLARSYS_STATE *,
+ const PLANET_DESC *world, COUNT node, BYTE scanType, NODE_INFO *);
+// Returns true if the node should be removed from the surface
+extern bool callPickupForScanType (SOLARSYS_STATE *solarSys,
+ PLANET_DESC *world, COUNT node, BYTE scanType);
+
+extern void RedrawSurfaceScan (const POINT *newLoc);
+extern CONTEXT GetScanContext (BOOLEAN *owner);
+extern void DestroyScanContext (void);
+
+bool isNodeRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr);
+COUNT countNodesRetrieved (PLANET_INFO *planetInfo, BYTE scanType);
+void setNodeRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr);
+void setNodeNotRetrieved (PLANET_INFO *planetInfo, BYTE scanType, BYTE nodeNr);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PLANETS_SCAN_H_ */
diff --git a/src/uqm/planets/solarsys.c b/src/uqm/planets/solarsys.c
new file mode 100644
index 0000000..11bd4c0
--- /dev/null
+++ b/src/uqm/planets/solarsys.c
@@ -0,0 +1,2021 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "solarsys.h"
+#include "lander.h"
+#include "../colors.h"
+#include "../controls.h"
+#include "../menustat.h"
+ // for DrawMenuStateStrings()
+#include "../starmap.h"
+#include "../races.h"
+#include "../gamestr.h"
+#include "../gendef.h"
+#include "../globdata.h"
+#include "../sis.h"
+#include "../init.h"
+#include "../shipcont.h"
+#include "../gameopt.h"
+#include "../nameref.h"
+#include "../resinst.h"
+#include "../settings.h"
+#include "../ipdisp.h"
+#include "../grpinfo.h"
+#include "../process.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "../state.h"
+#include "../uqmdebug.h"
+#include "../save.h"
+#include "options.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+#include "libs/misc.h"
+
+
+//#define DEBUG_SOLARSYS
+//#define SMOOTH_SYSTEM_ZOOM 1
+
+#define IP_FRAME_RATE (ONE_SECOND / 30)
+
+static BOOLEAN DoIpFlight (SOLARSYS_STATE *pSS);
+static void DrawSystem (SIZE radius, BOOLEAN IsInnerSystem);
+static FRAME CreateStarBackGround (void);
+static void DrawInnerSystem (void);
+static void DrawOuterSystem (void);
+static void ValidateOrbits (void);
+
+// SolarSysMenu() items
+enum SolarSysMenuMenuItems
+{
+ // XXX: Must match the enum in menustat.h
+ STARMAP = 1,
+ EQUIP_DEVICE,
+ CARGO,
+ ROSTER,
+ GAME_MENU,
+ NAVIGATION,
+};
+
+
+SOLARSYS_STATE *pSolarSysState;
+FRAME SISIPFrame;
+FRAME SunFrame;
+FRAME OrbitalFrame;
+FRAME SpaceJunkFrame;
+COLORMAP OrbitalCMap;
+COLORMAP SunCMap;
+MUSIC_REF SpaceMusic;
+
+SIZE EncounterRace;
+BYTE EncounterGroup;
+ // last encountered group info
+
+static FRAME StarsFrame;
+ // prepared star-field graphic
+static FRAME SolarSysFrame;
+ // saved solar system view graphic
+
+static RECT scaleRect;
+ // system zooms in when the flagship enters this rect
+
+RandomContext *SysGenRNG;
+
+#define DISPLAY_TO_LOC (DISPLAY_FACTOR >> 1)
+
+POINT
+locationToDisplay (POINT pt, SIZE scaleRadius)
+{
+ POINT out;
+
+ out.x = (SIS_SCREEN_WIDTH >> 1)
+ + (long)pt.x * DISPLAY_TO_LOC / scaleRadius;
+ out.y = (SIS_SCREEN_HEIGHT >> 1)
+ + (long)pt.y * DISPLAY_TO_LOC / scaleRadius;
+
+ return out;
+}
+
+POINT
+displayToLocation (POINT pt, SIZE scaleRadius)
+{
+ POINT out;
+
+ out.x = ((long)pt.x - (SIS_SCREEN_WIDTH >> 1))
+ * scaleRadius / DISPLAY_TO_LOC;
+ out.y = ((long)pt.y - (SIS_SCREEN_HEIGHT >> 1))
+ * scaleRadius / DISPLAY_TO_LOC;
+
+ return out;
+}
+
+POINT
+planetOuterLocation (COUNT planetI)
+{
+ SIZE scaleRadius = pSolarSysState->SunDesc[0].radius;
+ return displayToLocation (pSolarSysState->PlanetDesc[planetI].image.origin,
+ scaleRadius);
+}
+
+bool
+worldIsPlanet (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world)
+{
+ return world->pPrevDesc == solarSys->SunDesc;
+}
+
+bool
+worldIsMoon (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world)
+{
+ return world->pPrevDesc != solarSys->SunDesc;
+}
+
+// Returns the planet index of the world. If the world is a moon, then
+// this is the index of the planet it is orbiting.
+COUNT
+planetIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world)
+{
+ const PLANET_DESC *planet = worldIsPlanet (solarSys, world) ?
+ world : world->pPrevDesc;
+ return planet - solarSys->PlanetDesc;
+}
+
+COUNT
+moonIndex (const SOLARSYS_STATE *solarSys, const PLANET_DESC *moon)
+{
+ assert (!worldIsPlanet (solarSys, moon));
+ return moon - solarSys->MoonDesc;
+}
+
+// Test whether 'world' is the planetI-th planet, and if moonI is not
+// set to MATCH_PLANET, also whether 'world' is the moonI-th moon.
+bool
+matchWorld (const SOLARSYS_STATE *solarSys, const PLANET_DESC *world,
+ BYTE planetI, BYTE moonI)
+{
+ // Check whether we have the right planet.
+ if (planetIndex (solarSys, world) != planetI)
+ return false;
+
+ if (moonI == MATCH_PLANET)
+ {
+ // Only test whether we are at the planet.
+ if (!worldIsPlanet (solarSys, world))
+ return false;
+ }
+ else
+ {
+ // Test whether the moon matches too
+ if (!worldIsMoon (solarSys, world))
+ return false;
+
+ if (moonIndex (solarSys, world) != moonI)
+ return false;
+ }
+
+ return true;
+}
+
+bool
+playerInSolarSystem (void)
+{
+ return pSolarSysState != NULL;
+}
+
+bool
+playerInPlanetOrbit (void)
+{
+ return playerInSolarSystem () && pSolarSysState->InOrbit;
+}
+
+bool
+playerInInnerSystem (void)
+{
+ assert (playerInSolarSystem ());
+ assert (pSolarSysState->pBaseDesc == pSolarSysState->PlanetDesc
+ || pSolarSysState->pBaseDesc == pSolarSysState->MoonDesc);
+ return pSolarSysState->pBaseDesc != pSolarSysState->PlanetDesc;
+}
+
+// Sets the SysGenRNG to the required state first.
+static void
+GenerateMoons (SOLARSYS_STATE *system, PLANET_DESC *planet)
+{
+ COUNT i;
+ COUNT facing;
+ PLANET_DESC *pMoonDesc;
+
+ RandomContext_SeedRandom (SysGenRNG, planet->rand_seed);
+
+ (*system->genFuncs->generateName) (system, planet);
+ (*system->genFuncs->generateMoons) (system, planet);
+
+ facing = NORMALIZE_FACING (ANGLE_TO_FACING (
+ ARCTAN (planet->location.x, planet->location.y)));
+ for (i = 0, pMoonDesc = &system->MoonDesc[0];
+ i < MAX_MOONS; ++i, ++pMoonDesc)
+ {
+ pMoonDesc->pPrevDesc = planet;
+ if (i >= planet->NumPlanets)
+ continue;
+
+ pMoonDesc->temp_color = planet->temp_color;
+ }
+}
+
+void
+FreeIPData (void)
+{
+ DestroyDrawable (ReleaseDrawable (SISIPFrame));
+ SISIPFrame = 0;
+ DestroyDrawable (ReleaseDrawable (SunFrame));
+ SunFrame = 0;
+ DestroyColorMap (ReleaseColorMap (SunCMap));
+ SunCMap = 0;
+ DestroyColorMap (ReleaseColorMap (OrbitalCMap));
+ OrbitalCMap = 0;
+ DestroyDrawable (ReleaseDrawable (OrbitalFrame));
+ OrbitalFrame = 0;
+ DestroyDrawable (ReleaseDrawable (SpaceJunkFrame));
+ SpaceJunkFrame = 0;
+ DestroyMusic (SpaceMusic);
+ SpaceMusic = 0;
+
+ RandomContext_Delete (SysGenRNG);
+ SysGenRNG = NULL;
+}
+
+void
+LoadIPData (void)
+{
+ if (SpaceJunkFrame == 0)
+ {
+ SpaceJunkFrame = CaptureDrawable (
+ LoadGraphic (IPBKGND_MASK_PMAP_ANIM));
+ SISIPFrame = CaptureDrawable (LoadGraphic (SISIP_MASK_PMAP_ANIM));
+
+ OrbitalCMap = CaptureColorMap (LoadColorMap (ORBPLAN_COLOR_MAP));
+ OrbitalFrame = CaptureDrawable (
+ LoadGraphic (ORBPLAN_MASK_PMAP_ANIM));
+ SunCMap = CaptureColorMap (LoadColorMap (IPSUN_COLOR_MAP));
+ SunFrame = CaptureDrawable (LoadGraphic (SUN_MASK_PMAP_ANIM));
+
+ SpaceMusic = LoadMusic (IP_MUSIC);
+ }
+
+ if (!SysGenRNG)
+ {
+ SysGenRNG = RandomContext_New ();
+ }
+}
+
+
+static void
+sortPlanetPositions (void)
+{
+ COUNT i;
+ SIZE sort_array[MAX_PLANETS + 1];
+
+ // When this part is done, sort_array will contain the indices to
+ // all planets, sorted on their y position.
+ // The sun itself, which has its data located at
+ // pSolarSysState->PlanetDesc[-1], is included in this array.
+ // Very ugly stuff, but it's correct.
+
+ // Initialise sort_array.
+ for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i)
+ sort_array[i] = i - 1;
+
+ // Sort sort_array, based on the positions of the planets/sun.
+ for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i)
+ {
+ COUNT j;
+
+ for (j = pSolarSysState->SunDesc[0].NumPlanets; j > i; --j)
+ {
+ SIZE real_i, real_j;
+
+ real_i = sort_array[i];
+ real_j = sort_array[j];
+ if (pSolarSysState->PlanetDesc[real_i].image.origin.y >
+ pSolarSysState->PlanetDesc[real_j].image.origin.y)
+ {
+ SIZE temp;
+
+ temp = sort_array[i];
+ sort_array[i] = sort_array[j];
+ sort_array[j] = temp;
+ }
+ }
+ }
+
+ // Put the results of the sorting in the solar system structure.
+ pSolarSysState->FirstPlanetIndex = sort_array[0];
+ pSolarSysState->LastPlanetIndex =
+ sort_array[pSolarSysState->SunDesc[0].NumPlanets];
+ for (i = 0; i <= pSolarSysState->SunDesc[0].NumPlanets; ++i) {
+ PLANET_DESC *planet = &pSolarSysState->PlanetDesc[sort_array[i]];
+ planet->NextIndex = sort_array[i + 1];
+ }
+}
+
+static void
+initSolarSysSISCharacteristics (void)
+{
+ BYTE i;
+ BYTE num_thrusters;
+
+ num_thrusters = 0;
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (DriveSlots[i]) == FUSION_THRUSTER)
+ ++num_thrusters;
+ }
+ pSolarSysState->max_ship_speed = (BYTE)(
+ (num_thrusters + 5) * IP_SHIP_THRUST_INCREMENT);
+
+ pSolarSysState->turn_wait = IP_SHIP_TURN_WAIT;
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (JetSlots[i]) == TURNING_JETS)
+ pSolarSysState->turn_wait -= IP_SHIP_TURN_DECREMENT;
+ }
+}
+
+DWORD
+GetRandomSeedForStar (const STAR_DESC *star)
+{
+ return MAKE_DWORD (star->star_pt.x, star->star_pt.y);
+}
+
+// Returns an orbital PLANET_DESC when player is in orbit
+static PLANET_DESC *
+LoadSolarSys (void)
+{
+ COUNT i;
+ PLANET_DESC *orbital = NULL;
+ PLANET_DESC *pCurDesc;
+#define NUM_TEMP_RANGES 5
+ static const Color temp_color_array[NUM_TEMP_RANGES] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x0E), 0x54),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x06, 0x08), 0x62),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x0B, 0x00), 0x6D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x08, 0x00), 0x75),
+ };
+
+ RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (CurStarDescPtr));
+
+ SunFrame = SetAbsFrameIndex (SunFrame, STAR_TYPE (CurStarDescPtr->Type));
+
+ pCurDesc = &pSolarSysState->SunDesc[0];
+ pCurDesc->pPrevDesc = 0;
+ pCurDesc->rand_seed = RandomContext_Random (SysGenRNG);
+
+ pCurDesc->data_index = STAR_TYPE (CurStarDescPtr->Type);
+ pCurDesc->location.x = 0;
+ pCurDesc->location.y = 0;
+ pCurDesc->image.origin = pCurDesc->location;
+ pCurDesc->image.frame = SunFrame;
+
+ (*pSolarSysState->genFuncs->generatePlanets) (pSolarSysState);
+ if (GET_GAME_STATE (PLANETARY_CHANGE))
+ {
+ PutPlanetInfo ();
+ SET_GAME_STATE (PLANETARY_CHANGE, 0);
+ }
+
+ for (i = 0, pCurDesc = pSolarSysState->PlanetDesc;
+ i < MAX_PLANETS; ++i, ++pCurDesc)
+ {
+ pCurDesc->pPrevDesc = &pSolarSysState->SunDesc[0];
+ pCurDesc->image.origin = pCurDesc->location;
+ if (i >= pSolarSysState->SunDesc[0].NumPlanets)
+ {
+ pCurDesc->image.frame = 0;
+ }
+ else
+ {
+ COUNT index;
+ SYSTEM_INFO SysInfo;
+
+ DoPlanetaryAnalysis (&SysInfo, pCurDesc);
+ index = (SysInfo.PlanetInfo.SurfaceTemperature + 250) / 100;
+ if (index >= NUM_TEMP_RANGES)
+ index = NUM_TEMP_RANGES - 1;
+ pCurDesc->temp_color = temp_color_array[index];
+ }
+ }
+
+ sortPlanetPositions ();
+
+ if (!GLOBAL (ip_planet))
+ { // Outer system
+ pSolarSysState->pBaseDesc = pSolarSysState->PlanetDesc;
+ pSolarSysState->pOrbitalDesc = NULL;
+ }
+ else
+ { // Inner system
+ pSolarSysState->SunDesc[0].location = GLOBAL (ip_location);
+ GLOBAL (ip_location) = displayToLocation (
+ GLOBAL (ShipStamp.origin), MAX_ZOOM_RADIUS);
+
+ i = GLOBAL (ip_planet) - 1;
+ pSolarSysState->pOrbitalDesc = &pSolarSysState->PlanetDesc[i];
+ GenerateMoons (pSolarSysState, pSolarSysState->pOrbitalDesc);
+ pSolarSysState->pBaseDesc = pSolarSysState->MoonDesc;
+
+ SET_GAME_STATE (PLANETARY_LANDING, 0);
+ }
+
+ initSolarSysSISCharacteristics ();
+
+ if (GLOBAL (in_orbit))
+ { // Only when loading a game into orbital
+ i = GLOBAL (in_orbit) - 1;
+ if (i == 0)
+ { // Orbiting the planet itself
+ orbital = pSolarSysState->pBaseDesc->pPrevDesc;
+ }
+ else
+ { // Orbiting a moon
+ // -1 because planet itself is 1, and moons have to be 1-based
+ i -= 1;
+ orbital = &pSolarSysState->MoonDesc[i];
+ }
+ GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location;
+ GLOBAL (in_orbit) = 0;
+ }
+ else
+ {
+ i = GLOBAL (ShipFacing);
+ // XXX: Solar system reentry test depends on ShipFacing != 0
+ if (i == 0)
+ ++i;
+
+ GLOBAL (ShipStamp.frame) = SetAbsFrameIndex (SISIPFrame, i - 1);
+ }
+
+ return orbital;
+}
+
+static void
+saveNonOrbitalLocation (void)
+{
+ // XXX: Solar system reentry test depends on ShipFacing != 0
+ GLOBAL (ShipFacing) = GetFrameIndex (GLOBAL (ShipStamp.frame)) + 1;
+ GLOBAL (in_orbit) = 0;
+ if (!playerInInnerSystem ())
+ {
+ GLOBAL (ip_planet) = 0;
+ }
+ else
+ {
+ // ip_planet is 1-based because code tests for ip_planet!=0
+ GLOBAL (ip_planet) = 1 + planetIndex (pSolarSysState,
+ pSolarSysState->pOrbitalDesc);
+ GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location;
+ }
+}
+
+static void
+FreeSolarSys (void)
+{
+ if (pSolarSysState->InIpFlight)
+ {
+ pSolarSysState->InIpFlight = FALSE;
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ saveNonOrbitalLocation ();
+ }
+
+ DestroyDrawable (ReleaseDrawable (SolarSysFrame));
+ SolarSysFrame = NULL;
+
+ StopMusic ();
+
+// FreeIPData ();
+}
+
+static FRAME
+getCollisionFrame (PLANET_DESC *planet, COUNT WaitPlanet)
+{
+ if (pSolarSysState->WaitIntersect != (COUNT)~0
+ && pSolarSysState->WaitIntersect != WaitPlanet)
+ { // New collisions are with a single point (center of planet)
+ return DecFrameIndex (stars_in_space);
+ }
+ else
+ { // Existing collisions are cleared only once the ship does not
+ // intersect anymore with a full planet image
+ return planet->image.frame;
+ }
+}
+
+// Returns the planet with which the flagship is colliding
+static PLANET_DESC *
+CheckIntersect (void)
+{
+ COUNT i;
+ PLANET_DESC *pCurDesc;
+ INTERSECT_CONTROL ShipIntersect, PlanetIntersect;
+ COUNT NewWaitPlanet;
+ BYTE PlanetOffset, MoonOffset;
+
+ // Check collisions with the system center object
+ // This may be the planet in inner view, or the sun
+ pCurDesc = pSolarSysState->pBaseDesc->pPrevDesc;
+ PlanetOffset = pCurDesc - pSolarSysState->PlanetDesc + 1;
+ MoonOffset = 1; // the planet itself
+
+ ShipIntersect.IntersectStamp.origin = GLOBAL (ShipStamp.origin);
+ ShipIntersect.EndPoint = ShipIntersect.IntersectStamp.origin;
+ ShipIntersect.IntersectStamp.frame = GLOBAL (ShipStamp.frame);
+
+ PlanetIntersect.IntersectStamp.origin.x = SIS_SCREEN_WIDTH >> 1;
+ PlanetIntersect.IntersectStamp.origin.y = SIS_SCREEN_HEIGHT >> 1;
+ PlanetIntersect.EndPoint = PlanetIntersect.IntersectStamp.origin;
+
+ PlanetIntersect.IntersectStamp.frame = getCollisionFrame (pCurDesc,
+ MAKE_WORD (PlanetOffset, MoonOffset));
+
+ // Start with no collisions
+ NewWaitPlanet = 0;
+
+ if (pCurDesc != pSolarSysState->SunDesc /* can't intersect with sun */
+ && DrawablesIntersect (&ShipIntersect,
+ &PlanetIntersect, MAX_TIME_VALUE))
+ {
+#ifdef DEBUG_SOLARSYS
+ log_add (log_Debug, "0: Planet %d, Moon %d", PlanetOffset,
+ MoonOffset);
+#endif /* DEBUG_SOLARSYS */
+ NewWaitPlanet = MAKE_WORD (PlanetOffset, MoonOffset);
+ if (pSolarSysState->WaitIntersect != (COUNT)~0
+ && pSolarSysState->WaitIntersect != NewWaitPlanet)
+ {
+ pSolarSysState->WaitIntersect = NewWaitPlanet;
+#ifdef DEBUG_SOLARSYS
+ log_add (log_Debug, "Star index = %d, Planet index = %d, <%d, %d>",
+ CurStarDescPtr - star_array,
+ pCurDesc - pSolarSysState->PlanetDesc,
+ pSolarSysState->SunDesc[0].location.x,
+ pSolarSysState->SunDesc[0].location.y);
+#endif /* DEBUG_SOLARSYS */
+ return pCurDesc;
+ }
+ }
+
+ for (i = pCurDesc->NumPlanets,
+ pCurDesc = pSolarSysState->pBaseDesc; i; --i, ++pCurDesc)
+ {
+ PlanetIntersect.IntersectStamp.origin = pCurDesc->image.origin;
+ PlanetIntersect.EndPoint = PlanetIntersect.IntersectStamp.origin;
+ if (playerInInnerSystem ())
+ {
+ PlanetOffset = pCurDesc->pPrevDesc -
+ pSolarSysState->PlanetDesc;
+ MoonOffset = pCurDesc - pSolarSysState->MoonDesc + 2;
+ }
+ else
+ {
+ PlanetOffset = pCurDesc - pSolarSysState->PlanetDesc;
+ MoonOffset = 0;
+ }
+ ++PlanetOffset;
+ PlanetIntersect.IntersectStamp.frame = getCollisionFrame (pCurDesc,
+ MAKE_WORD (PlanetOffset, MoonOffset));
+
+ if (DrawablesIntersect (&ShipIntersect,
+ &PlanetIntersect, MAX_TIME_VALUE))
+ {
+#ifdef DEBUG_SOLARSYS
+ log_add (log_Debug, "1: Planet %d, Moon %d", PlanetOffset,
+ MoonOffset);
+#endif /* DEBUG_SOLARSYS */
+ NewWaitPlanet = MAKE_WORD (PlanetOffset, MoonOffset);
+
+ if (pSolarSysState->WaitIntersect == (COUNT)~0)
+ { // All collisions disallowed, but the ship is still colliding
+ // with something. Collisions will remain disabled.
+ break;
+ }
+ else if (pSolarSysState->WaitIntersect == NewWaitPlanet)
+ { // Existing and continued collision -- ignore
+ continue;
+ }
+
+ // Collision with a new planet/moon. This may cause a transition
+ // to an inner system or start an orbital view.
+ pSolarSysState->WaitIntersect = NewWaitPlanet;
+ return pCurDesc;
+ }
+ }
+
+ // This records the planet/moon with which the ship just collided
+ // It may be a previously existing collision also (the value won't change)
+ // If all collisions were disabled, this will reenable then once the ship
+ // stops colliding with any planets
+ if (pSolarSysState->WaitIntersect != (COUNT)~0 || NewWaitPlanet == 0)
+ pSolarSysState->WaitIntersect = NewWaitPlanet;
+
+ return NULL;
+}
+
+static void
+GetOrbitRect (RECT *pRect, COORD dx, COORD dy, SIZE radius,
+ int xnumer, int ynumer, int denom)
+{
+ pRect->corner.x = (SIS_SCREEN_WIDTH >> 1) + (long)-dx * xnumer / denom;
+ pRect->corner.y = (SIS_SCREEN_HEIGHT >> 1) + (long)-dy * ynumer / denom;
+ pRect->extent.width = (long)radius * (xnumer << 1) / denom;
+ pRect->extent.height = pRect->extent.width >> 1;
+}
+
+static void
+GetPlanetOrbitRect (RECT *r, PLANET_DESC *planet, int sizeNumer,
+ int dyNumer, int denom)
+{
+ COORD dx, dy;
+
+ dx = planet->radius;
+ dy = planet->radius;
+ if (sizeNumer > DISPLAY_FACTOR)
+ {
+ dx = dx + planet->location.x;
+ dy = (dy + planet->location.y) << 1;
+ }
+ GetOrbitRect (r, dx, dy, planet->radius, sizeNumer, dyNumer, denom);
+}
+
+static void
+ValidateOrbit (PLANET_DESC *planet, int sizeNumer, int dyNumer, int denom)
+{
+ COUNT index;
+
+ if (sizeNumer <= DISPLAY_FACTOR)
+ { // All planets in outer view, and moons in inner
+ RECT r;
+
+ GetPlanetOrbitRect (&r, planet, sizeNumer, dyNumer, denom);
+
+ // Calculate the location of the planet's image
+ r.corner.x += (r.extent.width >> 1);
+ r.corner.y += (r.extent.height >> 1);
+ r.corner.x += (long)planet->location.x * sizeNumer / denom;
+ // Ellipse function always has coefficients a^2 = 2 * b^2
+ r.corner.y += (long)planet->location.y * (sizeNumer / 2) / denom;
+
+ planet->image.origin = r.corner;
+ }
+
+ // Calculate the size and lighting angle of planet's image and
+ // set the image that will be drawn
+ index = planet->data_index & ~WORLD_TYPE_SPECIAL;
+ if (index < NUMBER_OF_PLANET_TYPES)
+ { // The world is a normal planetary body (planet or moon)
+ BYTE Type;
+ COUNT Size;
+ COUNT angle;
+
+ Type = PlanData[index].Type;
+ Size = PLANSIZE (Type);
+ if (sizeNumer > DISPLAY_FACTOR)
+ {
+ Size += 3;
+ }
+ else if (worldIsMoon (pSolarSysState, planet))
+ {
+ Size += 2;
+ }
+ else if (denom <= (MAX_ZOOM_RADIUS >> 2))
+ {
+ ++Size;
+ if (denom == MIN_ZOOM_RADIUS)
+ ++Size;
+ }
+
+ if (worldIsPlanet (pSolarSysState, planet))
+ { // Planet
+ angle = ARCTAN (planet->location.x, planet->location.y);
+ }
+ else
+ { // Moon
+ angle = ARCTAN (planet->pPrevDesc->location.x,
+ planet->pPrevDesc->location.y);
+ }
+ planet->image.frame = SetAbsFrameIndex (OrbitalFrame,
+ (Size << FACING_SHIFT) + NORMALIZE_FACING (
+ ANGLE_TO_FACING (angle)));
+ }
+ else if (planet->data_index == HIERARCHY_STARBASE)
+ {
+ planet->image.frame = SetAbsFrameIndex (SpaceJunkFrame, 16);
+ }
+ else if (planet->data_index == SA_MATRA)
+ {
+ planet->image.frame = SetAbsFrameIndex (SpaceJunkFrame, 19);
+ }
+}
+
+static void
+DrawOrbit (PLANET_DESC *planet, int sizeNumer, int dyNumer, int denom)
+{
+ RECT r;
+
+ GetPlanetOrbitRect (&r, planet, sizeNumer, dyNumer, denom);
+
+ SetContextForeGroundColor (planet->temp_color);
+ DrawOval (&r, 1);
+}
+
+static SIZE
+FindRadius (POINT shipLoc, SIZE fromRadius)
+{
+ SIZE nextRadius;
+ POINT displayLoc;
+
+ do
+ {
+ fromRadius >>= 1;
+ if (fromRadius > MIN_ZOOM_RADIUS)
+ nextRadius = fromRadius >> 1;
+ else
+ nextRadius = 0; // scaleRect will be nul
+
+ GetOrbitRect (&scaleRect, nextRadius, nextRadius, nextRadius,
+ DISPLAY_FACTOR, DISPLAY_FACTOR >> 2, fromRadius);
+ displayLoc = locationToDisplay (shipLoc, fromRadius);
+
+ } while (pointWithinRect (scaleRect, displayLoc));
+
+ return fromRadius;
+}
+
+static UWORD
+flagship_inertial_thrust (COUNT CurrentAngle)
+{
+ BYTE max_speed;
+ SIZE cur_delta_x, cur_delta_y;
+ COUNT TravelAngle;
+ VELOCITY_DESC *VelocityPtr;
+
+ max_speed = pSolarSysState->max_ship_speed;
+ VelocityPtr = &GLOBAL (velocity);
+ GetCurrentVelocityComponents (VelocityPtr, &cur_delta_x, &cur_delta_y);
+ TravelAngle = GetVelocityTravelAngle (VelocityPtr);
+ if (TravelAngle == CurrentAngle
+ && cur_delta_x == COSINE (CurrentAngle, max_speed)
+ && cur_delta_y == SINE (CurrentAngle, max_speed))
+ return (SHIP_AT_MAX_SPEED);
+ else
+ {
+ SIZE delta_x, delta_y;
+ DWORD desired_speed;
+
+ delta_x = cur_delta_x
+ + COSINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT);
+ delta_y = cur_delta_y
+ + SINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT);
+ desired_speed = (DWORD) ((long) delta_x * delta_x)
+ + (DWORD) ((long) delta_y * delta_y);
+ if (desired_speed <= (DWORD) ((UWORD) max_speed * max_speed))
+ SetVelocityComponents (VelocityPtr, delta_x, delta_y);
+ else if (TravelAngle == CurrentAngle)
+ {
+ SetVelocityComponents (VelocityPtr,
+ COSINE (CurrentAngle, max_speed),
+ SINE (CurrentAngle, max_speed));
+ return (SHIP_AT_MAX_SPEED);
+ }
+ else
+ {
+ VELOCITY_DESC v;
+
+ v = *VelocityPtr;
+
+ DeltaVelocityComponents (&v,
+ COSINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT >> 1)
+ - COSINE (TravelAngle, IP_SHIP_THRUST_INCREMENT),
+ SINE (CurrentAngle, IP_SHIP_THRUST_INCREMENT >> 1)
+ - SINE (TravelAngle, IP_SHIP_THRUST_INCREMENT));
+ GetCurrentVelocityComponents (&v, &cur_delta_x, &cur_delta_y);
+ desired_speed =
+ (DWORD) ((long) cur_delta_x * cur_delta_x)
+ + (DWORD) ((long) cur_delta_y * cur_delta_y);
+ if (desired_speed > (DWORD) ((UWORD) max_speed * max_speed))
+ {
+ SetVelocityComponents (VelocityPtr,
+ COSINE (CurrentAngle, max_speed),
+ SINE (CurrentAngle, max_speed));
+ return (SHIP_AT_MAX_SPEED);
+ }
+
+ *VelocityPtr = v;
+ }
+
+ return 0;
+ }
+}
+
+static void
+ProcessShipControls (void)
+{
+ COUNT index;
+ SIZE delta_x, delta_y;
+
+ if (CurrentInputState.key[PlayerControls[0]][KEY_UP])
+ delta_y = -1;
+ else
+ delta_y = 0;
+
+ delta_x = 0;
+ if (CurrentInputState.key[PlayerControls[0]][KEY_LEFT])
+ delta_x -= 1;
+ if (CurrentInputState.key[PlayerControls[0]][KEY_RIGHT])
+ delta_x += 1;
+
+ if (delta_x || delta_y < 0)
+ {
+ GLOBAL (autopilot.x) = ~0;
+ GLOBAL (autopilot.y) = ~0;
+ }
+ else if (GLOBAL (autopilot.x) != ~0 && GLOBAL (autopilot.y) != ~0)
+ delta_y = -1;
+ else
+ delta_y = 0;
+
+ index = GetFrameIndex (GLOBAL (ShipStamp.frame));
+ if (pSolarSysState->turn_counter)
+ --pSolarSysState->turn_counter;
+ else if (delta_x)
+ {
+ if (delta_x < 0)
+ index = NORMALIZE_FACING (index - 1);
+ else
+ index = NORMALIZE_FACING (index + 1);
+
+ GLOBAL (ShipStamp.frame) =
+ SetAbsFrameIndex (GLOBAL (ShipStamp.frame), index);
+
+ pSolarSysState->turn_counter = pSolarSysState->turn_wait;
+ }
+ if (pSolarSysState->thrust_counter)
+ --pSolarSysState->thrust_counter;
+ else if (delta_y < 0)
+ {
+#define THRUST_WAIT 1
+ flagship_inertial_thrust (FACING_TO_ANGLE (index));
+
+ pSolarSysState->thrust_counter = THRUST_WAIT;
+ }
+}
+
+static void
+enterInnerSystem (PLANET_DESC *planet)
+{
+#define INNER_ENTRY_DISTANCE (MIN_MOON_RADIUS + ((MAX_MOONS - 1) \
+ * MOON_DELTA) + (MOON_DELTA / 4))
+ COUNT angle;
+
+ // Calculate the inner system entry location and facing
+ angle = FACING_TO_ANGLE (GetFrameIndex (GLOBAL (ShipStamp.frame)))
+ + HALF_CIRCLE;
+ GLOBAL (ShipStamp.origin.x) = (SIS_SCREEN_WIDTH >> 1)
+ + COSINE (angle, INNER_ENTRY_DISTANCE);
+ GLOBAL (ShipStamp.origin.y) = (SIS_SCREEN_HEIGHT >> 1)
+ + SINE (angle, INNER_ENTRY_DISTANCE);
+ if (GLOBAL (ShipStamp.origin.y) < 0)
+ GLOBAL (ShipStamp.origin.y) = 1;
+ else if (GLOBAL (ShipStamp.origin.y) >= SIS_SCREEN_HEIGHT)
+ GLOBAL (ShipStamp.origin.y) =
+ (SIS_SCREEN_HEIGHT - 1) - 1;
+
+ GLOBAL (ip_location) = displayToLocation (
+ GLOBAL (ShipStamp.origin), MAX_ZOOM_RADIUS);
+
+ pSolarSysState->SunDesc[0].location =
+ planetOuterLocation (planetIndex (pSolarSysState, planet));
+ ZeroVelocityComponents (&GLOBAL (velocity));
+
+ GenerateMoons (pSolarSysState, planet);
+ pSolarSysState->pBaseDesc = pSolarSysState->MoonDesc;
+ pSolarSysState->pOrbitalDesc = planet;
+}
+
+static void
+leaveInnerSystem (PLANET_DESC *planet)
+{
+ COUNT outerPlanetWait;
+
+ pSolarSysState->pBaseDesc = pSolarSysState->PlanetDesc;
+ pSolarSysState->pOrbitalDesc = NULL;
+
+ outerPlanetWait = MAKE_WORD (planet - pSolarSysState->PlanetDesc + 1, 0);
+ GLOBAL (ip_location) = pSolarSysState->SunDesc[0].location;
+ XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE);
+ ZeroVelocityComponents (&GLOBAL (velocity));
+
+ // Now the ship is in outer system (as per game logic)
+
+ pSolarSysState->WaitIntersect = outerPlanetWait;
+ // See if we also intersect with another planet, and if we do,
+ // disable collisions comletely until we stop intersecting
+ // with any planet at all.
+ CheckIntersect ();
+ if (pSolarSysState->WaitIntersect != outerPlanetWait)
+ pSolarSysState->WaitIntersect = (COUNT)~0;
+}
+
+static void
+enterOrbital (PLANET_DESC *planet)
+{
+ ZeroVelocityComponents (&GLOBAL (velocity));
+ pSolarSysState->pOrbitalDesc = planet;
+ pSolarSysState->InOrbit = TRUE;
+}
+
+static BOOLEAN
+CheckShipLocation (SIZE *newRadius)
+{
+ SIZE radius;
+
+ radius = pSolarSysState->SunDesc[0].radius;
+ *newRadius = pSolarSysState->SunDesc[0].radius;
+
+ if (GLOBAL (ShipStamp.origin.x) < 0
+ || GLOBAL (ShipStamp.origin.x) >= SIS_SCREEN_WIDTH
+ || GLOBAL (ShipStamp.origin.y) < 0
+ || GLOBAL (ShipStamp.origin.y) >= SIS_SCREEN_HEIGHT)
+ {
+ // The ship leaves the screen.
+ if (!playerInInnerSystem ())
+ { // Outer zoom-out transition
+ if (radius == MAX_ZOOM_RADIUS)
+ {
+ // The ship leaves IP.
+ GLOBAL (CurrentActivity) |= END_INTERPLANETARY;
+ return FALSE; // no location change
+ }
+
+ *newRadius = FindRadius (GLOBAL (ip_location),
+ MAX_ZOOM_RADIUS << 1);
+ }
+ else
+ {
+ leaveInnerSystem (pSolarSysState->pOrbitalDesc);
+ }
+
+ return TRUE;
+ }
+
+ if (!playerInInnerSystem ()
+ && pointWithinRect (scaleRect, GLOBAL (ShipStamp.origin)))
+ { // Outer zoom-in transition
+ *newRadius = FindRadius (GLOBAL (ip_location), radius);
+ return TRUE;
+ }
+
+ if (GLOBAL (autopilot.x) == ~0 && GLOBAL (autopilot.y) == ~0)
+ { // Not on autopilot -- may collide with a planet
+ PLANET_DESC *planet = CheckIntersect ();
+ if (planet)
+ { // Collision with a planet
+ if (playerInInnerSystem ())
+ { // Entering planet orbit (scans, etc.)
+ enterOrbital (planet);
+ return FALSE; // no location change
+ }
+ else
+ { // Transition to inner system
+ enterInnerSystem (planet);
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE; // no location change
+}
+
+static void
+DrawSystemTransition (BOOLEAN inner)
+{
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+ if (inner)
+ DrawInnerSystem ();
+ else
+ DrawOuterSystem ();
+ RedrawQueue (FALSE);
+ ScreenTransition (3, NULL);
+ UnbatchGraphics ();
+}
+
+static void
+TransitionSystemIn (void)
+{
+ SetContext (SpaceContext);
+ DrawSystemTransition (playerInInnerSystem ());
+}
+
+static void
+ScaleSystem (SIZE new_radius)
+{
+#ifdef SMOOTH_SYSTEM_ZOOM
+ // XXX: This appears to have been an attempt to zoom the system view
+ // in a different way. This code zooms gradually instead of
+ // doing a crossfade from one zoom level to the other.
+ // TODO: Do not loop here, and instead increment the zoom level
+ // in IP_frame() with a function drawing the new zoom. The ship
+ // controls are not handled in the loop, and the flagship
+ // can collide with a group while zooming, and that is not handled
+ // 100% correctly.
+#define NUM_STEPS 10
+ COUNT i;
+ SIZE old_radius;
+ SIZE d, step;
+
+ old_radius = pSolarSysState->SunDesc[0].radius;
+
+ assert (old_radius != 0);
+ assert (old_radius != new_radius);
+
+ d = new_radius - old_radius;
+ step = d / NUM_STEPS;
+
+ for (i = 0; i < NUM_STEPS - 1; ++i)
+ {
+ pSolarSysState->SunDesc[0].radius += step;
+ XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE);
+
+ BatchGraphics ();
+ DrawOuterSystem ();
+ RedrawQueue (FALSE);
+ UnbatchGraphics ();
+
+ SleepThread (ONE_SECOND / 30);
+ }
+
+ // Final zoom step
+ pSolarSysState->SunDesc[0].radius = new_radius;
+ XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE);
+
+ BatchGraphics ();
+ DrawOuterSystem ();
+ RedrawQueue (FALSE);
+ UnbatchGraphics ();
+
+#else // !SMOOTH_SYSTEM_ZOOM
+ RECT r;
+
+ pSolarSysState->SunDesc[0].radius = new_radius;
+ XFormIPLoc (&GLOBAL (ip_location), &GLOBAL (ShipStamp.origin), TRUE);
+
+ GetContextClipRect (&r);
+ SetTransitionSource (&r);
+ BatchGraphics ();
+ DrawOuterSystem ();
+ RedrawQueue (FALSE);
+ ScreenTransition (3, &r);
+ UnbatchGraphics ();
+#endif // SMOOTH_SYSTEM_ZOOM
+}
+
+static void
+RestoreSystemView (void)
+{
+ STAMP s;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SolarSysFrame;
+ DrawStamp (&s);
+}
+
+// Normally called by DoIpFlight() to process a frame
+static void
+IP_frame (void)
+{
+ BOOLEAN locChange;
+ SIZE newRadius;
+
+ SetContext (SpaceContext);
+
+ GameClockTick ();
+ ProcessShipControls ();
+
+ locChange = CheckShipLocation (&newRadius);
+ if (locChange)
+ {
+ if (playerInInnerSystem ())
+ { // Entering inner system
+ DrawSystemTransition (TRUE);
+ }
+ else if (pSolarSysState->SunDesc[0].radius == newRadius)
+ { // Leaving inner system to outer
+ DrawSystemTransition (FALSE);
+ }
+ else
+ { // Zooming outer system
+ ScaleSystem (newRadius);
+ }
+ }
+ else
+ { // Just flying around, minding own business..
+ BatchGraphics ();
+ RestoreSystemView ();
+ RedrawQueue (FALSE);
+ DrawAutoPilotMessage (FALSE);
+ UnbatchGraphics ();
+ }
+
+}
+
+static BOOLEAN
+CheckZoomLevel (void)
+{
+ BOOLEAN InnerSystem;
+ POINT shipLoc;
+
+ InnerSystem = playerInInnerSystem ();
+ if (InnerSystem)
+ shipLoc = pSolarSysState->SunDesc[0].location;
+ else
+ shipLoc = GLOBAL (ip_location);
+
+ pSolarSysState->SunDesc[0].radius = FindRadius (shipLoc,
+ MAX_ZOOM_RADIUS << 1);
+ if (!InnerSystem)
+ { // Update ship stamp since the radius probably changed
+ XFormIPLoc (&shipLoc, &GLOBAL (ShipStamp.origin), TRUE);
+ }
+
+ return InnerSystem;
+}
+
+static void
+ValidateOrbits (void)
+{
+ COUNT i;
+ PLANET_DESC *planet;
+
+ for (i = pSolarSysState->SunDesc[0].NumPlanets,
+ planet = &pSolarSysState->PlanetDesc[0]; i; --i, ++planet)
+ {
+ ValidateOrbit (planet, DISPLAY_FACTOR, DISPLAY_FACTOR / 4,
+ pSolarSysState->SunDesc[0].radius);
+ }
+}
+
+static void
+ValidateInnerOrbits (void)
+{
+ COUNT i;
+ PLANET_DESC *planet;
+
+ assert (playerInInnerSystem ());
+
+ planet = pSolarSysState->pOrbitalDesc;
+ ValidateOrbit (planet, DISPLAY_FACTOR * 4, DISPLAY_FACTOR,
+ planet->radius);
+
+ for (i = 0; i < planet->NumPlanets; ++i)
+ {
+ PLANET_DESC *moon = &pSolarSysState->MoonDesc[i];
+ ValidateOrbit (moon, 2, 1, 2);
+ }
+}
+
+static void
+DrawInnerSystem (void)
+{
+ ValidateInnerOrbits ();
+ DrawSystem (pSolarSysState->pOrbitalDesc->radius, TRUE);
+ DrawSISTitle (GLOBAL_SIS (PlanetName));
+}
+
+static void
+DrawOuterSystem (void)
+{
+ ValidateOrbits ();
+ DrawSystem (pSolarSysState->SunDesc[0].radius, FALSE);
+ DrawHyperCoords (CurStarDescPtr->star_pt);
+}
+
+static void
+ResetSolarSys (void)
+{
+ // Originally there was a flash_task test here, however, I found no cases
+ // where flash_task could be set at the time of call. The test was
+ // probably needed on 3DO when IP_frame() was a task.
+ assert (!pSolarSysState->InIpFlight);
+
+ DrawMenuStateStrings (PM_STARMAP, -(PM_NAVIGATE - PM_SCAN));
+
+ InitDisplayList ();
+ // This also spawns the flagship element
+ DoMissions ();
+
+ // Figure out and note which planet/moon we just left, if any
+ // This records any existing collision and prevents the ship
+ // from entering planets until a new collision occurs.
+ // TODO: this may need logic similar to one in leaveInnerSystem()
+ // for when the ship collides with more than one planet at
+ // the same time. While quite rare, it's still possible.
+ CheckIntersect ();
+
+ pSolarSysState->InIpFlight = TRUE;
+
+ // Do not start playing the music if we entered the solarsys only
+ // to load a game (load invoked from Main menu)
+ // XXX: This is quite hacky
+ if (!PLRPlaying ((MUSIC_REF)~0) &&
+ (LastActivity != CHECK_LOAD || NextActivity))
+ {
+ PlayMusic (SpaceMusic, TRUE, 1);
+ }
+}
+
+static void
+EnterPlanetOrbit (void)
+{
+ if (pSolarSysState->InIpFlight)
+ { // This means we hit a planet in IP flight; not a Load into orbit
+ FreeSolarSys ();
+
+ if (worldIsMoon (pSolarSysState, pSolarSysState->pOrbitalDesc))
+ { // Moon -- use its origin
+ // XXX: The conversion functions do not error-correct, so the
+ // point we set here will change once flag_ship_preprocess()
+ // in ipdisp.c starts over again.
+ GLOBAL (ShipStamp.origin) =
+ pSolarSysState->pOrbitalDesc->image.origin;
+ }
+ else
+ { // Planet -- its origin is for the outer view, so use mid-screen
+ GLOBAL (ShipStamp.origin.x) = SIS_SCREEN_WIDTH >> 1;
+ GLOBAL (ShipStamp.origin.y) = SIS_SCREEN_HEIGHT >> 1;
+ }
+ }
+
+ GetPlanetInfo ();
+ (*pSolarSysState->genFuncs->generateOrbital) (pSolarSysState,
+ pSolarSysState->pOrbitalDesc);
+ LastActivity &= ~(CHECK_LOAD | CHECK_RESTART);
+ if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD |
+ START_ENCOUNTER)) || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0
+ || GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
+ return;
+
+ // Implement a to-do in generate.h for a better test
+ if (pSolarSysState->TopoFrame)
+ { // We've entered orbit; LoadPlanet() called planet surface-gen code
+ PlanetOrbitMenu ();
+ FreePlanet ();
+ }
+ // Otherwise, generateOrbital function started a homeworld conversation,
+ // and we did not get to the planet no matter what.
+
+ // START_ENCOUNTER could be set by Devices menu a number of ways:
+ // Talking Pet, Sun Device or a Caster over Chmmr, or
+ // a Caster for Ilwrath
+ // Could also have blown self up with Utwig Bomb
+ if (!(GLOBAL (CurrentActivity) & (START_ENCOUNTER |
+ CHECK_ABORT | CHECK_LOAD))
+ && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
+ { // Reload the system and return to the inner view
+ PLANET_DESC *orbital = LoadSolarSys ();
+ assert (!orbital);
+ CheckZoomLevel ();
+ ValidateOrbits ();
+ ValidateInnerOrbits ();
+ ResetSolarSys ();
+
+ RepairSISBorder ();
+ TransitionSystemIn ();
+ }
+}
+
+static void
+InitSolarSys (void)
+{
+ BOOLEAN InnerSystem;
+ BOOLEAN Reentry;
+ PLANET_DESC *orbital;
+
+
+ LoadIPData ();
+ LoadLanderData ();
+
+ Reentry = (GLOBAL (ShipFacing) != 0);
+ if (!Reentry)
+ {
+ GLOBAL (autopilot.x) = ~0;
+ GLOBAL (autopilot.y) = ~0;
+
+ GLOBAL (ShipStamp.origin.x) = SIS_SCREEN_WIDTH >> 1;
+ GLOBAL (ShipStamp.origin.y) = SIS_SCREEN_HEIGHT - 2;
+
+ GLOBAL (ip_location) = displayToLocation (GLOBAL (ShipStamp.origin),
+ MAX_ZOOM_RADIUS);
+ }
+
+
+ StarsFrame = CreateStarBackGround ();
+
+ SetContext (SpaceContext);
+ SetContextFGFrame (Screen);
+ SetContextBackGroundColor (BLACK_COLOR);
+
+
+ orbital = LoadSolarSys ();
+ InnerSystem = CheckZoomLevel ();
+ ValidateOrbits ();
+ if (InnerSystem)
+ ValidateInnerOrbits ();
+
+ if (Reentry)
+ {
+ (*pSolarSysState->genFuncs->reinitNpcs) (pSolarSysState);
+ }
+ else
+ {
+ EncounterRace = -1;
+ EncounterGroup = 0;
+ GLOBAL (BattleGroupRef) = 0;
+ ReinitQueue (&GLOBAL (ip_group_q));
+ ReinitQueue (&GLOBAL (npc_built_ship_q));
+ (*pSolarSysState->genFuncs->initNpcs) (pSolarSysState);
+ }
+
+ if (orbital)
+ {
+ enterOrbital (orbital);
+ }
+ else
+ { // Draw the borders, the system (inner or outer) and fade/transition
+ SetContext (SpaceContext);
+
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+
+ DrawSISFrame ();
+ DrawSISMessage (NULL);
+
+ ResetSolarSys ();
+
+ if (LastActivity == (CHECK_LOAD | CHECK_RESTART))
+ { // Starting a new game, NOT from load!
+ // We have to fade the screen in from intro or menu
+ DrawOuterSystem ();
+ RedrawQueue (FALSE);
+ UnbatchGraphics ();
+ FadeScreen (FadeAllToColor, ONE_SECOND / 2);
+
+ LastActivity = 0;
+ }
+ else if (LastActivity == CHECK_LOAD && !NextActivity)
+ { // Called just to load a game; invoked from Main menu
+ // No point in drawing anything
+ UnbatchGraphics ();
+ }
+ else
+ { // Entered a new system, or loaded into inner or outer
+ if (InnerSystem)
+ DrawInnerSystem ();
+ else
+ DrawOuterSystem ();
+ RedrawQueue (FALSE);
+ ScreenTransition (3, NULL);
+ UnbatchGraphics ();
+
+ LastActivity &= ~CHECK_LOAD;
+ }
+ }
+}
+
+static void
+endInterPlanetary (void)
+{
+ GLOBAL (CurrentActivity) &= ~END_INTERPLANETARY;
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ // These are game state changing ops and so cannot be
+ // called once another game has been loaded!
+ (*pSolarSysState->genFuncs->uninitNpcs) (pSolarSysState);
+ SET_GAME_STATE (USED_BROADCASTER, 0);
+ }
+}
+
+// Find the closest planet to a point, in interplanetary.
+static PLANET_DESC *
+closestPlanetInterPlanetary (const POINT *point)
+{
+ BYTE i;
+ BYTE numPlanets;
+ DWORD bestDistSquared;
+ PLANET_DESC *bestPlanet = NULL;
+
+ assert(pSolarSysState != NULL);
+
+ numPlanets = pSolarSysState->SunDesc[0].NumPlanets;
+
+ bestDistSquared = (DWORD) -1; // Maximum value of DWORD.
+ for (i = 0; i < numPlanets; i++)
+ {
+ PLANET_DESC *planet = &pSolarSysState->PlanetDesc[i];
+
+ SIZE dx = point->x - planet->image.origin.x;
+ SIZE dy = point->y - planet->image.origin.y;
+
+ DWORD distSquared = (DWORD) ((long) dx * dx + (long) dy * dy);
+ if (distSquared < bestDistSquared)
+ {
+ bestDistSquared = distSquared;
+ bestPlanet = planet;
+ }
+ }
+
+ return bestPlanet;
+}
+
+static void
+UninitSolarSys (void)
+{
+ FreeSolarSys ();
+
+//FreeLanderData ();
+//FreeIPData ();
+
+ DestroyDrawable (ReleaseDrawable (StarsFrame));
+ StarsFrame = NULL;
+
+ if (GLOBAL (CurrentActivity) & END_INTERPLANETARY)
+ {
+ endInterPlanetary ();
+ return;
+ }
+
+ if ((GLOBAL (CurrentActivity) & START_ENCOUNTER) && EncounterGroup)
+ {
+ GetGroupInfo (GLOBAL (BattleGroupRef), EncounterGroup);
+ // Generate the encounter location name based on the closest planet
+
+ if (GLOBAL (ip_planet) == 0)
+ {
+ PLANET_DESC *planet =
+ closestPlanetInterPlanetary (&GLOBAL (ShipStamp.origin));
+
+ (*pSolarSysState->genFuncs->generateName) (
+ pSolarSysState, planet);
+ }
+ }
+}
+
+static void
+CalcSunSize (PLANET_DESC *pSunDesc, SIZE radius)
+{
+ SIZE index = 0;
+
+ if (radius <= (MAX_ZOOM_RADIUS >> 1))
+ {
+ ++index;
+ if (radius <= (MAX_ZOOM_RADIUS >> 2))
+ ++index;
+ }
+
+ pSunDesc->image.origin.x = SIS_SCREEN_WIDTH >> 1;
+ pSunDesc->image.origin.y = SIS_SCREEN_HEIGHT >> 1;
+ pSunDesc->image.frame = SetRelFrameIndex (SunFrame, index);
+}
+
+static void
+SetPlanetColorMap (PLANET_DESC *planet)
+{
+ COUNT index = planet->data_index & ~WORLD_TYPE_SPECIAL;
+ assert (index < NUMBER_OF_PLANET_TYPES);
+ SetColorMap (GetColorMapAddress (SetAbsColorMapIndex (OrbitalCMap,
+ PLANCOLOR (PlanData[index].Type))));
+}
+
+static void
+DrawInnerPlanets (PLANET_DESC *planet)
+{
+ STAMP s;
+ COUNT i;
+ PLANET_DESC *moon;
+
+ // Draw the planet image
+ SetPlanetColorMap (planet);
+ s.origin.x = SIS_SCREEN_WIDTH >> 1;
+ s.origin.y = SIS_SCREEN_HEIGHT >> 1;
+ s.frame = planet->image.frame;
+
+ i = planet->data_index & ~WORLD_TYPE_SPECIAL;
+ if (i < NUMBER_OF_PLANET_TYPES
+ && (planet->data_index & PLANET_SHIELDED))
+ { // Shielded world looks "shielded" in inner view
+ s.frame = SetAbsFrameIndex (SpaceJunkFrame, 17);
+ }
+ DrawStamp (&s);
+
+ // Draw the moon images
+ for (i = planet->NumPlanets, moon = pSolarSysState->MoonDesc;
+ i; --i, ++moon)
+ {
+ if (!(moon->data_index & WORLD_TYPE_SPECIAL))
+ SetPlanetColorMap (moon);
+ DrawStamp (&moon->image);
+ }
+}
+
+static void
+DrawSystem (SIZE radius, BOOLEAN IsInnerSystem)
+{
+ BYTE i;
+ PLANET_DESC *pCurDesc;
+ PLANET_DESC *pBaseDesc;
+ CONTEXT oldContext;
+ STAMP s;
+
+ if (!SolarSysFrame)
+ { // Create the saved view graphic
+ RECT clipRect;
+
+ GetContextClipRect (&clipRect);
+ SolarSysFrame = CaptureDrawable (CreateDrawable (WANT_PIXMAP,
+ clipRect.extent.width, clipRect.extent.height, 1));
+ }
+
+ oldContext = SetContext (OffScreenContext);
+ SetContextFGFrame (SolarSysFrame);
+ SetContextClipRect (NULL);
+
+ DrawStarBackGround ();
+
+ pBaseDesc = pSolarSysState->pBaseDesc;
+ if (IsInnerSystem)
+ { // Draw the inner system view *planet's* orbit segment
+ pCurDesc = pSolarSysState->pOrbitalDesc;
+ DrawOrbit (pCurDesc, DISPLAY_FACTOR * 4, DISPLAY_FACTOR, radius);
+ }
+
+ // Draw the planet orbits or moon orbits
+ for (i = pBaseDesc->pPrevDesc->NumPlanets, pCurDesc = pBaseDesc;
+ i; --i, ++pCurDesc)
+ {
+ if (IsInnerSystem)
+ DrawOrbit (pCurDesc, 2, 1, 2);
+ else
+ DrawOrbit (pCurDesc, DISPLAY_FACTOR, DISPLAY_FACTOR / 4,
+ radius);
+ }
+
+ if (IsInnerSystem)
+ { // Draw the inner system view
+ DrawInnerPlanets (pSolarSysState->pOrbitalDesc);
+ }
+ else
+ { // Draw the outer system view
+ SIZE index;
+
+ CalcSunSize (&pSolarSysState->SunDesc[0], radius);
+
+ index = pSolarSysState->FirstPlanetIndex;
+ for (;;)
+ {
+ pCurDesc = &pSolarSysState->PlanetDesc[index];
+ if (pCurDesc == &pSolarSysState->SunDesc[0])
+ { // It's a sun
+ SetColorMap (GetColorMapAddress (SetAbsColorMapIndex (
+ SunCMap, STAR_COLOR (CurStarDescPtr->Type))));
+ }
+ else
+ { // It's a planet
+ SetPlanetColorMap (pCurDesc);
+ }
+ DrawStamp (&pCurDesc->image);
+
+ if (index == pSolarSysState->LastPlanetIndex)
+ break;
+ index = pCurDesc->NextIndex;
+ }
+ }
+
+ SetContext (oldContext);
+
+ // Draw the now-saved view graphic
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SolarSysFrame;
+ DrawStamp (&s);
+}
+
+void
+DrawStarBackGround (void)
+{
+ STAMP s;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = StarsFrame;
+ DrawStamp (&s);
+}
+
+static FRAME
+CreateStarBackGround (void)
+{
+ COUNT i, j;
+ DWORD rand_val;
+ STAMP s;
+ CONTEXT oldContext;
+ RECT clipRect;
+ FRAME frame;
+
+ // Use SpaceContext to find out the dimensions of the background
+ oldContext = SetContext (SpaceContext);
+ GetContextClipRect (&clipRect);
+
+ // Prepare a pre-drawn stars frame for this system
+ frame = CaptureDrawable (CreateDrawable (WANT_PIXMAP,
+ clipRect.extent.width, clipRect.extent.height, 1));
+ SetContext (OffScreenContext);
+ SetContextFGFrame (frame);
+ SetContextClipRect (NULL);
+ SetContextBackGroundColor (BLACK_COLOR);
+
+ ClearDrawable ();
+
+ RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (CurStarDescPtr));
+
+#define NUM_DIM_PIECES 8
+ s.frame = SpaceJunkFrame;
+ for (i = 0; i < NUM_DIM_PIECES; ++i)
+ {
+#define NUM_DIM_DRAWN 5
+ for (j = 0; j < NUM_DIM_DRAWN; ++j)
+ {
+ rand_val = RandomContext_Random (SysGenRNG);
+ s.origin.x = LOWORD (rand_val) % SIS_SCREEN_WIDTH;
+ s.origin.y = HIWORD (rand_val) % SIS_SCREEN_HEIGHT;
+
+ DrawStamp (&s);
+ }
+ s.frame = IncFrameIndex (s.frame);
+ }
+#define NUM_BRT_PIECES 8
+ for (i = 0; i < NUM_BRT_PIECES; ++i)
+ {
+#define NUM_BRT_DRAWN 30
+ for (j = 0; j < NUM_BRT_DRAWN; ++j)
+ {
+ rand_val = RandomContext_Random (SysGenRNG);
+ s.origin.x = LOWORD (rand_val) % SIS_SCREEN_WIDTH;
+ s.origin.y = HIWORD (rand_val) % SIS_SCREEN_HEIGHT;
+
+ DrawStamp (&s);
+ }
+ s.frame = IncFrameIndex (s.frame);
+ }
+
+ SetContext (oldContext);
+
+ return frame;
+}
+
+void
+XFormIPLoc (POINT *pIn, POINT *pOut, BOOLEAN ToDisplay)
+{
+ if (ToDisplay)
+ *pOut = locationToDisplay (*pIn, pSolarSysState->SunDesc[0].radius);
+ else
+ *pOut = displayToLocation (*pIn, pSolarSysState->SunDesc[0].radius);
+}
+
+void
+ExploreSolarSys (void)
+{
+ SOLARSYS_STATE SolarSysState;
+
+ if (CurStarDescPtr == 0)
+ {
+ POINT universe;
+
+ universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+ CurStarDescPtr = FindStar (0, &universe, 1, 1);
+ if (!CurStarDescPtr)
+ {
+ log_add (log_Fatal, "ExploreSolarSys(): do not know where you are!");
+ explode ();
+ }
+ }
+ GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX (CurStarDescPtr->star_pt.x);
+ GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY (CurStarDescPtr->star_pt.y);
+
+ pSolarSysState = &SolarSysState;
+
+ memset (pSolarSysState, 0, sizeof (*pSolarSysState));
+
+ SolarSysState.genFuncs = getGenerateFunctions (CurStarDescPtr->Index);
+
+ InitSolarSys ();
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ SolarSysState.InputFunc = DoIpFlight;
+ DoInput (&SolarSysState, FALSE);
+ UninitSolarSys ();
+ pSolarSysState = 0;
+}
+
+UNICODE *
+GetNamedPlanetaryBody (void)
+{
+ if (!CurStarDescPtr || !playerInSolarSystem () || !playerInInnerSystem ())
+ return NULL; // Not inside an inner system, so no name
+
+ assert (pSolarSysState->pOrbitalDesc != NULL);
+
+ if (CurStarDescPtr->Index == SOL_DEFINED)
+ { // Planets and moons in Sol
+ int planet;
+ int moon;
+
+ planet = planetIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
+
+ if (worldIsPlanet (pSolarSysState, pSolarSysState->pOrbitalDesc))
+ { // A planet
+ return GAME_STRING (PLANET_NUMBER_BASE + planet);
+ }
+
+ // Moons
+ moon = moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
+ switch (planet)
+ {
+ case 2: // Earth
+ switch (moon)
+ {
+ case 0: // Starbase
+ return GAME_STRING (STARBASE_STRING_BASE + 0);
+ case 1: // Luna
+ return GAME_STRING (PLANET_NUMBER_BASE + 9);
+ }
+ break;
+ case 4: // Jupiter
+ switch (moon)
+ {
+ case 0: // Io
+ return GAME_STRING (PLANET_NUMBER_BASE + 10);
+ case 1: // Europa
+ return GAME_STRING (PLANET_NUMBER_BASE + 11);
+ case 2: // Ganymede
+ return GAME_STRING (PLANET_NUMBER_BASE + 12);
+ case 3: // Callisto
+ return GAME_STRING (PLANET_NUMBER_BASE + 13);
+ }
+ break;
+ case 5: // Saturn
+ if (moon == 0) // Titan
+ return GAME_STRING (PLANET_NUMBER_BASE + 14);
+ break;
+ case 7: // Neptune
+ if (moon == 0) // Triton
+ return GAME_STRING (PLANET_NUMBER_BASE + 15);
+ break;
+ }
+ }
+ else if (CurStarDescPtr->Index == SPATHI_DEFINED)
+ {
+ if (matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc,
+ 0, MATCH_PLANET))
+ {
+#ifdef NOTYET
+ return "Spathiwa";
+#endif // NOTYET
+ }
+ }
+ else if (CurStarDescPtr->Index == SAMATRA_DEFINED)
+ {
+ if (matchWorld (pSolarSysState, pSolarSysState->pOrbitalDesc, 4, 0))
+ { // Sa-Matra
+ return GAME_STRING (PLANET_NUMBER_BASE + 32);
+ }
+ }
+
+ return NULL;
+}
+
+void
+GetPlanetOrMoonName (UNICODE *buf, COUNT bufsize)
+{
+ UNICODE *named;
+ int moon;
+ int i;
+
+ named = GetNamedPlanetaryBody ();
+ if (named)
+ {
+ utf8StringCopy (buf, bufsize, named);
+ return;
+ }
+
+ // Either not named or we already have a name
+ utf8StringCopy (buf, bufsize, GLOBAL_SIS (PlanetName));
+
+ if (!playerInSolarSystem () || !playerInInnerSystem () ||
+ worldIsPlanet (pSolarSysState, pSolarSysState->pOrbitalDesc))
+ { // Outer or inner system or orbiting a planet
+ return;
+ }
+
+ // Orbiting an unnamed moon
+ i = strlen (buf);
+ buf += i;
+ bufsize -= i;
+ moon = moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
+ if (bufsize >= 3)
+ {
+ snprintf (buf, bufsize, "-%c", 'A' + moon);
+ buf[bufsize - 1] = '\0';
+ }
+}
+
+void
+SaveSolarSysLocation (void)
+{
+ assert (playerInSolarSystem ());
+
+ // This is a two-stage saving procedure
+ // Stage 1: called when saving from inner/outer view
+ // Stage 2: called when saving from orbital
+
+ if (!playerInPlanetOrbit ())
+ {
+ saveNonOrbitalLocation ();
+ }
+ else
+ { // In orbit around a planet.
+ BYTE moon;
+
+ // Update the starinfo.dat file if necessary.
+ if (GET_GAME_STATE (PLANETARY_CHANGE))
+ {
+ PutPlanetInfo ();
+ SET_GAME_STATE (PLANETARY_CHANGE, 0);
+ }
+
+ // GLOBAL (ip_planet) is already set
+ assert (GLOBAL (ip_planet) != 0);
+
+ // has to be at least 1 because code tests for in_orbit!=0
+ moon = 1; /* the planet itself */
+ if (worldIsMoon (pSolarSysState, pSolarSysState->pOrbitalDesc))
+ {
+ moon += moonIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
+ // +1 because moons have to be 1-based
+ moon += 1;
+ }
+ GLOBAL (in_orbit) = moon;
+ }
+}
+
+static BOOLEAN
+DoSolarSysMenu (MENU_STATE *pMS)
+{
+ BOOLEAN select = PulsedInputState.menu[KEY_MENU_SELECT];
+ BOOLEAN handled;
+
+ if ((GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0)
+ return FALSE;
+
+ handled = DoMenuChooser (pMS, PM_STARMAP);
+ if (handled)
+ return TRUE;
+
+ if (LastActivity == CHECK_LOAD)
+ select = TRUE; // Selected LOAD from main menu
+
+ if (!select)
+ return TRUE;
+
+ SetFlashRect (NULL);
+
+ switch (pMS->CurState)
+ {
+ case EQUIP_DEVICE:
+ select = DevicesMenu ();
+ if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ { // Invoked Talking Pet or a Caster for Ilwrath
+ // Going into conversation
+ return FALSE;
+ }
+ break;
+ case CARGO:
+ CargoMenu ();
+ break;
+ case ROSTER:
+ select = RosterMenu ();
+ break;
+ case GAME_MENU:
+ if (!GameOptions ())
+ return FALSE; // abort or load
+ break;
+ case STARMAP:
+ StarMap ();
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ TransitionSystemIn ();
+ // Fall through !!!
+ case NAVIGATION:
+ return FALSE;
+ }
+
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ if (select)
+ { // 3DO menu jumps to NAVIGATE after a successful submenu run
+ if (optWhichMenu != OPT_PC)
+ pMS->CurState = NAVIGATION;
+ DrawMenuStateStrings (PM_STARMAP, pMS->CurState);
+ }
+ SetFlashRect (SFR_MENU_3DO);
+ }
+
+ return TRUE;
+}
+
+static void
+SolarSysMenu (void)
+{
+ MENU_STATE MenuState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+
+ if (LastActivity == CHECK_LOAD)
+ { // Selected LOAD from main menu
+ MenuState.CurState = GAME_MENU;
+ }
+ else
+ {
+ DrawMenuStateStrings (PM_STARMAP, STARMAP);
+ MenuState.CurState = STARMAP;
+ }
+
+ DrawStatusMessage (NULL);
+ SetFlashRect (SFR_MENU_3DO);
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ MenuState.InputFunc = DoSolarSysMenu;
+ DoInput (&MenuState, TRUE);
+
+ DrawMenuStateStrings (PM_STARMAP, -NAVIGATION);
+}
+
+static BOOLEAN
+DoIpFlight (SOLARSYS_STATE *pSS)
+{
+ static TimeCount NextTime;
+ BOOLEAN cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
+
+ if (pSS->InOrbit)
+ { // CheckShipLocation() or InitSolarSys() sent us to orbital
+ EnterPlanetOrbit ();
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ pSS->InOrbit = FALSE;
+ }
+ else if (cancel || LastActivity == CHECK_LOAD)
+ {
+ SolarSysMenu ();
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ }
+ else
+ {
+ assert (pSS->InIpFlight);
+ IP_frame ();
+ SleepThreadUntil (NextTime);
+ NextTime = GetTimeCounter () + IP_FRAME_RATE;
+ }
+
+ return (!(GLOBAL (CurrentActivity)
+ & (START_ENCOUNTER | END_INTERPLANETARY
+ | CHECK_ABORT | CHECK_LOAD))
+ && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0);
+}
diff --git a/src/uqm/planets/solarsys.h b/src/uqm/planets/solarsys.h
new file mode 100644
index 0000000..0f86fdd
--- /dev/null
+++ b/src/uqm/planets/solarsys.h
@@ -0,0 +1,34 @@
+//Copyright (C) 2011, Scott A. Colcord
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SOLARSYS_H
+#define SOLARSYS_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern void LoadIPData (void);
+extern void FreeIPData (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SOLARSYS_H */
+
diff --git a/src/uqm/planets/sundata.h b/src/uqm/planets/sundata.h
new file mode 100644
index 0000000..6d7888e
--- /dev/null
+++ b/src/uqm/planets/sundata.h
@@ -0,0 +1,73 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_PLANETS_SUNDATA_H_
+#define UQM_PLANETS_SUNDATA_H_
+
+#include "plandata.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/*------------------------------ Global Data ------------------------------ */
+
+#define NUMBER_OF_SUN_SIZES (SUPER_GIANT_STAR - DWARF_STAR + 1)
+
+#define DWARF_ENERGY 1
+#define GIANT_ENERGY 5
+#define SUPERGIANT_ENERGY 20
+
+typedef struct
+{
+ BYTE StarSize;
+ BYTE StarIntensity;
+ UWORD StarEnergy;
+
+ PLANET_INFO PlanetInfo;
+} SYSTEM_INFO;
+
+#define GENERATE_ALL ((COUNT)~0)
+
+extern COUNT GenerateMineralDeposits (const SYSTEM_INFO *, COUNT whichDeposit,
+ NODE_INFO *info);
+extern COUNT GenerateLifeForms (const SYSTEM_INFO *, COUNT whichLife,
+ NODE_INFO *info);
+extern void GenerateRandomLocation (POINT *loc);
+extern COUNT GenerateRandomNodes (const SYSTEM_INFO *, COUNT scan, COUNT numNodes,
+ COUNT type, COUNT whichNode, NODE_INFO *info);
+// Generate lifeforms from a preset lifeTypes[] array
+extern COUNT GeneratePresetLife (const SYSTEM_INFO *,
+ const SBYTE *lifeTypes, COUNT whichLife, NODE_INFO *info);
+
+#define DWARF_ELEMENT_DENSITY 1
+#define GIANT_ELEMENT_DENSITY 3
+#define SUPERGIANT_ELEMENT_DENSITY 8
+
+#define MAX_ELEMENT_DENSITY ((MAX_ELEMENT_UNITS * SUPERGIANT_ELEMENT_DENSITY) << 1)
+
+extern void DoPlanetaryAnalysis (SYSTEM_INFO *SysInfoPtr,
+ PLANET_DESC *pPlanetDesc);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PLANETS_SUNDATA_H_ */
+
diff --git a/src/uqm/planets/surface.c b/src/uqm/planets/surface.c
new file mode 100644
index 0000000..79f9e75
--- /dev/null
+++ b/src/uqm/planets/surface.c
@@ -0,0 +1,251 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "lifeform.h"
+#include "planets.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+
+
+//#define DEBUG_SURFACE
+
+const BYTE *Elements;
+const PlanetFrame *PlanData;
+
+static COUNT
+CalcMineralDeposits (const SYSTEM_INFO *SysInfoPtr, COUNT which_deposit,
+ NODE_INFO *info)
+{
+ BYTE j;
+ COUNT num_deposits;
+ const ELEMENT_ENTRY *eptr;
+
+ eptr = &SysInfoPtr->PlanetInfo.PlanDataPtr->UsefulElements[0];
+ num_deposits = 0;
+ j = NUM_USEFUL_ELEMENTS;
+ do
+ {
+ BYTE num_possible;
+
+ num_possible = LOBYTE (RandomContext_Random (SysGenRNG))
+ % (DEPOSIT_QUANTITY (eptr->Density) + 1);
+ while (num_possible--)
+ {
+#define MEDIUM_DEPOSIT_THRESHOLD 150
+#define LARGE_DEPOSIT_THRESHOLD 225
+ COUNT deposit_quality_fine;
+ COUNT deposit_quality_gross;
+
+ deposit_quality_fine = (LOWORD (RandomContext_Random (SysGenRNG)) % 100)
+ + (
+ DEPOSIT_QUALITY (eptr->Density)
+ + SysInfoPtr->StarSize
+ ) * 50;
+ if (deposit_quality_fine < MEDIUM_DEPOSIT_THRESHOLD)
+ deposit_quality_gross = 0;
+ else if (deposit_quality_fine < LARGE_DEPOSIT_THRESHOLD)
+ deposit_quality_gross = 1;
+ else
+ deposit_quality_gross = 2;
+
+ GenerateRandomLocation (&info->loc_pt);
+
+ info->density = MAKE_WORD (
+ deposit_quality_gross, deposit_quality_fine / 10 + 1);
+ info->type = eptr->ElementType;
+#ifdef DEBUG_SURFACE
+ log_add (log_Debug, "\t\t%d units of %Fs",
+ info->density,
+ Elements[eptr->ElementType].name);
+#endif /* DEBUG_SURFACE */
+ if (num_deposits >= which_deposit
+ || ++num_deposits == sizeof (DWORD) * 8)
+ { // reached the maximum or the requested node
+ return num_deposits;
+ }
+ }
+ ++eptr;
+ } while (--j);
+
+ return num_deposits;
+}
+
+// Returns:
+// for whichLife==~0 : the number of nodes generated
+// for whichLife<32 : the index of the last node (no known usage exists)
+// Sets the SysGenRNG to the required state first.
+COUNT
+GenerateMineralDeposits (const SYSTEM_INFO *SysInfoPtr, COUNT whichDeposit,
+ NODE_INFO *info)
+{
+ NODE_INFO temp_info;
+ if (!info) // user not interested in info but we need space for it
+ info = &temp_info;
+ RandomContext_SeedRandom (SysGenRNG,
+ SysInfoPtr->PlanetInfo.ScanSeed[MINERAL_SCAN]);
+ return CalcMineralDeposits (SysInfoPtr, whichDeposit, info);
+}
+
+static COUNT
+CalcLifeForms (const SYSTEM_INFO *SysInfoPtr, COUNT which_life,
+ NODE_INFO *info)
+{
+ COUNT num_life_forms;
+
+ num_life_forms = 0;
+ if (PLANSIZE (SysInfoPtr->PlanetInfo.PlanDataPtr->Type) != GAS_GIANT)
+ {
+#define MIN_LIFE_CHANCE 10
+ SIZE life_var;
+
+ life_var = RandomContext_Random (SysGenRNG) & 1023;
+ if (life_var < SysInfoPtr->PlanetInfo.LifeChance
+ || (SysInfoPtr->PlanetInfo.LifeChance < MIN_LIFE_CHANCE
+ && life_var < MIN_LIFE_CHANCE))
+ {
+ BYTE num_types;
+
+ num_types = 1 + LOBYTE (RandomContext_Random (SysGenRNG))
+ % MAX_LIFE_VARIATION;
+ do
+ {
+ BYTE index, num_creatures;
+ UWORD rand_val;
+
+ rand_val = RandomContext_Random (SysGenRNG);
+ index = LOBYTE (rand_val) % NUM_CREATURE_TYPES;
+ num_creatures = 1 + HIBYTE (rand_val) % 10;
+ do
+ {
+ GenerateRandomLocation (&info->loc_pt);
+ info->type = index;
+ info->density = 0;
+
+ if (num_life_forms >= which_life
+ || ++num_life_forms == sizeof (DWORD) * 8)
+ { // reached the maximum or the requested node
+ return num_life_forms;
+ }
+ } while (--num_creatures);
+ } while (--num_types);
+ }
+#ifdef DEBUG_SURFACE
+ else
+ {
+ log_add (log_Debug, "It's dead, Jim! (%d >= %d)", life_var,
+ SysInfoPtr->PlanetInfo.LifeChance);
+ }
+#endif /* DEBUG_SURFACE */
+ }
+
+ return num_life_forms;
+}
+
+// Returns:
+// for whichLife==~0 : the number of lifeforms generated
+// for whichLife<32 : the index of the last lifeform (no known usage exists)
+// Sets the SysGenRNG to the required state first.
+COUNT
+GenerateLifeForms (const SYSTEM_INFO *SysInfoPtr, COUNT whichLife,
+ NODE_INFO *info)
+{
+ NODE_INFO temp_info;
+ if (!info) // user not interested in info but we need space for it
+ info = &temp_info;
+ RandomContext_SeedRandom (SysGenRNG,
+ SysInfoPtr->PlanetInfo.ScanSeed[BIOLOGICAL_SCAN]);
+ return CalcLifeForms (SysInfoPtr, whichLife, info);
+}
+
+// Returns:
+// for whichLife==~0 : the number of lifeforms generated
+// for whichLife<32 : the index of the last lifeform (no known usage exists)
+// Sets the SysGenRNG to the required state first.
+// lifeTypes[] is terminated with -1
+COUNT
+GeneratePresetLife (const SYSTEM_INFO *SysInfoPtr, const SBYTE *lifeTypes,
+ COUNT whichLife, NODE_INFO *info)
+{
+ COUNT i;
+ NODE_INFO temp_info;
+
+ if (!info) // user not interested in info but we need space for it
+ info = &temp_info;
+
+ // This function may look unnecessarily complicated, but it must be
+ // kept this way to preserve the universe. That is done by preserving
+ // the order and number of Random() calls.
+
+ RandomContext_SeedRandom (SysGenRNG,
+ SysInfoPtr->PlanetInfo.ScanSeed[BIOLOGICAL_SCAN]);
+
+ for (i = 0; lifeTypes[i] >= 0; ++i)
+ {
+ GenerateRandomLocation (&info->loc_pt);
+ info->type = lifeTypes[i];
+ // density is irrelevant for bio nodes
+ info->density = 0;
+
+ if (i >= whichLife)
+ break;
+ }
+
+ return i;
+}
+
+void
+GenerateRandomLocation (POINT *loc)
+{
+ UWORD rand_val;
+
+ rand_val = RandomContext_Random (SysGenRNG);
+ loc->x = 8 + LOBYTE (rand_val) % (MAP_WIDTH - (8 << 1));
+ loc->y = 8 + HIBYTE (rand_val) % (MAP_HEIGHT - (8 << 1));
+}
+
+// Returns:
+// for whichNode==~0 : the number of nodes generated
+// for whichNode<32 : the index of the last node (no known usage exists)
+// Sets the SysGenRNG to the required state first.
+COUNT
+GenerateRandomNodes (const SYSTEM_INFO *SysInfoPtr, COUNT scan, COUNT numNodes,
+ COUNT type, COUNT whichNode, NODE_INFO *info)
+{
+ COUNT i;
+ NODE_INFO temp_info;
+
+ if (!info) // user not interested in info but we need space for it
+ info = &temp_info;
+
+ RandomContext_SeedRandom (SysGenRNG,
+ SysInfoPtr->PlanetInfo.ScanSeed[scan]);
+
+ for (i = 0; i < numNodes; ++i)
+ {
+ GenerateRandomLocation (&info->loc_pt);
+ // type is irrelevant for energy nodes
+ info->type = type;
+ // density is irrelevant for energy and bio nodes
+ info->density = 0;
+
+ if (i >= whichNode)
+ break;
+ }
+
+ return i;
+}
diff --git a/src/uqm/process.c b/src/uqm/process.c
new file mode 100644
index 0000000..142c58e
--- /dev/null
+++ b/src/uqm/process.c
@@ -0,0 +1,1108 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "process.h"
+
+#include "races.h"
+#include "collide.h"
+#include "options.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "hyper.h"
+#include "element.h"
+#include "battle.h"
+#include "weapon.h"
+#include "libs/graphics/drawable.h"
+#include "libs/graphics/drawcmd.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/log.h"
+#include "libs/misc.h"
+
+
+//#define DEBUG_PROCESS
+
+COUNT DisplayFreeList;
+PRIMITIVE DisplayArray[MAX_DISPLAY_PRIMS];
+extern POINT SpaceOrg;
+
+SIZE zoom_out = 1 << ZOOM_SHIFT;
+static SIZE opt_max_zoom_out;
+
+#if 0
+static inline void
+CALC_ZOOM_STUFF (COUNT* idx, COUNT* sc)
+{
+ int i, z;
+
+ z = 1 << ZOOM_SHIFT;
+ for (i = 0; (z <<= 1) <= zoom_out; i++)
+ ;
+ *idx = i;
+ *sc = ((1 << i) << (ZOOM_SHIFT + 8)) / zoom_out;
+}
+#else
+static inline void
+CALC_ZOOM_STUFF (COUNT* idx, COUNT* sc)
+{
+ int i;
+
+ if (zoom_out < 2 << ZOOM_SHIFT)
+ i = 0;
+ else if (zoom_out < 4 << ZOOM_SHIFT)
+ i = 1;
+ else
+ i = 2;
+ *idx = i;
+ *sc = (1 << (i + ZOOM_SHIFT + 8)) / zoom_out;
+}
+#endif
+
+HELEMENT
+AllocElement (void)
+{
+ HELEMENT hElement;
+
+ hElement = AllocLink (&disp_q);
+ if (hElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ memset (ElementPtr, 0, sizeof (*ElementPtr));
+ ElementPtr->PrimIndex = AllocDisplayPrim ();
+ if (ElementPtr->PrimIndex == END_OF_LIST)
+ {
+ log_add (log_Error, "AllocElement: Out of display prims!");
+ explode ();
+ }
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ UnlockElement (hElement);
+ }
+
+ return (hElement);
+}
+
+void
+FreeElement (HELEMENT hElement)
+{
+ if (hElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ FreeDisplayPrim (ElementPtr->PrimIndex);
+ UnlockElement (hElement);
+
+ FreeLink (&disp_q, hElement);
+ }
+}
+
+void
+SetUpElement (ELEMENT *ElementPtr)
+{
+ ElementPtr->next = ElementPtr->current;
+ if (CollidingElement (ElementPtr))
+ {
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectFrame (ElementPtr);
+ }
+}
+
+static void
+PreProcess (ELEMENT *ElementPtr)
+{
+ ELEMENT_FLAGS state_flags;
+
+ if (ElementPtr->life_span == 0)
+ {
+ if (ElementPtr->pParent) /* untarget this dead element */
+ Untarget (ElementPtr);
+
+ ElementPtr->state_flags |= DISAPPEARING;
+ if (ElementPtr->death_func)
+ (*ElementPtr->death_func) (ElementPtr);
+ }
+
+ state_flags = ElementPtr->state_flags;
+ if (!(state_flags & DISAPPEARING))
+ {
+ if (state_flags & APPEARING)
+ {
+ SetUpElement (ElementPtr);
+
+ if (state_flags & PLAYER_SHIP)
+ state_flags &= ~APPEARING; /* want to preprocess ship */
+ }
+
+ if (ElementPtr->preprocess_func && !(state_flags & APPEARING))
+ {
+ (*ElementPtr->preprocess_func) (ElementPtr);
+
+ state_flags = ElementPtr->state_flags;
+ if ((state_flags & CHANGING) && CollidingElement (ElementPtr))
+ InitIntersectFrame (ElementPtr);
+ }
+
+ if (!(state_flags & IGNORE_VELOCITY))
+ {
+ SIZE delta_x, delta_y;
+
+ GetNextVelocityComponents (&ElementPtr->velocity,
+ &delta_x, &delta_y, 1);
+ if (delta_x != 0 || delta_y != 0)
+ {
+ state_flags |= CHANGING;
+ ElementPtr->next.location.x += delta_x;
+ ElementPtr->next.location.y += delta_y;
+ }
+ }
+
+ if (CollidingElement (ElementPtr))
+ InitIntersectEndPoint (ElementPtr);
+
+ if (state_flags & FINITE_LIFE)
+ --ElementPtr->life_span;
+ }
+
+ ElementPtr->state_flags = (state_flags & ~(POST_PROCESS | COLLISION))
+ | PRE_PROCESS;
+}
+
+static void
+PostProcess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->postprocess_func)
+ (*ElementPtr->postprocess_func) (ElementPtr);
+ ElementPtr->current = ElementPtr->next;
+
+ if (CollidingElement (ElementPtr))
+ {
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+ }
+
+ ElementPtr->state_flags = (ElementPtr->state_flags
+ & ~(PRE_PROCESS | CHANGING | APPEARING))
+ | POST_PROCESS;
+}
+
+static COUNT
+CalcReduction (SIZE dx, SIZE dy)
+{
+ COUNT next_reduction;
+
+#ifdef KDEBUG
+ log_add (log_Debug, "CalcReduction:");
+#endif
+
+ if (optMeleeScale == TFB_SCALE_STEP)
+ {
+ SIZE sdx, sdy;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) > IN_ENCOUNTER)
+ return (0);
+
+ sdx = dx;
+ sdy = dy;
+ for (next_reduction = MAX_VIS_REDUCTION;
+ (dx <<= REDUCTION_SHIFT) <= TRANSITION_WIDTH
+ && (dy <<= REDUCTION_SHIFT) <= TRANSITION_HEIGHT
+ && next_reduction > 0;
+ next_reduction -= REDUCTION_SHIFT)
+ ;
+
+ /* check for "real" zoom in */
+ if (next_reduction < zoom_out
+ && zoom_out <= MAX_VIS_REDUCTION)
+ {
+#define HYSTERESIS_X DISPLAY_TO_WORLD(24)
+#define HYSTERESIS_Y DISPLAY_TO_WORLD(20)
+ if (((sdx + HYSTERESIS_X)
+ << (MAX_VIS_REDUCTION - next_reduction)) > TRANSITION_WIDTH
+ || ((sdy + HYSTERESIS_Y)
+ << (MAX_VIS_REDUCTION - next_reduction)) > TRANSITION_HEIGHT)
+ /* if we don't zoom in, we want to stay at next+1 */
+ next_reduction += REDUCTION_SHIFT;
+ }
+
+ if (next_reduction == 0
+ && LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ next_reduction += REDUCTION_SHIFT;
+ }
+ else
+ {
+ if (LOBYTE (GLOBAL (CurrentActivity)) > IN_ENCOUNTER)
+ return (1 << ZOOM_SHIFT);
+
+ dx = (dx * MAX_ZOOM_OUT) / (LOG_SPACE_WIDTH >> 2);
+ if (dx < (1 << ZOOM_SHIFT))
+ dx = 1 << ZOOM_SHIFT;
+ else if (dx > MAX_ZOOM_OUT)
+ dx = MAX_ZOOM_OUT;
+
+ dy = (dy * MAX_ZOOM_OUT) / (LOG_SPACE_HEIGHT >> 2);
+ if (dy < (1 << ZOOM_SHIFT))
+ dy = 1 << ZOOM_SHIFT;
+ else if (dy > MAX_ZOOM_OUT)
+ dy = MAX_ZOOM_OUT;
+
+ if (dy > dx)
+ next_reduction = dy;
+ else
+ next_reduction = dx;
+
+ if (next_reduction < (2 << ZOOM_SHIFT)
+ && LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ next_reduction = (2 << ZOOM_SHIFT);
+ }
+
+#ifdef KDEBUG
+ log_add (log_Debug, "CalcReduction: exit");
+#endif
+
+ return (next_reduction);
+}
+
+static VIEW_STATE
+CalcView (POINT *pNewScrollPt, SIZE next_reduction,
+ SIZE *pdx, SIZE *pdy, COUNT ships_alive)
+{
+ SIZE dx, dy;
+ VIEW_STATE view_state;
+
+#ifdef KDEBUG
+ log_add (log_Debug, "CalcView:");
+#endif
+ dx = ((COORD)(LOG_SPACE_WIDTH >> 1) - pNewScrollPt->x);
+ dy = ((COORD)(LOG_SPACE_HEIGHT >> 1) - pNewScrollPt->y);
+ dx = WRAP_DELTA_X (dx);
+ dy = WRAP_DELTA_Y (dy);
+ if (ships_alive == 1)
+ {
+#define ORG_JUMP_X ((SIZE)DISPLAY_ALIGN(LOG_SPACE_WIDTH / 75))
+#define ORG_JUMP_Y ((SIZE)DISPLAY_ALIGN(LOG_SPACE_HEIGHT / 75))
+ if (dx > ORG_JUMP_X)
+ dx = ORG_JUMP_X;
+ else if (dx < -ORG_JUMP_X)
+ dx = -ORG_JUMP_X;
+ if (dy > ORG_JUMP_Y)
+ dy = ORG_JUMP_Y;
+ else if (dy < -ORG_JUMP_Y)
+ dy = -ORG_JUMP_Y;
+ }
+
+ if ((dx || dy) && inHQSpace ())
+ MoveSIS (&dx, &dy);
+
+ if (zoom_out == next_reduction)
+ view_state = dx == 0 && dy == 0 && !inHQSpace ()
+ ? VIEW_STABLE : VIEW_SCROLL;
+ else
+ {
+ if (optMeleeScale == TFB_SCALE_STEP)
+ {
+ SpaceOrg.x = (COORD)(LOG_SPACE_WIDTH >> 1)
+ - (LOG_SPACE_WIDTH >> ((MAX_REDUCTION + 1)
+ - next_reduction));
+ SpaceOrg.y = (COORD)(LOG_SPACE_HEIGHT >> 1)
+ - (LOG_SPACE_HEIGHT >> ((MAX_REDUCTION + 1)
+ - next_reduction));
+ }
+ else
+ {
+#define ZOOM_JUMP ((1 << ZOOM_SHIFT) >> 3)
+ if (ships_alive == 1
+ && zoom_out > next_reduction
+ && zoom_out <= MAX_ZOOM_OUT
+ && zoom_out - next_reduction > ZOOM_JUMP)
+ next_reduction = zoom_out - ZOOM_JUMP;
+
+ // Always align the origin on a whole pixel to reduce the
+ // amount of object positioning jitter
+ SpaceOrg.x = DISPLAY_ALIGN((int)(LOG_SPACE_WIDTH >> 1) -
+ (LOG_SPACE_WIDTH * next_reduction / (MAX_ZOOM_OUT << 2)));
+ SpaceOrg.y = DISPLAY_ALIGN((int)(LOG_SPACE_HEIGHT >> 1) -
+ (LOG_SPACE_HEIGHT * next_reduction / (MAX_ZOOM_OUT << 2)));
+ }
+ zoom_out = next_reduction;
+ view_state = VIEW_CHANGE;
+ }
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) <= IN_HYPERSPACE)
+ MoveGalaxy (view_state, dx, dy);
+
+ *pdx = dx;
+ *pdy = dy;
+
+#ifdef KDEBUG
+ log_add (log_Debug, "CalcView: exit");
+#endif
+ return (view_state);
+}
+
+
+static ELEMENT_FLAGS
+ProcessCollisions (HELEMENT hSuccElement, ELEMENT *ElementPtr,
+ TIME_VALUE min_time, ELEMENT_FLAGS process_flags)
+{
+ HELEMENT hTestElement;
+
+ while ((hTestElement = hSuccElement) != 0)
+ {
+ ELEMENT *TestElementPtr;
+
+ LockElement (hTestElement, &TestElementPtr);
+ if (!(TestElementPtr->state_flags & process_flags))
+ PreProcess (TestElementPtr);
+ hSuccElement = GetSuccElement (TestElementPtr);
+
+ if (TestElementPtr == ElementPtr)
+ {
+ UnlockElement (hTestElement);
+ continue;
+ }
+
+ if (CollisionPossible (TestElementPtr, ElementPtr))
+ {
+ ELEMENT_FLAGS state_flags, test_state_flags;
+ TIME_VALUE time_val;
+
+ state_flags = ElementPtr->state_flags;
+ test_state_flags = TestElementPtr->state_flags;
+ if (((state_flags | test_state_flags) & FINITE_LIFE)
+ && (((state_flags & APPEARING)
+ && ElementPtr->life_span > 1)
+ || ((test_state_flags & APPEARING)
+ && TestElementPtr->life_span > 1)))
+ time_val = 0;
+ else
+ {
+ while ((time_val = DrawablesIntersect (&ElementPtr->IntersectControl,
+ &TestElementPtr->IntersectControl, min_time)) == 1
+ && !((state_flags | test_state_flags) & FINITE_LIFE))
+ {
+#ifdef DEBUG_PROCESS
+ log_add (log_Debug, "BAD NEWS 0x%x <--> 0x%x", ElementPtr,
+ TestElementPtr);
+#endif /* DEBUG_PROCESS */
+ if (state_flags & COLLISION)
+ {
+ InitIntersectEndPoint (TestElementPtr);
+ TestElementPtr->IntersectControl.IntersectStamp.origin =
+ TestElementPtr->IntersectControl.EndPoint;
+ time_val = DrawablesIntersect (&ElementPtr->IntersectControl,
+ &TestElementPtr->IntersectControl, 1);
+ InitIntersectStartPoint (TestElementPtr);
+ }
+
+ if (time_val == 1)
+ {
+ FRAME CurFrame, NextFrame,
+ TestCurFrame, TestNextFrame;
+
+ CurFrame = ElementPtr->current.image.frame;
+ NextFrame = ElementPtr->next.image.frame;
+ TestCurFrame = TestElementPtr->current.image.frame;
+ TestNextFrame = TestElementPtr->next.image.frame;
+ if (NextFrame == CurFrame
+ && TestNextFrame == TestCurFrame)
+ {
+ if (test_state_flags & APPEARING)
+ {
+ do_damage (TestElementPtr, TestElementPtr->hit_points);
+ if (TestElementPtr->pParent) /* untarget this dead element */
+ Untarget (TestElementPtr);
+
+ TestElementPtr->state_flags |= (COLLISION | DISAPPEARING);
+ if (TestElementPtr->death_func)
+ (*TestElementPtr->death_func) (TestElementPtr);
+ }
+ if (state_flags & APPEARING)
+ {
+ do_damage (ElementPtr, ElementPtr->hit_points);
+ if (ElementPtr->pParent) /* untarget this dead element */
+ Untarget (ElementPtr);
+
+ ElementPtr->state_flags |= (COLLISION | DISAPPEARING);
+ if (ElementPtr->death_func)
+ (*ElementPtr->death_func) (ElementPtr);
+
+ UnlockElement (hTestElement);
+ return (COLLISION);
+ }
+
+ time_val = 0;
+ }
+ else
+ {
+ if (GetFrameIndex (CurFrame) !=
+ GetFrameIndex (NextFrame))
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (NextFrame,
+ CurFrame);
+ else if (NextFrame != CurFrame)
+ {
+ ElementPtr->next.image =
+ ElementPtr->current.image;
+ if (ElementPtr->life_span > NORMAL_LIFE)
+ ElementPtr->life_span = NORMAL_LIFE;
+ }
+
+ if (GetFrameIndex (TestCurFrame) !=
+ GetFrameIndex (TestNextFrame))
+ TestElementPtr->next.image.frame =
+ SetEquFrameIndex (TestNextFrame,
+ TestCurFrame);
+ else if (TestNextFrame != TestCurFrame)
+ {
+ TestElementPtr->next.image =
+ TestElementPtr->current.image;
+ if (TestElementPtr->life_span > NORMAL_LIFE)
+ TestElementPtr->life_span = NORMAL_LIFE;
+ }
+
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectFrame (ElementPtr);
+ if (state_flags & PLAYER_SHIP)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ StarShipPtr->ShipFacing =
+ GetFrameIndex (
+ ElementPtr->next.image.frame);
+ }
+
+ InitIntersectStartPoint (TestElementPtr);
+ InitIntersectEndPoint (TestElementPtr);
+ InitIntersectFrame (TestElementPtr);
+ if (test_state_flags & PLAYER_SHIP)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (TestElementPtr, &StarShipPtr);
+ StarShipPtr->ShipFacing =
+ GetFrameIndex (
+ TestElementPtr->next.image.frame);
+ }
+ }
+ }
+
+ if (time_val == 0)
+ {
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectEndPoint (TestElementPtr);
+
+ break;
+ }
+ }
+ }
+
+ if (time_val > 0)
+ {
+ POINT SavePt, TestSavePt;
+
+#ifdef DEBUG_PROCESS
+ log_add (log_Debug, "0x%x <--> 0x%x at %u", ElementPtr,
+ TestElementPtr, time_val);
+#endif /* DEBUG_PROCESS */
+ SavePt = ElementPtr->IntersectControl.EndPoint;
+ TestSavePt = TestElementPtr->IntersectControl.EndPoint;
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectEndPoint (TestElementPtr);
+ if (time_val == 1
+ || (((state_flags & COLLISION)
+ || !ProcessCollisions (hSuccElement, ElementPtr,
+ time_val - 1, process_flags))
+ && ((test_state_flags & COLLISION)
+ || !ProcessCollisions (
+ !(TestElementPtr->state_flags & APPEARING) ?
+ GetSuccElement (ElementPtr) :
+ GetHeadElement (), TestElementPtr,
+ time_val - 1, process_flags))))
+ {
+ state_flags = ElementPtr->state_flags;
+ test_state_flags = TestElementPtr->state_flags;
+
+#ifdef DEBUG_PROCESS
+ log_add (log_Debug, "PROCESSING 0x%x <--> 0x%x at %u",
+ ElementPtr, TestElementPtr, time_val);
+#endif /* DEBUG_PROCESS */
+ if (test_state_flags & PLAYER_SHIP)
+ {
+ (*TestElementPtr->collision_func) (
+ TestElementPtr, &TestSavePt,
+ ElementPtr, &SavePt
+ );
+ (*ElementPtr->collision_func) (
+ ElementPtr, &SavePt,
+ TestElementPtr, &TestSavePt
+ );
+ }
+ else
+ {
+ (*ElementPtr->collision_func) (
+ ElementPtr, &SavePt,
+ TestElementPtr, &TestSavePt
+ );
+ (*TestElementPtr->collision_func) (
+ TestElementPtr, &TestSavePt,
+ ElementPtr, &SavePt
+ );
+ }
+
+ if (TestElementPtr->state_flags & COLLISION)
+ {
+ if (!(test_state_flags & COLLISION))
+ {
+ TestElementPtr->IntersectControl.IntersectStamp.origin =
+ TestSavePt;
+ TestElementPtr->next.location.x =
+ DISPLAY_TO_WORLD (TestSavePt.x);
+ TestElementPtr->next.location.y =
+ DISPLAY_TO_WORLD (TestSavePt.y);
+ InitIntersectEndPoint (TestElementPtr);
+ }
+ }
+
+ if (ElementPtr->state_flags & COLLISION)
+ {
+ if (!(state_flags & COLLISION))
+ {
+ ElementPtr->IntersectControl.IntersectStamp.origin =
+ SavePt;
+ ElementPtr->next.location.x =
+ DISPLAY_TO_WORLD (SavePt.x);
+ ElementPtr->next.location.y =
+ DISPLAY_TO_WORLD (SavePt.y);
+ InitIntersectEndPoint (ElementPtr);
+
+ if (!(state_flags & FINITE_LIFE) &&
+ !(test_state_flags & FINITE_LIFE))
+ {
+ collide (ElementPtr, TestElementPtr);
+
+ ProcessCollisions (GetHeadElement (), ElementPtr,
+ MAX_TIME_VALUE, process_flags);
+ ProcessCollisions (GetHeadElement (), TestElementPtr,
+ MAX_TIME_VALUE, process_flags);
+ }
+ }
+ UnlockElement (hTestElement);
+ return (COLLISION);
+ }
+
+ if (!CollidingElement (ElementPtr))
+ {
+ ElementPtr->state_flags |= COLLISION;
+ UnlockElement (hTestElement);
+ return (COLLISION);
+ }
+ }
+ }
+ }
+
+ UnlockElement (hTestElement);
+ }
+
+ return (ElementPtr->state_flags & COLLISION);
+}
+
+static VIEW_STATE
+PreProcessQueue (SIZE *pscroll_x, SIZE *pscroll_y)
+{
+ SIZE min_reduction, max_reduction;
+ COUNT sides_active;
+ POINT Origin;
+ HELEMENT hElement;
+ COUNT ships_alive;
+
+#ifdef KDEBUG
+ log_add (log_Debug, "PreProcess:");
+#endif
+ sides_active = (battle_counter[0] ? 1 : 0)
+ + (battle_counter[1] ? 1 : 0);
+
+ if (optMeleeScale == TFB_SCALE_STEP)
+ min_reduction = max_reduction = MAX_VIS_REDUCTION + 1;
+ else
+ min_reduction = max_reduction = MAX_ZOOM_OUT + (1 << ZOOM_SHIFT);
+
+ Origin.x = (COORD)(LOG_SPACE_WIDTH >> 1);
+ Origin.y = (COORD)(LOG_SPACE_HEIGHT >> 1);
+
+ hElement = GetHeadElement ();
+ ships_alive = 0;
+ while (hElement != 0)
+ {
+ ELEMENT *ElementPtr;
+ HELEMENT hNextElement;
+
+ LockElement (hElement, &ElementPtr);
+
+ if (!(ElementPtr->state_flags & PRE_PROCESS))
+ PreProcess (ElementPtr);
+ hNextElement = GetSuccElement (ElementPtr);
+
+ if (CollidingElement (ElementPtr)
+ && !(ElementPtr->state_flags & COLLISION))
+ ProcessCollisions (hNextElement, ElementPtr,
+ MAX_TIME_VALUE, PRE_PROCESS);
+
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ {
+ SIZE dx, dy;
+
+ ships_alive++;
+ if (max_reduction > opt_max_zoom_out
+ && min_reduction > opt_max_zoom_out)
+ {
+ Origin.x = DISPLAY_ALIGN (ElementPtr->next.location.x);
+ Origin.y = DISPLAY_ALIGN (ElementPtr->next.location.y);
+ }
+
+ dx = DISPLAY_ALIGN (ElementPtr->next.location.x) - Origin.x;
+ dx = WRAP_DELTA_X (dx);
+ dy = DISPLAY_ALIGN (ElementPtr->next.location.y) - Origin.y;
+ dy = WRAP_DELTA_Y (dy);
+
+ if (sides_active <= 2 || ElementPtr->playerNr == 0)
+ {
+ Origin.x = DISPLAY_ALIGN (Origin.x + (dx >> 1));
+ Origin.y = DISPLAY_ALIGN (Origin.y + (dy >> 1));
+
+ if (dx < 0)
+ dx = -dx;
+ if (dy < 0)
+ dy = -dy;
+ max_reduction = CalcReduction (dx, dy);
+ }
+ else if (max_reduction > opt_max_zoom_out
+ && min_reduction <= opt_max_zoom_out)
+ {
+ Origin.x = DISPLAY_ALIGN (Origin.x + (dx >> 1));
+ Origin.y = DISPLAY_ALIGN (Origin.y + (dy >> 1));
+
+ if (dx < 0)
+ dx = -dx;
+ if (dy < 0)
+ dy = -dy;
+ min_reduction = CalcReduction (dx, dy);
+ }
+ else
+ {
+ SIZE reduction;
+
+ if (dx < 0)
+ dx = -dx;
+ if (dy < 0)
+ dy = -dy;
+ reduction = CalcReduction (dx << 1, dy << 1);
+
+ if (min_reduction > opt_max_zoom_out
+ || reduction < min_reduction)
+ min_reduction = reduction;
+ }
+// log_add (log_Debug, "dx = %d dy = %d min_red = %d max_red = %d",
+// dx, dy, min_reduction, max_reduction);
+ }
+
+ UnlockElement (hElement);
+ hElement = hNextElement;
+ }
+
+ if ((min_reduction > opt_max_zoom_out || min_reduction <= max_reduction)
+ && (min_reduction = max_reduction) > opt_max_zoom_out
+ && (min_reduction = zoom_out) > opt_max_zoom_out)
+ {
+ if (optMeleeScale == TFB_SCALE_STEP)
+ min_reduction = 0;
+ else
+ min_reduction = 1 << ZOOM_SHIFT;
+ }
+
+#ifdef KDEBUG
+ log_add (log_Debug, "PreProcess: exit");
+#endif
+ return (CalcView (&Origin, min_reduction, pscroll_x, pscroll_y, ships_alive));
+}
+
+void
+InsertPrim (PRIM_LINKS *pLinks, COUNT primIndex, COUNT iPI)
+{
+ COUNT Link;
+ PRIM_LINKS PL;
+
+ if (iPI == END_OF_LIST)
+ {
+ Link = GetSuccLink (*pLinks); /* get tail */
+ if (Link == END_OF_LIST)
+ *pLinks = MakeLinks (primIndex, primIndex);
+ else
+ *pLinks = MakeLinks (GetPredLink (*pLinks), primIndex);
+ }
+ else
+ {
+ PL = GetPrimLinks (&DisplayArray[iPI]);
+ if (iPI != GetPredLink (*pLinks)) /* if not the head */
+ Link = GetPredLink (PL);
+ else
+ {
+ Link = END_OF_LIST;
+ *pLinks = MakeLinks (primIndex, GetSuccLink (*pLinks));
+ }
+ SetPrimLinks (&DisplayArray[iPI], primIndex, GetSuccLink (PL));
+ }
+
+ if (Link != END_OF_LIST)
+ {
+ PL = GetPrimLinks (&DisplayArray[Link]);
+ SetPrimLinks (&DisplayArray[Link], GetPredLink (PL), primIndex);
+ }
+ SetPrimLinks (&DisplayArray[primIndex], Link, iPI);
+}
+
+PRIM_LINKS DisplayLinks;
+
+static inline COORD
+CalcDisplayCoord (COORD c, COORD orgc, SIZE reduction)
+{
+ if (optMeleeScale == TFB_SCALE_STEP)
+ { /* old fixed-step zoom style */
+ return (c - orgc) >> reduction;
+ }
+ else
+ { /* new continuous zoom style */
+ return ((c - orgc) << ZOOM_SHIFT) / reduction;
+ }
+}
+
+static void
+PostProcessQueue (VIEW_STATE view_state, SIZE scroll_x,
+ SIZE scroll_y)
+{
+ POINT delta;
+ SIZE reduction;
+ HELEMENT hElement;
+
+#ifdef KDEBUG
+ log_add (log_Debug, "PostProcess:");
+#endif
+ if (optMeleeScale == TFB_SCALE_STEP)
+ reduction = zoom_out + ONE_SHIFT;
+ else
+ reduction = zoom_out << ONE_SHIFT;
+
+ hElement = GetHeadElement ();
+ while (hElement != 0)
+ {
+ ELEMENT_FLAGS state_flags;
+ ELEMENT *ElementPtr;
+ HELEMENT hNextElement;
+
+ LockElement (hElement, &ElementPtr);
+
+ state_flags = ElementPtr->state_flags;
+ if (state_flags & PRE_PROCESS)
+ {
+ if (!(state_flags & COLLISION))
+ ElementPtr->state_flags &= ~DEFY_PHYSICS;
+ else
+ ElementPtr->state_flags &= ~COLLISION;
+
+ if (state_flags & POST_PROCESS)
+ {
+ delta.x = 0;
+ delta.y = 0;
+ }
+ else
+ {
+ delta.x = scroll_x;
+ delta.y = scroll_y;
+ }
+ }
+ else
+ {
+ HELEMENT hPostElement;
+
+ hPostElement = hElement;
+ do
+ {
+ ELEMENT *PostElementPtr;
+
+ LockElement (hPostElement, &PostElementPtr);
+ if (!(PostElementPtr->state_flags & PRE_PROCESS))
+ PreProcess (PostElementPtr);
+ hNextElement = GetSuccElement (PostElementPtr);
+
+ if (CollidingElement (PostElementPtr)
+ && !(PostElementPtr->state_flags & COLLISION))
+ ProcessCollisions (GetHeadElement (), PostElementPtr,
+ MAX_TIME_VALUE, PRE_PROCESS | POST_PROCESS);
+ UnlockElement (hPostElement);
+ hPostElement = hNextElement;
+ } while (hPostElement != 0);
+
+ scroll_x = 0;
+ scroll_y = 0;
+ delta.x = 0;
+ delta.y = 0;
+ /* because these are newly added elements that are
+ * already in adjusted coordinates */
+ state_flags = ElementPtr->state_flags;
+ }
+
+ if (state_flags & DISAPPEARING)
+ {
+ hNextElement = GetSuccElement (ElementPtr);
+ UnlockElement (hElement);
+ RemoveElement (hElement);
+ FreeElement (hElement);
+ }
+ else
+ {
+ GRAPHICS_PRIM ObjType;
+
+ ObjType = GetPrimType (&DisplayArray[ElementPtr->PrimIndex]);
+ if (view_state != VIEW_STABLE
+ || (state_flags & (APPEARING | CHANGING)))
+ {
+ POINT next;
+
+ if (ObjType == LINE_PRIM)
+ {
+ SIZE dx, dy;
+
+ dx = ElementPtr->next.location.x
+ - ElementPtr->current.location.x;
+ dy = ElementPtr->next.location.y
+ - ElementPtr->current.location.y;
+
+ next.x = WRAP_X (ElementPtr->current.location.x + delta.x);
+ next.y = WRAP_Y (ElementPtr->current.location.y + delta.y);
+ DisplayArray[ElementPtr->PrimIndex].Object.Line.first.x =
+ CalcDisplayCoord (next.x, SpaceOrg.x, reduction);
+ DisplayArray[ElementPtr->PrimIndex].Object.Line.first.y =
+ CalcDisplayCoord (next.y, SpaceOrg.y, reduction);
+
+ next.x += dx;
+ next.y += dy;
+ DisplayArray[ElementPtr->PrimIndex].Object.Line.second.x =
+ CalcDisplayCoord (next.x, SpaceOrg.x, reduction);
+ DisplayArray[ElementPtr->PrimIndex].Object.Line.second.y =
+ CalcDisplayCoord (next.y, SpaceOrg.y, reduction);
+ }
+ else
+ {
+ next.x = WRAP_X (ElementPtr->next.location.x + delta.x);
+ next.y = WRAP_Y (ElementPtr->next.location.y + delta.y);
+ DisplayArray[ElementPtr->PrimIndex].Object.Point.x =
+ CalcDisplayCoord (next.x, SpaceOrg.x, reduction);
+ DisplayArray[ElementPtr->PrimIndex].Object.Point.y =
+ CalcDisplayCoord (next.y, SpaceOrg.y, reduction);
+
+ if (ObjType == STAMP_PRIM || ObjType == STAMPFILL_PRIM)
+ {
+ if (view_state == VIEW_CHANGE
+ || (state_flags & (APPEARING | CHANGING)))
+ {
+ COUNT index, scale = GSCALE_IDENTITY;
+
+ if (optMeleeScale == TFB_SCALE_STEP)
+ index = zoom_out;
+ else
+ CALC_ZOOM_STUFF (&index, &scale);
+
+ ElementPtr->next.image.frame = SetEquFrameIndex (
+ ElementPtr->next.image.farray[index],
+ ElementPtr->next.image.frame);
+
+ if (optMeleeScale == TFB_SCALE_TRILINEAR &&
+ index < 2 && scale != GSCALE_IDENTITY)
+ {
+ // enqueues drawcommand to assign next
+ // (smaller) zoom level image as mipmap,
+ // needed for trilinear scaling
+
+ FRAME frame = ElementPtr->next.image.frame;
+ FRAME mmframe = SetEquFrameIndex (
+ ElementPtr->next.image.farray[
+ index + 1], frame);
+
+ // TODO: This is currently hacky, this code
+ // really should not dereference FRAME.
+ // Perhaps make mipmap part of STAMP prim?
+ if (frame && mmframe)
+ {
+ HOT_SPOT mmhs = GetFrameHot (mmframe);
+ TFB_DrawScreen_SetMipmap (frame->image,
+ mmframe->image, mmhs.x, mmhs.y);
+ }
+ }
+ }
+ DisplayArray[ElementPtr->PrimIndex].Object.Stamp.frame =
+ ElementPtr->next.image.frame;
+ }
+ }
+
+ ElementPtr->next.location = next;
+ }
+
+ PostProcess (ElementPtr);
+
+ if (ObjType < NUM_PRIMS)
+ InsertPrim (&DisplayLinks, ElementPtr->PrimIndex, END_OF_LIST);
+
+ hNextElement = GetSuccElement (ElementPtr);
+ UnlockElement (hElement);
+ }
+
+ hElement = hNextElement;
+ }
+#ifdef KDEBUG
+ log_add (log_Debug, "PostProcess: exit");
+#endif
+}
+
+void
+InitDisplayList (void)
+{
+ COUNT i;
+
+ if (optMeleeScale == TFB_SCALE_STEP)
+ {
+ zoom_out = MAX_VIS_REDUCTION + 1;
+ opt_max_zoom_out = MAX_VIS_REDUCTION;
+ }
+ else
+ {
+ zoom_out = MAX_ZOOM_OUT + (1 << ZOOM_SHIFT);
+ opt_max_zoom_out = MAX_ZOOM_OUT;
+ }
+
+ ReinitQueue (&disp_q);
+
+ for (i = 0; i < MAX_DISPLAY_PRIMS; ++i)
+ SetPrimLinks (&DisplayArray[i], END_OF_LIST, i + 1);
+ SetPrimLinks (&DisplayArray[i - 1], END_OF_LIST, END_OF_LIST);
+ DisplayFreeList = 0;
+ DisplayLinks = MakeLinks (END_OF_LIST, END_OF_LIST);
+}
+
+UWORD nth_frame = 0;
+
+void
+RedrawQueue (BOOLEAN clear)
+{
+ SIZE scroll_x, scroll_y;
+ VIEW_STATE view_state;
+
+ SetContext (StatusContext);
+
+ view_state = PreProcessQueue (&scroll_x, &scroll_y);
+ PostProcessQueue (view_state, scroll_x, scroll_y);
+
+ if (optStereoSFX)
+ UpdateSoundPositions ();
+
+ SetContext (SpaceContext);
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE
+ || !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ BYTE skip_frames;
+
+ skip_frames = HIBYTE (nth_frame);
+ if (skip_frames != (BYTE)~0
+ && (skip_frames == 0 || (--nth_frame & 0x00FF) == 0))
+ {
+ nth_frame += skip_frames;
+ if (clear)
+ ClearDrawable (); // this is for BATCH_BUILD_PAGE effect, but not scaled by SetGraphicScale
+
+ if (optMeleeScale != TFB_SCALE_STEP)
+ {
+ COUNT index, scale;
+
+ CALC_ZOOM_STUFF (&index, &scale);
+ SetGraphicScale (scale);
+ }
+
+ DrawBatch (DisplayArray, DisplayLinks, 0);//BATCH_BUILD_PAGE);
+ SetGraphicScale (0);
+ }
+
+ FlushSounds ();
+ }
+ else
+ { // sfx queue needs to be flushed when aborting
+ ProcessSound ((SOUND)~0, NULL);
+ FlushSounds ();
+ }
+
+ DisplayLinks = MakeLinks (END_OF_LIST, END_OF_LIST);
+}
+
+// Set the hTarget field to 0 for all elements in the display list that
+// have hTarget set to ElementPtr.
+void
+Untarget (ELEMENT *ElementPtr)
+{
+ HELEMENT hElement, hNextElement;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ HELEMENT hTarget;
+ ELEMENT *ListPtr;
+
+ LockElement (hElement, &ListPtr);
+ hNextElement = GetSuccElement (ListPtr);
+
+ hTarget = ListPtr->hTarget;
+ if (hTarget)
+ {
+ ELEMENT *TargetElementPtr;
+
+ LockElement (hTarget, &TargetElementPtr);
+ if (TargetElementPtr == ElementPtr)
+ ListPtr->hTarget = 0;
+ UnlockElement (hTarget);
+ }
+
+ UnlockElement (hElement);
+ }
+}
+
+void
+RemoveElement (HLINK hLink)
+{
+ if (optStereoSFX)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hLink, &ElementPtr);
+ if (ElementPtr != NULL)
+ RemoveSoundsForObject(ElementPtr);
+ UnlockElement (hLink);
+ }
+ RemoveQueue (&disp_q, hLink);
+}
+
+
diff --git a/src/uqm/process.h b/src/uqm/process.h
new file mode 100644
index 0000000..d794a2e
--- /dev/null
+++ b/src/uqm/process.h
@@ -0,0 +1,37 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_PROCESS_H_INCL_
+#define UQM_PROCESS_H_INCL_
+
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+#include "element.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern void RedrawQueue (BOOLEAN clear);
+extern void InitDisplayList (void);
+extern void SetUpElement (ELEMENT *ElementPtr);
+extern void InsertPrim (PRIM_LINKS *pLinks, COUNT primIndex, COUNT iPI);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_PROCESS_H_INCL_ */
diff --git a/src/uqm/races.h b/src/uqm/races.h
new file mode 100644
index 0000000..b1a1617
--- /dev/null
+++ b/src/uqm/races.h
@@ -0,0 +1,675 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_RACES_H_
+#define UQM_RACES_H_
+
+#include "types.h"
+#include "libs/compiler.h"
+#include "units.h"
+#include "displist.h"
+
+typedef struct STARSHIP STARSHIP;
+typedef HLINK HSTARSHIP;
+
+#include "element.h"
+#include "libs/sndlib.h"
+#include "libs/reslib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+// TODO: remove RACES_PER_PLAYER remnant of SC1
+#define RACES_PER_PLAYER 7
+#define MAX_SHIPS_PER_SIDE 14
+
+/* SHIP_INFO.ship_flags - ship specific flags */
+/* bits 0 and 1 are now available */
+#define SEEKING_WEAPON (1 << 2)
+#define SEEKING_SPECIAL (1 << 3)
+#define POINT_DEFENSE (1 << 4)
+ /* Ship has some point-defense capabilities */
+#define IMMEDIATE_WEAPON (1 << 5)
+#define CREW_IMMUNE (1 << 6)
+#define FIRES_FORE (1 << 7)
+#define FIRES_RIGHT (1 << 8)
+#define FIRES_AFT (1 << 9)
+#define FIRES_LEFT (1 << 10)
+#define SHIELD_DEFENSE (1 << 11)
+#define DONT_CHASE (1 << 12)
+#define PLAYER_CAPTAIN (1 << 13)
+ /* The protagonist himself is on board. He gets a different color. */
+
+typedef UWORD STATUS_FLAGS;
+
+/* STATUS_FLAGS - heat of battle specific flags */
+#define LEFT (1 << 0)
+#define RIGHT (1 << 1)
+#define THRUST (1 << 2)
+#define WEAPON (1 << 3)
+#define SPECIAL (1 << 4)
+#define LOW_ON_ENERGY (1 << 5)
+#define SHIP_BEYOND_MAX_SPEED (1 << 6)
+#define SHIP_AT_MAX_SPEED (1 << 7)
+#define SHIP_IN_GRAVITY_WELL (1 << 8)
+#define PLAY_VICTORY_DITTY (1 << 9)
+
+/* These track the old resource package orderings for the ship resource indices */
+typedef enum
+{
+ NO_ID,
+ ARILOU_ID,
+ CHMMR_ID,
+ EARTHLING_ID,
+ ORZ_ID,
+ PKUNK_ID,
+ SHOFIXTI_ID,
+ SPATHI_ID,
+ SUPOX_ID,
+ THRADDASH_ID,
+ UTWIG_ID,
+ VUX_ID,
+ YEHAT_ID,
+ MELNORME_ID,
+ DRUUGE_ID,
+ ILWRATH_ID,
+ MYCON_ID,
+ SLYLANDRO_ID,
+ UMGAH_ID,
+ UR_QUAN_ID,
+ ZOQFOTPIK_ID,
+ SYREEN_ID,
+ KOHR_AH_ID,
+ ANDROSYNTH_ID,
+ CHENJESU_ID,
+ MMRNMHRM_ID,
+ LAST_MELEE_ID = MMRNMHRM_ID,
+ SIS_SHIP_ID,
+ SA_MATRA_ID,
+ UR_QUAN_PROBE_ID,
+ NUM_SPECIES_ID
+} SPECIES_ID;
+
+typedef struct captain_stuff
+{
+ RESOURCE captain_rsc;
+ FRAME background;
+ FRAME turn;
+ FRAME thrust;
+ FRAME weapon;
+ FRAME special;
+} CAPTAIN_STUFF;
+
+typedef enum
+{
+ PURSUE = 0,
+ AVOID,
+ ENTICE,
+ NO_MOVEMENT
+} MOVEMENT_STATE;
+
+typedef struct
+{
+ ELEMENT *ObjectPtr;
+ COUNT facing;
+ COUNT which_turn;
+ MOVEMENT_STATE MoveState;
+} EVALUATE_DESC;
+
+typedef void (IntelligenceFunc) (ELEMENT *ShipPtr,
+ EVALUATE_DESC *ObjectsOfConcern, COUNT ConcernCounter);
+typedef struct
+{
+ COUNT ManeuverabilityIndex;
+ COUNT WeaponRange;
+ IntelligenceFunc *intelligence_func;
+} INTEL_STUFF;
+
+typedef struct
+{
+ COUNT max_thrust;
+ COUNT thrust_increment;
+ BYTE energy_regeneration;
+ BYTE weapon_energy_cost;
+ BYTE special_energy_cost;
+ BYTE energy_wait;
+ BYTE turn_wait;
+ BYTE thrust_wait;
+ BYTE weapon_wait;
+ BYTE special_wait;
+ BYTE ship_mass;
+} CHARACTERISTIC_STUFF;
+
+typedef struct
+{
+ UWORD ship_flags;
+ BYTE ship_cost;
+
+ COUNT crew_level;
+ COUNT max_crew;
+ BYTE energy_level;
+ BYTE max_energy;
+
+ RESOURCE race_strings_rsc;
+ RESOURCE icons_rsc;
+ RESOURCE melee_icon_rsc;
+
+ STRING race_strings;
+ FRAME icons;
+ FRAME melee_icon;
+} SHIP_INFO;
+
+typedef struct
+{
+ COUNT strength;
+ POINT known_loc;
+
+#define INFINITE_RADIUS ((COUNT) ~0)
+} FLEET_STUFF;
+
+typedef struct
+{
+ RESOURCE ship_rsc[NUM_VIEWS];
+ RESOURCE weapon_rsc[NUM_VIEWS];
+ RESOURCE special_rsc[NUM_VIEWS];
+ CAPTAIN_STUFF captain_control;
+ RESOURCE victory_ditty_rsc;
+ RESOURCE ship_sounds_rsc;
+
+ FRAME ship[NUM_VIEWS];
+ FRAME weapon[NUM_VIEWS];
+ FRAME special[NUM_VIEWS];
+ MUSIC_REF victory_ditty;
+ SOUND ship_sounds;
+} DATA_STUFF;
+
+
+typedef struct race_desc RACE_DESC;
+
+typedef void (PREPROCESS_FUNC) (ELEMENT *ElementPtr);
+typedef void (POSTPROCESS_FUNC) (ELEMENT *ElementPtr);
+typedef COUNT (INIT_WEAPON_FUNC) (ELEMENT *ElementPtr, HELEMENT Weapon[]);
+typedef void (UNINIT_FUNC) (RACE_DESC *pRaceDesc);
+
+struct race_desc
+{
+ SHIP_INFO ship_info _ALIGNED_ANY;
+ FLEET_STUFF fleet _ALIGNED_ANY;
+ CHARACTERISTIC_STUFF characteristics _ALIGNED_ANY;
+ DATA_STUFF ship_data _ALIGNED_ANY;
+ INTEL_STUFF cyborg_control _ALIGNED_ANY;
+
+ UNINIT_FUNC *uninit_func;
+ PREPROCESS_FUNC *preprocess_func;
+ POSTPROCESS_FUNC *postprocess_func;
+ INIT_WEAPON_FUNC *init_weapon_func;
+
+ void* data; // private ship data, ship code owns this
+
+ void *CodeRef;
+};
+
+#define SHIP_BASE_COMMON \
+ /* LINK elements; must be first */ \
+ HLINK pred; \
+ HLINK succ; \
+ \
+ SPECIES_ID SpeciesID; \
+ BYTE captains_name_index \
+ /* Also used in full-game to detect if a STARSHIP is an escort
+ * or the flagship (captains_name_index == 0) */
+
+typedef struct
+{
+ SHIP_BASE_COMMON;
+} SHIP_BASE;
+
+
+struct STARSHIP
+{
+ SHIP_BASE_COMMON;
+
+ RACE_DESC *RaceDescPtr;
+
+ // Ship information
+ COUNT crew_level;
+ // In full-game battles: crew left
+ // In SuperMelee: irrelevant
+ COUNT max_crew;
+ BYTE ship_cost;
+ // In Super Melee ship queue: ship cost
+ // In full-game: irrelevant
+ COUNT index;
+ // original queue index
+ STRING race_strings;
+ FRAME icons;
+
+ // Battle states
+ BYTE weapon_counter;
+ // In battle: frames left before primary weapon can be used
+ BYTE special_counter;
+ // In battle: frames left before special can be used
+ BYTE energy_counter;
+ // In battle: frames left before energy regeneration
+
+ BYTE ship_input_state;
+ STATUS_FLAGS cur_status_flags;
+ STATUS_FLAGS old_status_flags;
+
+ HELEMENT hShip;
+ COUNT ShipFacing;
+
+ SIZE playerNr;
+ // 0: bottom player; In full-game: the human player (RPG)
+ // 1: top player; In full-game: the NPC opponent
+ // -1: neutral; this should currently never happen (asserts)
+ BYTE control;
+ // HUMAN, COMPUTER or NETWORK control flags, see intel.h
+};
+
+#define RPG_PLAYER_NUM 0
+#define NPC_PLAYER_NUM 1
+
+static inline STARSHIP *
+LockStarShip (const QUEUE *pq, HSTARSHIP h)
+{
+ assert (GetLinkSize (pq) == sizeof (STARSHIP));
+ return (STARSHIP *) LockLink (pq, h);
+}
+
+#define UnlockStarShip(pq, h) UnlockLink (pq, h)
+#define FreeStarShip(pq, h) FreeLink (pq, h)
+
+
+typedef HLINK HSHIPFRAG;
+
+typedef struct
+{
+ SHIP_BASE_COMMON;
+
+ BYTE race_id;
+ BYTE index;
+ COUNT crew_level;
+ /* For ships in npc_built_ship_q, the value INFINITE_FLEET for
+ * crew_level indicates an infinite number of ships. */
+ COUNT max_crew;
+
+ BYTE energy_level;
+ BYTE max_energy;
+ // XXX: energy_level and max_energy are unused. We save and load
+ // them, but otherwise nothing needs them atm.
+
+ STRING race_strings;
+ FRAME icons;
+ FRAME melee_icon; /* Only used by Shipyard */
+
+#define INFINITE_FLEET ((COUNT) ~0)
+} SHIP_FRAGMENT;
+
+static inline SHIP_FRAGMENT *
+LockShipFrag (const QUEUE *pq, HSHIPFRAG h)
+{
+ assert (GetLinkSize (pq) == sizeof (SHIP_FRAGMENT));
+ return (SHIP_FRAGMENT *) LockLink (pq, h);
+}
+
+#define UnlockShipFrag(pq, h) UnlockLink (pq, h)
+#define FreeShipFrag(pq, h) FreeLink (pq, h)
+
+
+typedef HLINK HFLEETINFO;
+
+typedef struct
+{
+ // LINK elements; must be first
+ HFLEETINFO pred;
+ HFLEETINFO succ;
+
+ SPECIES_ID SpeciesID;
+
+ UWORD allied_state; /* GOOD_GUY, BAD_GUY or DEAD_GUY */
+ BYTE days_left; /* Days left before the fleet reachers 'dest_loc'. */
+ BYTE growth_fract;
+ COUNT crew_level;
+ COUNT max_crew;
+ BYTE growth;
+ BYTE max_energy;
+ POINT loc; /* Location of the fleet (center) */
+
+ STRING race_strings;
+ /* Race specific strings, see doc/devel/racestrings. */
+ FRAME icons;
+ FRAME melee_icon;
+
+ COUNT actual_strength;
+ /* Measure for the size of the sphere of influence.
+ * 0 if there is none and no ships will be generated.
+ * '(COUNT) ~0' if there is none, and the ship generation
+ * is handled separately. */
+ COUNT known_strength;
+ /* Measure for the size of the sphere of influence when last
+ * checked the starmap.
+ * 0 if the race's SoI is not known. */
+ POINT known_loc;
+ /* Location of the SoI (center) when last checked
+ * the starmap. */
+
+ BYTE growth_err_term;
+ BYTE func_index;
+ /* Function index defined in clock.h (the same as in SetEvent())
+ * for the function to call when the fleet reaches 'dest_loc'.
+ * '(BYTE) ~0' means no function to call. */
+ POINT dest_loc;
+ /* Location to which the fleet (center) is moving. */
+
+} FLEET_INFO;
+
+// Values for FLEET_INFO.allied_state
+enum
+{
+ DEAD_GUY = 0, // Race is extinct
+ GOOD_GUY, // Race is allied with the player
+ BAD_GUY, // Race is not allied with the player
+};
+
+static inline FLEET_INFO *
+LockFleetInfo (const QUEUE *pq, HFLEETINFO h)
+{
+ assert (GetLinkSize (pq) == sizeof (FLEET_INFO));
+ return (FLEET_INFO *) LockLink (pq, h);
+}
+
+#define UnlockFleetInfo(pq, h) UnlockLink (pq, h)
+
+enum
+{
+ ARILOU_SHIP,
+ CHMMR_SHIP,
+ HUMAN_SHIP,
+ ORZ_SHIP,
+ PKUNK_SHIP,
+ SHOFIXTI_SHIP,
+ SPATHI_SHIP,
+ SUPOX_SHIP,
+ THRADDASH_SHIP,
+ UTWIG_SHIP,
+ VUX_SHIP,
+ YEHAT_SHIP,
+ MELNORME_SHIP,
+ DRUUGE_SHIP,
+ ILWRATH_SHIP,
+ MYCON_SHIP,
+ SLYLANDRO_SHIP,
+ UMGAH_SHIP,
+ URQUAN_SHIP,
+ ZOQFOTPIK_SHIP,
+
+ SYREEN_SHIP,
+ BLACK_URQUAN_SHIP,
+ YEHAT_REBEL_SHIP,
+ URQUAN_DRONE_SHIP,
+ SAMATRA_SHIP = URQUAN_DRONE_SHIP,
+
+ NUM_AVAILABLE_RACES
+};
+
+#define RACE_COMMUNICATION \
+ ARILOU_CONVERSATION, /* ARILOU_SHIP */ \
+ CHMMR_CONVERSATION, /* CHMMR_SHIP */ \
+ INVALID_CONVERSATION, /* HUMAN_SHIP */ \
+ ORZ_CONVERSATION, /* ORZ_SHIP */ \
+ PKUNK_CONVERSATION, /* PKUNK_SHIP */ \
+ SHOFIXTI_CONVERSATION, /* SHOFIXTI_SHIP */ \
+ SPATHI_CONVERSATION, /* SPATHI_SHIP */ \
+ SUPOX_CONVERSATION, /* SUPOX_SHIP */ \
+ THRADD_CONVERSATION, /* THRADDASH_SHIP */ \
+ UTWIG_CONVERSATION, /* UTWIG_SHIP */ \
+ VUX_CONVERSATION, /* VUX_SHIP */ \
+ YEHAT_CONVERSATION, /* YEHAT_SHIP */ \
+ MELNORME_CONVERSATION, /* MELNORME_SHIP */ \
+ DRUUGE_CONVERSATION, /* DRUUGE_SHIP */ \
+ ILWRATH_CONVERSATION, /* ILWRATH_SHIP */ \
+ MYCON_CONVERSATION, /* MYCON_SHIP */ \
+ SLYLANDRO_CONVERSATION, /* SLYLANDRO_SHIP */ \
+ UMGAH_CONVERSATION, /* UMGAH_SHIP */ \
+ URQUAN_CONVERSATION, /* URQUAN_SHIP */ \
+ ZOQFOTPIK_CONVERSATION, /* ZOQFOTPIK_SHIP */ \
+ INVALID_CONVERSATION, /* SYREEN_SHIP */ \
+ BLACKURQ_CONVERSATION, /* BLACK_URQUAN_SHIP */ \
+ YEHAT_REBEL_CONVERSATION, /* YEHAT_REBEL_SHIP */ \
+ URQUAN_DRONE_CONVERSATION, /* URQUAN_DRONE_SHIP */
+
+#define RACE_SHIP_FOR_COMM \
+ ARILOU_SHIP, /* ARILOU_CONVERSATION */ \
+ CHMMR_SHIP, /* CHMMR_CONVERSATION */ \
+ HUMAN_SHIP, /* COMMANDER_CONVERSATION */ \
+ ORZ_SHIP, /* ORZ_CONVERSATION */ \
+ PKUNK_SHIP, /* PKUNK_CONVERSATION */ \
+ SHOFIXTI_SHIP, /* SHOFIXTI_CONVERSATION */ \
+ SPATHI_SHIP, /* SPATHI_CONVERSATION */ \
+ SUPOX_SHIP, /* SUPOX_CONVERSATION */ \
+ THRADDASH_SHIP, /* THRADD_CONVERSATION */ \
+ UTWIG_SHIP, /* UTWIG_CONVERSATION */ \
+ VUX_SHIP, /* VUX_CONVERSATION */ \
+ YEHAT_SHIP, /* YEHAT_CONVERSATION */ \
+ MELNORME_SHIP, /* MELNORME_CONVERSATION */ \
+ DRUUGE_SHIP, /* DRUUGE_CONVERSATION */ \
+ ILWRATH_SHIP, /* ILWRATH_CONVERSATION */ \
+ MYCON_SHIP, /* MYCON_CONVERSATION */ \
+ SLYLANDRO_SHIP, /* SLYLANDRO_CONVERSATION */ \
+ UMGAH_SHIP, /* UMGAH_CONVERSATION */ \
+ URQUAN_SHIP, /* URQUAN_CONVERSATION */ \
+ ZOQFOTPIK_SHIP, /* ZOQFOTPIK_CONVERSATION */ \
+ SYREEN_SHIP, /* SYREEN_CONVERSATION */ \
+ BLACK_URQUAN_SHIP, /* BLACKURQ_CONVERSATION */ \
+ UMGAH_SHIP, /* TALKING_PET_CONVERSATION */ \
+ SLYLANDRO_SHIP, /* SLYLANDRO_HOME_CONVERSATION */ \
+ URQUAN_DRONE_SHIP, /* URQUAN_DRONE_CONVERSATION */ \
+ YEHAT_SHIP, /* YEHAT_REBEL_CONVERSATION */ \
+ HUMAN_SHIP /* INVALID_CONVERSATION */
+
+#define RACE_SHIP_COST \
+ 1600, /* ARILOU_SHIP */ \
+ 3000, /* CHMMR_SHIP */ \
+ 1100, /* HUMAN_SHIP */ \
+ 2300, /* ORZ_SHIP */ \
+ 2000, /* PKUNK_SHIP */ \
+ 500, /* SHOFIXTI_SHIP */ \
+ 1800, /* SPATHI_SHIP */ \
+ 1600, /* SUPOX_SHIP */ \
+ 1000, /* THRADDASH_SHIP */ \
+ 2200, /* UTWIG_SHIP */ \
+ 1200, /* VUX_SHIP */ \
+ 2300, /* YEHAT_SHIP */ \
+ 3600, /* MELNORME_SHIP */ \
+ 1700, /* DRUUGE_SHIP */ \
+ 1000, /* ILWRATH_SHIP */ \
+ 2100, /* MYCON_SHIP */ \
+ 4400, /* SLYLANDRO_SHIP */ \
+ 700, /* UMGAH_SHIP */ \
+ 3000, /* URQUAN_SHIP */ \
+ 600, /* ZOQFOTPIK_SHIP */ \
+ 1300, /* SYREEN_SHIP */ \
+ 3000, /* BLACK_URQUAN_SHIP */ \
+ 2300, /* YEHAT_REBEL_SHIP */
+
+#define LOG_TO_IP(s) ((s) << 1)
+#define RACE_IP_SPEED \
+ LOG_TO_IP (40), /* ARILOU_SHIP */ \
+ LOG_TO_IP (27), /* CHMMR_SHIP */ \
+ LOG_TO_IP (24), /* HUMAN_SHIP */ \
+ LOG_TO_IP (40), /* ORZ_SHIP */ \
+ LOG_TO_IP (40), /* PKUNK_SHIP */ \
+ LOG_TO_IP (35), /* SHOFIXTI_SHIP */ \
+ LOG_TO_IP (48), /* SPATHI_SHIP */ \
+ LOG_TO_IP (40), /* SUPOX_SHIP */ \
+ LOG_TO_IP (28), /* THRADDASH_SHIP */ \
+ LOG_TO_IP (30), /* UTWIG_SHIP */ \
+ LOG_TO_IP (21), /* VUX_SHIP */ \
+ LOG_TO_IP (30), /* YEHAT_SHIP */ \
+ LOG_TO_IP (40), /* MELNORME_SHIP */ \
+ LOG_TO_IP (20), /* DRUUGE_SHIP */ \
+ LOG_TO_IP (25), /* ILWRATH_SHIP */ \
+ LOG_TO_IP (27), /* MYCON_SHIP */ \
+ LOG_TO_IP (60), /* SLYLANDRO_SHIP */ \
+ LOG_TO_IP (18), /* UMGAH_SHIP */ \
+ LOG_TO_IP (30), /* URQUAN_SHIP */ \
+ LOG_TO_IP (40), /* ZOQFOTPIK_SHIP */ \
+ LOG_TO_IP (36), /* SYREEN_SHIP */ \
+ LOG_TO_IP (30), /* BLACK_URQUAN_SHIP */ \
+ LOG_TO_IP (30), /* YEHAT_REBEL_SHIP */ \
+ LOG_TO_IP (90), /* URQUAN_DRONE_SHIP */
+
+#define LOG_TO_HYPER(s) (WORLD_TO_VELOCITY (s) >> 1)
+#define RACE_HYPER_SPEED \
+ LOG_TO_HYPER (40), /* ARILOU_SHIP */ \
+ LOG_TO_HYPER (27), /* CHMMR_SHIP */ \
+ LOG_TO_HYPER (24), /* HUMAN_SHIP */ \
+ LOG_TO_HYPER (40), /* ORZ_SHIP */ \
+ LOG_TO_HYPER (40), /* PKUNK_SHIP */ \
+ LOG_TO_HYPER (35), /* SHOFIXTI_SHIP */ \
+ LOG_TO_HYPER (48), /* SPATHI_SHIP */ \
+ LOG_TO_HYPER (40), /* SUPOX_SHIP */ \
+ LOG_TO_HYPER (50), /* THRADDASH_SHIP */ \
+ LOG_TO_HYPER (30), /* UTWIG_SHIP */ \
+ LOG_TO_HYPER (21), /* VUX_SHIP */ \
+ LOG_TO_HYPER (30), /* YEHAT_SHIP */ \
+ LOG_TO_HYPER (40), /* MELNORME_SHIP */ \
+ LOG_TO_HYPER (20), /* DRUUGE_SHIP */ \
+ LOG_TO_HYPER (25), /* ILWRATH_SHIP */ \
+ LOG_TO_HYPER (27), /* MYCON_SHIP */ \
+ LOG_TO_HYPER (60), /* SLYLANDRO_SHIP */ \
+ LOG_TO_HYPER (18), /* UMGAH_SHIP */ \
+ LOG_TO_HYPER (30), /* URQUAN_SHIP */ \
+ LOG_TO_HYPER (40), /* ZOQFOTPIK_SHIP */ \
+ LOG_TO_HYPER (36), /* SYREEN_SHIP */ \
+ LOG_TO_HYPER (30), /* BLACK_URQUAN_SHIP */ \
+ LOG_TO_HYPER (30), /* YEHAT_REBEL_SHIP */
+
+#define RACE_HYPERSPACE_PERCENT \
+ 20, /* ARILOU_SHIP */ \
+ 0, /* CHMMR_SHIP */ \
+ 0, /* HUMAN_SHIP */ \
+ 20, /* ORZ_SHIP */ \
+ 40, /* PKUNK_SHIP */ \
+ 0, /* SHOFIXTI_SHIP */ \
+ 20, /* SPATHI_SHIP */ \
+ 40, /* SUPOX_SHIP */ \
+ 60, /* THRADDASH_SHIP */ \
+ 40, /* UTWIG_SHIP */ \
+ 40, /* VUX_SHIP */ \
+ 60, /* YEHAT_SHIP */ \
+ 0, /* MELNORME_SHIP */ \
+ 30, /* DRUUGE_SHIP */ \
+ 60, /* ILWRATH_SHIP */ \
+ 40, /* MYCON_SHIP */ \
+ 2, /* SLYLANDRO_SHIP */ \
+ 30, /* UMGAH_SHIP */ \
+ 70, /* URQUAN_SHIP */ \
+ 0, /* ZOQFOTPIK_SHIP */ \
+ 0, /* SYREEN_SHIP */ \
+ 70, /* BLACK_URQUAN_SHIP */ \
+ 60, /* YEHAT_REBEL_SHIP */ \
+ 0, /* URQUAN_DRONE_SHIP */
+
+#define RACE_INTERPLANETARY_PERCENT \
+ 0, /* ARILOU_SHIP */ \
+ 0, /* CHMMR_SHIP */ \
+ 0, /* HUMAN_SHIP */ \
+ 20, /* ORZ_SHIP */ \
+ 20, /* PKUNK_SHIP */ \
+ 0, /* SHOFIXTI_SHIP */ \
+ 10, /* SPATHI_SHIP */ \
+ 20, /* SUPOX_SHIP */ \
+ 20, /* THRADDASH_SHIP */ \
+ 20, /* UTWIG_SHIP */ \
+ 20, /* VUX_SHIP */ \
+ 40, /* YEHAT_SHIP */ \
+ 0, /* MELNORME_SHIP */ \
+ 20, /* DRUUGE_SHIP */ \
+ 60, /* ILWRATH_SHIP */ \
+ 20, /* MYCON_SHIP */ \
+ 5, /* SLYLANDRO_SHIP */ \
+ 20, /* UMGAH_SHIP */ \
+ 40, /* URQUAN_SHIP */ \
+ 0, /* ZOQFOTPIK_SHIP */ \
+ 0, /* SYREEN_SHIP */ \
+ 40, /* BLACK_URQUAN_SHIP */ \
+ 40, /* YEHAT_REBEL_SHIP */ \
+ 0, /* URQUAN_DRONE_SHIP */
+
+// How many ships will an encounter consist of.
+// The first number specifies the minimum, the second the maximum.
+// The chance is 50% for each ship past the minimum to be present.
+#define RACE_ENCOUNTER_MAKEUP \
+ MAKE_BYTE (1, 5), /* ARILOU_SHIP */ \
+ 0, /* CHMMR_SHIP */ \
+ 0, /* HUMAN_SHIP */ \
+ MAKE_BYTE (1, 5), /* ORZ_SHIP */ \
+ MAKE_BYTE (1, 5), /* PKUNK_SHIP */ \
+ 0, /* SHOFIXTI_SHIP */ \
+ MAKE_BYTE (1, 5), /* SPATHI_SHIP */ \
+ MAKE_BYTE (1, 5), /* SUPOX_SHIP */ \
+ MAKE_BYTE (1, 5), /* THRADDASH_SHIP */ \
+ MAKE_BYTE (1, 5), /* UTWIG_SHIP */ \
+ MAKE_BYTE (1, 5), /* VUX_SHIP */ \
+ MAKE_BYTE (1, 5), /* YEHAT_SHIP */ \
+ MAKE_BYTE (1, 1), /* MELNORME_SHIP */ \
+ MAKE_BYTE (1, 5), /* DRUUGE_SHIP */ \
+ MAKE_BYTE (1, 5), /* ILWRATH_SHIP */ \
+ MAKE_BYTE (1, 5), /* MYCON_SHIP */ \
+ MAKE_BYTE (1, 1), /* SLYLANDRO_SHIP */ \
+ MAKE_BYTE (1, 5), /* UMGAH_SHIP */ \
+ MAKE_BYTE (1, 5), /* URQUAN_SHIP */ \
+ MAKE_BYTE (1, 5), /* ZOQFOTPIK_SHIP */ \
+ 0, /* SYREEN_SHIP */ \
+ MAKE_BYTE (1, 5), /* BLACK_URQUAN_SHIP */ \
+ MAKE_BYTE (1, 5), /* YEHAT_REBEL_SHIP */
+
+#define RACE_COLORS \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x10), 0x53), /* ARILOU_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x00), 0x00), /* CHMMR_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x01, 0x1f), 0x4D), /* HUMAN_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0E, 0x00, 0x0E), 0x36), /* ORZ_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x06, 0x08), 0x62), /* PKUNK_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x00), 0x00), /* SHOFIXTI_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0C, 0x05, 0x00), 0x76), /* SPATHI_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0C, 0x05, 0x00), 0x76), /* SUPOX_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x06, 0x08), 0x62), /* THRADDASH_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x06, 0x08), 0x62), /* UTWIG_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x10), 0x53), /* VUX_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0A, 0x00, 0x11), 0x3D), /* YEHAT_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x06, 0x08), 0x62), /* MELNORME_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D), /* DRUUGE_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0E, 0x00, 0x0E), 0x36), /* ILWRATH_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0E, 0x00, 0x0E), 0x36), /* MYCON_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0C, 0x05, 0x00), 0x76), /* SLYLANDRO_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0A, 0x00, 0x11), 0x3D), /* UMGAH_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x08, 0x00), 0x6E), /* URQUAN_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D), /* ZOQFOTPIK_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x00), 0x00), /* SYREEN_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x06, 0x06, 0x06), 0x20), /* BLACK_URQUAN_SHIP */ \
+ BUILD_COLOR (MAKE_RGB15_INIT (0x14, 0x07, 0x1F), 0x39), /* YEHAT_REBEL_SHIP */
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_RACES_H_ */
diff --git a/src/uqm/resinst.h b/src/uqm/resinst.h
new file mode 100644
index 0000000..c876abc
--- /dev/null
+++ b/src/uqm/resinst.h
@@ -0,0 +1,24 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "ikey_con.h"
+#include "igfxres.h"
+#include "ifontres.h"
+#include "istrtab.h"
+#include "isndres.h"
+#include "imusicre.h"
diff --git a/src/uqm/restart.c b/src/uqm/restart.c
new file mode 100644
index 0000000..f52e753
--- /dev/null
+++ b/src/uqm/restart.c
@@ -0,0 +1,413 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "restart.h"
+
+#include "colors.h"
+#include "controls.h"
+#include "credits.h"
+#include "starmap.h"
+#include "fmv.h"
+#include "menustat.h"
+#include "gamestr.h"
+#include "globdata.h"
+#include "intel.h"
+#include "supermelee/melee.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "save.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "setupmenu.h"
+#include "util.h"
+#include "starcon.h"
+#include "uqmversion.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/inplib.h"
+
+
+enum
+{
+ START_NEW_GAME = 0,
+ LOAD_SAVED_GAME,
+ PLAY_SUPER_MELEE,
+ SETUP_GAME,
+ QUIT_GAME
+};
+
+// Draw the full restart menu. Nothing is done with selections.
+static void
+DrawRestartMenuGraphic (MENU_STATE *pMS)
+{
+ RECT r;
+ STAMP s;
+ TEXT t;
+ UNICODE buf[64];
+
+ s.frame = pMS->CurFrame;
+ GetFrameRect (s.frame, &r);
+ s.origin.x = (SCREEN_WIDTH - r.extent.width) >> 1;
+ s.origin.y = (SCREEN_HEIGHT - r.extent.height) >> 1;
+
+ SetContextBackGroundColor (BLACK_COLOR);
+ BatchGraphics ();
+ ClearDrawable ();
+ FlushColorXForms ();
+ DrawStamp (&s);
+
+ // Put the version number in the bottom right corner.
+ SetContextFont (TinyFont);
+ t.pStr = buf;
+ t.baseline.x = SCREEN_WIDTH - 3;
+ t.baseline.y = SCREEN_HEIGHT - 2;
+ t.align = ALIGN_RIGHT;
+ t.CharCount = (COUNT)~0;
+ sprintf (buf, "v%d.%d.%d%s", UQM_MAJOR_VERSION, UQM_MINOR_VERSION,
+ UQM_PATCH_VERSION, UQM_EXTRA_VERSION);
+ SetContextForeGroundColor (WHITE_COLOR);
+ font_DrawText (&t);
+
+ UnbatchGraphics ();
+}
+
+static void
+DrawRestartMenu (MENU_STATE *pMS, BYTE NewState, FRAME f)
+{
+ POINT origin;
+ origin.x = 0;
+ origin.y = 0;
+ Flash_setOverlay(pMS->flashContext,
+ &origin, SetAbsFrameIndex (f, NewState + 1));
+}
+
+static BOOLEAN
+DoRestart (MENU_STATE *pMS)
+{
+ static TimeCount LastInputTime;
+ static TimeCount InactTimeOut;
+ TimeCount TimeIn = GetTimeCounter ();
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (pMS->Initialized)
+ Flash_process(pMS->flashContext);
+
+ if (!pMS->Initialized)
+ {
+ if (pMS->hMusic)
+ {
+ StopMusic ();
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+ pMS->hMusic = LoadMusic (MAINMENU_MUSIC);
+ InactTimeOut = (pMS->hMusic ? 120 : 20) * ONE_SECOND;
+ pMS->flashContext = Flash_createOverlay (ScreenContext,
+ NULL, NULL);
+ Flash_setMergeFactors (pMS->flashContext, -3, 3, 16);
+ Flash_setSpeed (pMS->flashContext, (6 * ONE_SECOND) / 16, 0,
+ (6 * ONE_SECOND) / 16, 0);
+ Flash_setFrameTime (pMS->flashContext, ONE_SECOND / 16);
+ Flash_setState(pMS->flashContext, FlashState_fadeIn,
+ (3 * ONE_SECOND) / 16);
+ DrawRestartMenu (pMS, pMS->CurState, pMS->CurFrame);
+ Flash_start (pMS->flashContext);
+ PlayMusic (pMS->hMusic, TRUE, 1);
+ LastInputTime = GetTimeCounter ();
+ pMS->Initialized = TRUE;
+
+ SleepThreadUntil (FadeScreen (FadeAllToColor, ONE_SECOND / 2));
+ }
+ else if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ switch (pMS->CurState)
+ {
+ case LOAD_SAVED_GAME:
+ LastActivity = CHECK_LOAD;
+ GLOBAL (CurrentActivity) = IN_INTERPLANETARY;
+ break;
+ case START_NEW_GAME:
+ LastActivity = CHECK_LOAD | CHECK_RESTART;
+ GLOBAL (CurrentActivity) = IN_INTERPLANETARY;
+ break;
+ case PLAY_SUPER_MELEE:
+ GLOBAL (CurrentActivity) = SUPER_MELEE;
+ break;
+ case SETUP_GAME:
+ Flash_pause(pMS->flashContext);
+ Flash_setState(pMS->flashContext, FlashState_fadeIn,
+ (3 * ONE_SECOND) / 16);
+ SetupMenu ();
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN,
+ MENU_SOUND_SELECT);
+ LastInputTime = GetTimeCounter ();
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+ DrawRestartMenuGraphic (pMS);
+ ScreenTransition (3, NULL);
+ DrawRestartMenu (pMS, pMS->CurState, pMS->CurFrame);
+ Flash_continue(pMS->flashContext);
+ UnbatchGraphics ();
+ return TRUE;
+ case QUIT_GAME:
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2));
+ GLOBAL (CurrentActivity) = CHECK_ABORT;
+ break;
+ }
+
+ Flash_pause(pMS->flashContext);
+
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_UP] ||
+ PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ BYTE NewState;
+
+ NewState = pMS->CurState;
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (NewState == START_NEW_GAME)
+ NewState = QUIT_GAME;
+ else
+ --NewState;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (NewState == QUIT_GAME)
+ NewState = START_NEW_GAME;
+ else
+ ++NewState;
+ }
+ if (NewState != pMS->CurState)
+ {
+ BatchGraphics ();
+ DrawRestartMenu (pMS, NewState, pMS->CurFrame);
+ UnbatchGraphics ();
+ pMS->CurState = NewState;
+ }
+
+ LastInputTime = GetTimeCounter ();
+ }
+ else if (PulsedInputState.menu[KEY_MENU_LEFT] ||
+ PulsedInputState.menu[KEY_MENU_RIGHT])
+ { // Does nothing, but counts as input for timeout purposes
+ LastInputTime = GetTimeCounter ();
+ }
+ else if (MouseButtonDown)
+ {
+ Flash_pause(pMS->flashContext);
+ DoPopupWindow (GAME_STRING (MAINMENU_STRING_BASE + 54));
+ // Mouse not supported message
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN, MENU_SOUND_SELECT);
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+ DrawRestartMenuGraphic (pMS);
+ DrawRestartMenu (pMS, pMS->CurState, pMS->CurFrame);
+ ScreenTransition (3, NULL);
+ UnbatchGraphics ();
+ Flash_continue(pMS->flashContext);
+
+ LastInputTime = GetTimeCounter ();
+ }
+ else
+ { // No input received, check if timed out
+ if (GetTimeCounter () - LastInputTime > InactTimeOut)
+ {
+ SleepThreadUntil (FadeMusic (0, ONE_SECOND));
+ StopMusic ();
+ FadeMusic (NORMAL_VOLUME, 0);
+
+ GLOBAL (CurrentActivity) = (ACTIVITY)~0;
+ return FALSE;
+ }
+ }
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+static BOOLEAN
+RestartMenu (MENU_STATE *pMS)
+{
+ TimeCount TimeOut;
+
+ ReinitQueue (&race_q[0]);
+ ReinitQueue (&race_q[1]);
+
+ SetContext (ScreenContext);
+
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ if (GLOBAL_SIS (CrewEnlisted) == (COUNT)~0
+ && GET_GAME_STATE (UTWIG_BOMB_ON_SHIP)
+ && !GET_GAME_STATE (UTWIG_BOMB))
+ { // player blew himself up with Utwig bomb
+ SET_GAME_STATE (UTWIG_BOMB_ON_SHIP, 0);
+
+ SleepThreadUntil (FadeScreen (FadeAllToWhite, ONE_SECOND / 8)
+ + ONE_SECOND / 60);
+ SetContextBackGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F));
+ ClearDrawable ();
+ FlushColorXForms ();
+
+ TimeOut = ONE_SECOND / 8;
+ }
+ else
+ {
+ TimeOut = ONE_SECOND / 2;
+
+ if (LOBYTE (LastActivity) == WON_LAST_BATTLE)
+ {
+ GLOBAL (CurrentActivity) = WON_LAST_BATTLE;
+ Victory ();
+ Credits (TRUE);
+
+ FreeGameData ();
+
+ GLOBAL (CurrentActivity) = CHECK_ABORT;
+ }
+ }
+
+ LastActivity = 0;
+ NextActivity = 0;
+
+ // TODO: This fade is not always necessary, especially after a splash
+ // screen. It only makes a user wait.
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, TimeOut));
+ if (TimeOut == ONE_SECOND / 8)
+ SleepThread (ONE_SECOND * 3);
+
+ pMS->CurFrame = CaptureDrawable (LoadGraphic (RESTART_PMAP_ANIM));
+
+ DrawRestartMenuGraphic (pMS);
+ GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN, MENU_SOUND_SELECT);
+ SetDefaultMenuRepeatDelay ();
+ DoInput (pMS, TRUE);
+
+ StopMusic ();
+ if (pMS->hMusic)
+ {
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+
+ Flash_terminate (pMS->flashContext);
+ pMS->flashContext = 0;
+ DestroyDrawable (ReleaseDrawable (pMS->CurFrame));
+ pMS->CurFrame = 0;
+
+ if (GLOBAL (CurrentActivity) == (ACTIVITY)~0)
+ return (FALSE); // timed out
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return (FALSE); // quit
+
+ TimeOut = FadeScreen (FadeAllToBlack, ONE_SECOND / 2);
+
+ SleepThreadUntil (TimeOut);
+ FlushColorXForms ();
+
+ SeedRandomNumbers ();
+
+ return (LOBYTE (GLOBAL (CurrentActivity)) != SUPER_MELEE);
+}
+
+static BOOLEAN
+TryStartGame (void)
+{
+ MENU_STATE MenuState;
+
+ LastActivity = GLOBAL (CurrentActivity);
+ GLOBAL (CurrentActivity) = 0;
+
+ memset (&MenuState, 0, sizeof (MenuState));
+ MenuState.InputFunc = DoRestart;
+
+ while (!RestartMenu (&MenuState))
+ { // spin until a game is started or loaded
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE &&
+ !(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ FreeGameData ();
+ Melee ();
+ MenuState.Initialized = FALSE;
+ }
+ else if (GLOBAL (CurrentActivity) == (ACTIVITY)~0)
+ { // timed out
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2));
+ return (FALSE);
+ }
+ else if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ { // quit
+ return (FALSE);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOLEAN
+StartGame (void)
+{
+ do
+ {
+ while (!TryStartGame ())
+ {
+ if (GLOBAL (CurrentActivity) == (ACTIVITY)~0)
+ { // timed out
+ GLOBAL (CurrentActivity) = 0;
+ SplashScreen (0);
+ Credits (FALSE);
+ }
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return (FALSE); // quit
+ }
+
+ if (LastActivity & CHECK_RESTART)
+ { // starting a new game
+ Introduction ();
+ }
+
+ } while (GLOBAL (CurrentActivity) & CHECK_ABORT);
+
+ {
+ extern STAR_DESC starmap_array[];
+ extern const BYTE element_array[];
+ extern const PlanetFrame planet_array[];
+
+ star_array = starmap_array;
+ Elements = element_array;
+ PlanData = planet_array;
+ }
+
+ PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING;
+ PlayerControl[1] = COMPUTER_CONTROL | AWESOME_RATING;
+
+ return (TRUE);
+}
+
diff --git a/src/uqm/restart.h b/src/uqm/restart.h
new file mode 100644
index 0000000..0eef97f
--- /dev/null
+++ b/src/uqm/restart.h
@@ -0,0 +1,33 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_RESTART_H_
+#define UQM_RESTART_H_
+
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+extern BOOLEAN StartGame (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_RESTART_H_ */
diff --git a/src/uqm/save.c b/src/uqm/save.c
new file mode 100644
index 0000000..8e39401
--- /dev/null
+++ b/src/uqm/save.c
@@ -0,0 +1,813 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <assert.h>
+
+#include "save.h"
+
+#include "build.h"
+#include "controls.h"
+#include "starmap.h"
+#include "encount.h"
+#include "libs/file.h"
+#include "gamestr.h"
+#include "globdata.h"
+#include "options.h"
+#include "races.h"
+#include "shipcont.h"
+#include "setup.h"
+#include "state.h"
+#include "grpintrn.h"
+#include "util.h"
+#include "hyper.h"
+ // for SaveSisHyperState()
+#include "planets/planets.h"
+ // for SaveSolarSysLocation() and tests
+#include "libs/inplib.h"
+#include "libs/log.h"
+#include "libs/memlib.h"
+
+// Status boolean. If for some insane reason you need to
+// save games in different threads, you'll need to
+// protect your calls to SaveGame with a mutex.
+
+// It's arguably over-paranoid to check for error on
+// every single write, but this preserves the older
+// behavior.
+
+static BOOLEAN io_ok = TRUE;
+
+// XXX: these should handle endian conversions later
+static inline void
+write_8 (void *fp, BYTE v)
+{
+ if (io_ok)
+ if (WriteResFile (&v, 1, 1, fp) != 1)
+ io_ok = FALSE;
+}
+
+static inline void
+write_16 (void *fp, UWORD v)
+{
+ write_8 (fp, (BYTE)( v & 0xff));
+ write_8 (fp, (BYTE)((v >> 8) & 0xff));
+}
+
+static inline void
+write_32 (void *fp, DWORD v)
+{
+ write_8 (fp, (BYTE)( v & 0xff));
+ write_8 (fp, (BYTE)((v >> 8) & 0xff));
+ write_8 (fp, (BYTE)((v >> 16) & 0xff));
+ write_8 (fp, (BYTE)((v >> 24) & 0xff));
+}
+
+static inline void
+write_a8 (void *fp, const BYTE *ar, COUNT count)
+{
+ if (io_ok)
+ if (WriteResFile (ar, 1, count, fp) != count)
+ io_ok = FALSE;
+}
+
+static inline void
+write_str (void *fp, const char *str, COUNT count)
+{
+ // no type conversion needed for strings
+ write_a8 (fp, (const BYTE *)str, count);
+}
+
+static inline void
+write_a16 (void *fp, const UWORD *ar, COUNT count)
+{
+ for ( ; count > 0; --count, ++ar)
+ {
+ if (!io_ok)
+ break;
+ write_16 (fp, *ar);
+ }
+}
+
+static void
+SaveShipQueue (uio_Stream *fh, QUEUE *pQueue, DWORD tag)
+{
+ COUNT num_links;
+ HSHIPFRAG hStarShip;
+
+ num_links = CountLinks (pQueue);
+ if (num_links == 0)
+ return;
+ write_32 (fh, tag);
+ write_32 (fh, num_links * 11); // Size of chunk: each entry is 11 bytes long.
+
+ hStarShip = GetHeadLink (pQueue);
+ while (num_links--)
+ {
+ HSHIPFRAG hNextShip;
+ SHIP_FRAGMENT *FragPtr;
+ COUNT Index;
+
+ FragPtr = LockShipFrag (pQueue, hStarShip);
+ hNextShip = _GetSuccLink (FragPtr);
+
+ Index = FragPtr->race_id;
+ // Write the number identifying this ship type.
+ // See races.h; look for the enum containing NUM_AVAILABLE_RACES.
+ write_16 (fh, Index);
+
+ // Write SHIP_FRAGMENT elements
+ write_8 (fh, FragPtr->captains_name_index);
+ write_8 (fh, FragPtr->race_id);
+ write_8 (fh, FragPtr->index);
+ write_16 (fh, FragPtr->crew_level);
+ write_16 (fh, FragPtr->max_crew);
+ write_8 (fh, FragPtr->energy_level);
+ write_8 (fh, FragPtr->max_energy);
+
+ UnlockShipFrag (pQueue, hStarShip);
+ hStarShip = hNextShip;
+ }
+}
+
+static void
+SaveRaceQueue (uio_Stream *fh, QUEUE *pQueue)
+{
+ COUNT num_links;
+ HFLEETINFO hFleet;
+
+ num_links = CountLinks (pQueue);
+ if (num_links == 0)
+ return;
+ write_32 (fh, RACE_Q_TAG);
+ // Write chunk size: 30 bytes per entry
+ write_32 (fh, num_links * 30);
+
+ hFleet = GetHeadLink (pQueue);
+ while (num_links--)
+ {
+ HFLEETINFO hNextFleet;
+ FLEET_INFO *FleetPtr;
+ COUNT Index;
+
+ FleetPtr = LockFleetInfo (pQueue, hFleet);
+ hNextFleet = _GetSuccLink (FleetPtr);
+
+ Index = GetIndexFromStarShip (pQueue, hFleet);
+ // The index is the position in the queue.
+ write_16 (fh, Index);
+
+ // Write FLEET_INFO elements
+ write_16 (fh, FleetPtr->allied_state);
+ write_8 (fh, FleetPtr->days_left);
+ write_8 (fh, FleetPtr->growth_fract);
+ write_16 (fh, FleetPtr->crew_level);
+ write_16 (fh, FleetPtr->max_crew);
+ write_8 (fh, FleetPtr->growth);
+ write_8 (fh, FleetPtr->max_energy);
+ write_16 (fh, FleetPtr->loc.x);
+ write_16 (fh, FleetPtr->loc.y);
+
+ write_16 (fh, FleetPtr->actual_strength);
+ write_16 (fh, FleetPtr->known_strength);
+ write_16 (fh, FleetPtr->known_loc.x);
+ write_16 (fh, FleetPtr->known_loc.y);
+ write_8 (fh, FleetPtr->growth_err_term);
+ write_8 (fh, FleetPtr->func_index);
+ write_16 (fh, FleetPtr->dest_loc.x);
+ write_16 (fh, FleetPtr->dest_loc.y);
+
+ UnlockFleetInfo (pQueue, hFleet);
+ hFleet = hNextFleet;
+ }
+}
+
+static void
+SaveGroupQueue (uio_Stream *fh, QUEUE *pQueue)
+{
+ HIPGROUP hGroup, hNextGroup;
+ COUNT num_links;
+
+ num_links = CountLinks (pQueue);
+ if (num_links == 0)
+ return;
+ write_32 (fh, IP_GRP_Q_TAG);
+ write_32 (fh, num_links * 13); // 13 bytes per element right now
+
+ for (hGroup = GetHeadLink (pQueue); hGroup; hGroup = hNextGroup)
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (pQueue, hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+
+ write_16 (fh, GroupPtr->group_counter);
+ write_8 (fh, GroupPtr->race_id);
+ write_8 (fh, GroupPtr->sys_loc);
+ write_8 (fh, GroupPtr->task);
+ write_8 (fh, GroupPtr->in_system); /* was crew_level */
+ write_8 (fh, GroupPtr->dest_loc);
+ write_8 (fh, GroupPtr->orbit_pos);
+ write_8 (fh, GroupPtr->group_id); /* was max_energy */
+ write_16 (fh, GroupPtr->loc.x);
+ write_16 (fh, GroupPtr->loc.y);
+
+ UnlockIpGroup (pQueue, hGroup);
+ }
+}
+
+static void
+SaveEncounters (uio_Stream *fh)
+{
+ COUNT num_links;
+ HENCOUNTER hEncounter;
+ num_links = CountLinks (&GLOBAL (encounter_q));
+ if (num_links == 0)
+ return;
+ write_32 (fh, ENCOUNTERS_TAG);
+ write_32 (fh, 65 * num_links);
+
+ hEncounter = GetHeadLink (&GLOBAL (encounter_q));
+ while (num_links--)
+ {
+ HENCOUNTER hNextEncounter;
+ ENCOUNTER *EncounterPtr;
+ COUNT i;
+
+ LockEncounter (hEncounter, &EncounterPtr);
+ hNextEncounter = GetSuccEncounter (EncounterPtr);
+
+ write_16 (fh, EncounterPtr->transition_state);
+ write_16 (fh, EncounterPtr->origin.x);
+ write_16 (fh, EncounterPtr->origin.y);
+ write_16 (fh, EncounterPtr->radius);
+ // former STAR_DESC fields
+ write_16 (fh, EncounterPtr->loc_pt.x);
+ write_16 (fh, EncounterPtr->loc_pt.y);
+ write_8 (fh, EncounterPtr->race_id);
+ write_8 (fh, EncounterPtr->num_ships);
+ write_8 (fh, EncounterPtr->flags);
+
+ // Save each entry in the BRIEF_SHIP_INFO array
+ for (i = 0; i < MAX_HYPER_SHIPS; i++)
+ {
+ const BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i];
+
+ write_8 (fh, ShipInfo->race_id);
+ write_16 (fh, ShipInfo->crew_level);
+ write_16 (fh, ShipInfo->max_crew);
+ write_8 (fh, ShipInfo->max_energy);
+ }
+
+ // Save the stuff after the BRIEF_SHIP_INFO array
+ write_32 (fh, EncounterPtr->log_x);
+ write_32 (fh, EncounterPtr->log_y);
+
+ UnlockEncounter (hEncounter);
+ hEncounter = hNextEncounter;
+ }
+}
+
+static void
+SaveEvents (uio_Stream *fh)
+{
+ COUNT num_links;
+ HEVENT hEvent;
+ num_links = CountLinks (&GLOBAL (GameClock.event_q));
+ if (num_links == 0)
+ return;
+ write_32 (fh, EVENTS_TAG);
+ write_32 (fh, num_links * 5); /* Event chunks are five bytes each */
+
+ hEvent = GetHeadLink (&GLOBAL (GameClock.event_q));
+ while (num_links--)
+ {
+ HEVENT hNextEvent;
+ EVENT *EventPtr;
+
+ LockEvent (hEvent, &EventPtr);
+ hNextEvent = GetSuccEvent (EventPtr);
+
+ write_8 (fh, EventPtr->day_index);
+ write_8 (fh, EventPtr->month_index);
+ write_16 (fh, EventPtr->year_index);
+ write_8 (fh, EventPtr->func_index);
+
+ UnlockEvent (hEvent);
+ hEvent = hNextEvent;
+ }
+}
+
+/* The clock state is folded in with the game state chunk. */
+static void
+SaveClockState (const CLOCK_STATE *ClockPtr, uio_Stream *fh)
+{
+ write_8 (fh, ClockPtr->day_index);
+ write_8 (fh, ClockPtr->month_index);
+ write_16 (fh, ClockPtr->year_index);
+ write_16 (fh, ClockPtr->tick_count);
+ write_16 (fh, ClockPtr->day_in_ticks);
+}
+
+/* Save out the game state chunks. There are two of these; the Global
+ * State chunk is fixed size, but the Game State tag can be extended
+ * by modders. */
+static void
+SaveGameState (const GAME_STATE *GSPtr, uio_Stream *fh)
+{
+ write_32 (fh, GLOBAL_STATE_TAG);
+ write_32 (fh, 75);
+ write_8 (fh, GSPtr->glob_flags);
+ write_8 (fh, GSPtr->CrewCost);
+ write_8 (fh, GSPtr->FuelCost);
+ write_a8 (fh, GSPtr->ModuleCost, NUM_MODULES);
+ write_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES);
+ write_16 (fh, GSPtr->CurrentActivity);
+
+ SaveClockState (&GSPtr->GameClock, fh);
+
+ write_16 (fh, GSPtr->autopilot.x);
+ write_16 (fh, GSPtr->autopilot.y);
+ write_16 (fh, GSPtr->ip_location.x);
+ write_16 (fh, GSPtr->ip_location.y);
+ /* STAMP ShipStamp */
+ write_16 (fh, GSPtr->ShipStamp.origin.x);
+ write_16 (fh, GSPtr->ShipStamp.origin.y);
+ write_16 (fh, GSPtr->ShipFacing);
+ write_8 (fh, GSPtr->ip_planet);
+ write_8 (fh, GSPtr->in_orbit);
+
+ /* VELOCITY_DESC velocity */
+ write_16 (fh, GSPtr->velocity.TravelAngle);
+ write_16 (fh, GSPtr->velocity.vector.width);
+ write_16 (fh, GSPtr->velocity.vector.height);
+ write_16 (fh, GSPtr->velocity.fract.width);
+ write_16 (fh, GSPtr->velocity.fract.height);
+ write_16 (fh, GSPtr->velocity.error.width);
+ write_16 (fh, GSPtr->velocity.error.height);
+ write_16 (fh, GSPtr->velocity.incr.width);
+ write_16 (fh, GSPtr->velocity.incr.height);
+
+ /* The Game state bits. Vanilla UQM uses 155 bytes here at
+ * present. Only the first 99 bytes are significant, though;
+ * the rest will be overwritten by the BtGp chunks. */
+ write_32 (fh, GAME_STATE_TAG);
+ write_32 (fh, sizeof (GSPtr->GameState));
+ write_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState));
+}
+
+/* This is folded into the Summary chunk */
+static void
+SaveSisState (const SIS_STATE *SSPtr, void *fp)
+{
+ write_32 (fp, SSPtr->log_x);
+ write_32 (fp, SSPtr->log_y);
+ write_32 (fp, SSPtr->ResUnits);
+ write_32 (fp, SSPtr->FuelOnBoard);
+ write_16 (fp, SSPtr->CrewEnlisted);
+ write_16 (fp, SSPtr->TotalElementMass);
+ write_16 (fp, SSPtr->TotalBioMass);
+ write_a8 (fp, SSPtr->ModuleSlots, NUM_MODULE_SLOTS);
+ write_a8 (fp, SSPtr->DriveSlots, NUM_DRIVE_SLOTS);
+ write_a8 (fp, SSPtr->JetSlots, NUM_JET_SLOTS);
+ write_8 (fp, SSPtr->NumLanders);
+ write_a16 (fp, SSPtr->ElementAmounts, NUM_ELEMENT_CATEGORIES);
+
+ write_str (fp, SSPtr->ShipName, SIS_NAME_SIZE);
+ write_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE);
+ write_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE);
+}
+
+/* Write out the Summary Chunk. This is variable length because of the
+ savegame name */
+static void
+SaveSummary (const SUMMARY_DESC *SummPtr, void *fp)
+{
+ write_32 (fp, SUMMARY_TAG);
+ write_32 (fp, 160 + strlen(SummPtr->SaveName));
+ SaveSisState (&SummPtr->SS, fp);
+
+ write_8 (fp, SummPtr->Activity);
+ write_8 (fp, SummPtr->Flags);
+ write_8 (fp, SummPtr->day_index);
+ write_8 (fp, SummPtr->month_index);
+ write_16 (fp, SummPtr->year_index);
+ write_8 (fp, SummPtr->MCreditLo);
+ write_8 (fp, SummPtr->MCreditHi);
+ write_8 (fp, SummPtr->NumShips);
+ write_8 (fp, SummPtr->NumDevices);
+ write_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS);
+ write_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES);
+ write_a8 (fp, (BYTE *) SummPtr->SaveName, strlen(SummPtr->SaveName));
+}
+
+/* Save the Star Description chunk. This is not to be confused with
+ * the Star *Info* chunk, which records which planetary features you
+ * have exploited with your lander */
+static void
+SaveStarDesc (const STAR_DESC *SDPtr, uio_Stream *fh)
+{
+ write_32 (fh, STAR_TAG);
+ write_32 (fh, 8);
+ write_16 (fh, SDPtr->star_pt.x);
+ write_16 (fh, SDPtr->star_pt.y);
+ write_8 (fh, SDPtr->Type);
+ write_8 (fh, SDPtr->Index);
+ write_8 (fh, SDPtr->Prefix);
+ write_8 (fh, SDPtr->Postfix);
+}
+
+static void
+PrepareSummary (SUMMARY_DESC *SummPtr, const char *name)
+{
+ SummPtr->SS = GlobData.SIS_state;
+
+ SummPtr->Activity = LOBYTE (GLOBAL (CurrentActivity));
+ switch (SummPtr->Activity)
+ {
+ case IN_HYPERSPACE:
+ if (inQuasiSpace ())
+ SummPtr->Activity = IN_QUASISPACE;
+ break;
+ case IN_INTERPLANETARY:
+ // Get a better planet name for summary
+ GetPlanetOrMoonName (SummPtr->SS.PlanetName,
+ sizeof (SummPtr->SS.PlanetName));
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) == (BYTE)~0)
+ SummPtr->Activity = IN_STARBASE;
+ else if (playerInPlanetOrbit ())
+ SummPtr->Activity = IN_PLANET_ORBIT;
+ break;
+ case IN_LAST_BATTLE:
+ utf8StringCopy (SummPtr->SS.PlanetName,
+ sizeof (SummPtr->SS.PlanetName),
+ GAME_STRING (PLANET_NUMBER_BASE + 32)); // Sa-Matra
+ break;
+ }
+
+ SummPtr->MCreditLo = GET_GAME_STATE (MELNORME_CREDIT0);
+ SummPtr->MCreditHi = GET_GAME_STATE (MELNORME_CREDIT1);
+
+ {
+ HSHIPFRAG hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)), SummPtr->NumShips = 0;
+ hStarShip; hStarShip = hNextShip, ++SummPtr->NumShips)
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ SummPtr->ShipList[SummPtr->NumShips] = StarShipPtr->race_id;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+ }
+
+ SummPtr->NumDevices = InventoryDevices (SummPtr->DeviceList,
+ MAX_EXCLUSIVE_DEVICES);
+
+ SummPtr->Flags = GET_GAME_STATE (LANDER_SHIELDS)
+ | (GET_GAME_STATE (IMPROVED_LANDER_SPEED) << (4 + 0))
+ | (GET_GAME_STATE (IMPROVED_LANDER_CARGO) << (4 + 1))
+ | (GET_GAME_STATE (IMPROVED_LANDER_SHOT) << (4 + 2))
+ | ((GET_GAME_STATE (CHMMR_BOMB_STATE) < 2 ? 0 : 1) << (4 + 3));
+
+ SummPtr->day_index = GLOBAL (GameClock.day_index);
+ SummPtr->month_index = GLOBAL (GameClock.month_index);
+ SummPtr->year_index = GLOBAL (GameClock.year_index);
+ SummPtr->SaveName[SAVE_NAME_SIZE-1] = 0;
+ strncpy (SummPtr->SaveName, name, SAVE_NAME_SIZE-1);
+}
+
+static void
+SaveProblemMessage (STAMP *MsgStamp)
+{
+#define MAX_MSG_LINES 1
+ RECT r = {{0, 0}, {0, 0}};
+ COUNT i;
+ TEXT t;
+ UNICODE *ppStr[MAX_MSG_LINES];
+
+ // TODO: This should probably just use DoPopupWindow()
+
+ ppStr[0] = GAME_STRING (SAVEGAME_STRING_BASE + 2);
+
+ SetContextFont (StarConFont);
+
+ t.baseline.x = t.baseline.y = 0;
+ t.align = ALIGN_CENTER;
+ for (i = 0; i < MAX_MSG_LINES; ++i)
+ {
+ RECT tr;
+
+ t.pStr = ppStr[i];
+ if (*t.pStr == '\0')
+ break;
+ t.CharCount = (COUNT)~0;
+ TextRect (&t, &tr, NULL);
+ if (i == 0)
+ r = tr;
+ else
+ BoxUnion (&tr, &r, &r);
+ t.baseline.y += 11;
+ }
+ t.baseline.x = ((SIS_SCREEN_WIDTH >> 1) - (r.extent.width >> 1))
+ - r.corner.x;
+ t.baseline.y = ((SIS_SCREEN_HEIGHT >> 1) - (r.extent.height >> 1))
+ - r.corner.y;
+ r.corner.x += t.baseline.x - 4;
+ r.corner.y += t.baseline.y - 4;
+ r.extent.width += 8;
+ r.extent.height += 8;
+
+ *MsgStamp = SaveContextFrame (&r);
+
+ BatchGraphics ();
+ DrawStarConBox (&r, 2,
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19),
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F),
+ TRUE, BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x0F));
+
+ for (i = 0; i < MAX_MSG_LINES; ++i)
+ {
+ t.pStr = ppStr[i];
+ if (*t.pStr == '\0')
+ break;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += 11;
+ }
+ UnbatchGraphics ();
+}
+
+void
+SaveProblem (void)
+{
+ STAMP s;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (SpaceContext);
+ SaveProblemMessage (&s);
+ FlushGraphics ();
+
+ WaitForAnyButton (TRUE, WAIT_INFINITE, FALSE);
+
+ // Restore the screen under the message
+ DrawStamp (&s);
+ SetContext (OldContext);
+ DestroyDrawable (ReleaseDrawable (s.frame));
+}
+
+static void
+SaveFlagshipState (void)
+{
+ if (inHQSpace ())
+ {
+ // Player is in HyperSpace or QuasiSpace.
+ SaveSisHyperState ();
+ }
+ else if (playerInSolarSystem ())
+ {
+ SaveSolarSysLocation ();
+ }
+}
+
+static void
+SaveStarInfo (uio_Stream *fh)
+{
+ GAME_STATE_FILE *fp;
+ fp = OpenStateFile (STARINFO_FILE, "rb");
+ if (fp)
+ {
+ DWORD flen = LengthStateFile (fp);
+ if (flen % 4)
+ {
+ log_add (log_Warning, "Unexpected Star Info length! Expected an integral number of DWORDS.\n");
+ }
+ else
+ {
+ write_32 (fh, SCAN_TAG);
+ write_32 (fh, flen);
+ while (flen)
+ {
+ DWORD val;
+ sread_32 (fp, &val);
+ write_32 (fh, val);
+ flen -= 4;
+ }
+ }
+ CloseStateFile (fp);
+ }
+}
+
+static void
+SaveBattleGroup (GAME_STATE_FILE *fp, DWORD encounter_id, DWORD grpoffs, uio_Stream *fh)
+{
+ GROUP_HEADER h;
+ DWORD size = 12;
+ int i;
+ SeekStateFile (fp, grpoffs, SEEK_SET);
+ ReadGroupHeader (fp, &h);
+ for (i = 1; i <= h.NumGroups; ++i)
+ {
+ BYTE NumShips;
+ SeekStateFile (fp, h.GroupOffset[i], SEEK_SET);
+ sread_8 (fp, NULL);
+ sread_8 (fp, &NumShips);
+ size += 2 + 10 * NumShips;
+ }
+ write_32 (fh, BATTLE_GROUP_TAG);
+ write_32 (fh, size);
+ write_32 (fh, encounter_id);
+ write_8 (fh, (grpoffs && (GLOBAL (BattleGroupRef) == grpoffs)) ? 1 : 0); // current
+ write_16 (fh, h.star_index);
+ write_8 (fh, h.day_index);
+ write_8 (fh, h.month_index);
+ write_16 (fh, h.year_index);
+ write_8 (fh, h.NumGroups);
+ for (i = 1; i <= h.NumGroups; ++i)
+ {
+ int j;
+ BYTE b;
+ SeekStateFile (fp, h.GroupOffset[i], SEEK_SET);
+ sread_8 (fp, &b); // Group race icon
+ write_8 (fh, b);
+ sread_8 (fp, &b); // NumShips
+ write_8 (fh, b);
+ for (j = 0; j < b; ++j)
+ {
+ BYTE race_outer;
+ SHIP_FRAGMENT sf;
+ sread_8 (fp, &race_outer);
+ ReadShipFragment (fp, &sf);
+ write_8 (fh, race_outer);
+ write_8 (fh, sf.captains_name_index);
+ write_8 (fh, sf.race_id);
+ write_8 (fh, sf.index);
+ write_16 (fh, sf.crew_level);
+ write_16 (fh, sf.max_crew);
+ write_8 (fh, sf.energy_level);
+ write_8 (fh, sf.max_energy);
+ }
+ }
+}
+
+static void
+SaveGroups (uio_Stream *fh)
+{
+ GAME_STATE_FILE *fp;
+ fp = OpenStateFile (RANDGRPINFO_FILE, "rb");
+ if (fp && LengthStateFile (fp) > 0)
+ {
+ GROUP_HEADER h;
+ BYTE lastenc, count;
+ int i;
+ ReadGroupHeader (fp, &h);
+ /* Group List */
+ SeekStateFile (fp, h.GroupOffset[0], SEEK_SET);
+ sread_8 (fp, &lastenc);
+ sread_8 (fp, &count);
+ write_32 (fh, GROUP_LIST_TAG);
+ write_32 (fh, 1 + 14 * count); // Chunk size
+ write_8 (fh, lastenc);
+ for (i = 0; i < count; ++i)
+ {
+ BYTE race_outer;
+ IP_GROUP ip;
+ sread_8 (fp, &race_outer);
+ ReadIpGroup (fp, &ip);
+
+ write_8 (fh, race_outer);
+ write_16 (fh, ip.group_counter);
+ write_8 (fh, ip.race_id);
+ write_8 (fh, ip.sys_loc);
+ write_8 (fh, ip.task);
+ write_8 (fh, ip.in_system);
+ write_8 (fh, ip.dest_loc);
+ write_8 (fh, ip.orbit_pos);
+ write_8 (fh, ip.group_id);
+ write_16 (fh, ip.loc.x);
+ write_16 (fh, ip.loc.y);
+ }
+ SaveBattleGroup (fp, 0, 0, fh);
+ CloseStateFile (fp);
+ }
+ fp = OpenStateFile (DEFGRPINFO_FILE, "rb");
+ if (fp && LengthStateFile (fp) > 0)
+ {
+ int state_index = SHOFIXTI_GRPOFFS0;
+ int encounter_index = 1;
+ while (state_index < NUM_GAME_STATE_BITS)
+ {
+ DWORD grpoffs = GET_GAME_STATE_32 (state_index);
+ if (grpoffs)
+ {
+ SaveBattleGroup (fp, encounter_index, grpoffs, fh);
+ }
+ ++encounter_index;
+ state_index += 32;
+ }
+ CloseStateFile (fp);
+ }
+}
+
+// This function first writes to a memory file, and then writes the whole
+// lot to the actual save file at once.
+BOOLEAN
+SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name)
+{
+ uio_Stream *out_fp;
+ POINT pt;
+ STAR_DESC SD;
+ char file[PATH_MAX];
+ if (CurStarDescPtr)
+ SD = *CurStarDescPtr;
+ else
+ memset (&SD, 0, sizeof (SD));
+
+ // XXX: Backup: SaveFlagshipState() overwrites ip_location
+ pt = GLOBAL (ip_location);
+ SaveFlagshipState ();
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY
+ && !(GLOBAL (CurrentActivity)
+ & (START_ENCOUNTER | START_INTERPLANETARY)))
+ PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP);
+
+ // Write the memory file to the actual savegame file.
+ sprintf (file, "uqmsave.%02u", which_game);
+ if ((out_fp = res_OpenResFile (saveDir, file, "wb")))
+ {
+ io_ok = TRUE;
+ write_32 (out_fp, SAVEFILE_TAG);
+
+ PrepareSummary (SummPtr, name);
+ SaveSummary (SummPtr, out_fp);
+
+ SaveGameState (&GlobData.Game_state, out_fp);
+
+ // XXX: Restore
+ GLOBAL (ip_location) = pt;
+ // Only relevant when loading a game and must be cleaned
+ GLOBAL (in_orbit) = 0;
+
+ SaveRaceQueue (out_fp, &GLOBAL (avail_race_q));
+ // START_INTERPLANETARY is only set when saving from Homeworld
+ // encounter screen. When the game is loaded, the
+ // GenerateOrbitalFunction for the current star system
+ // create the encounter anew and populate the npc queue.
+ if (!(GLOBAL (CurrentActivity) & START_INTERPLANETARY))
+ {
+ if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ SaveShipQueue (out_fp, &GLOBAL (npc_built_ship_q), NPC_SHIP_Q_TAG);
+ else if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY)
+ // XXX: Technically, this queue does not need to be
+ // saved/loaded at all. IP groups will be reloaded
+ // from group state files. But the original code did,
+ // and so will we until we can prove we do not need to.
+ SaveGroupQueue (out_fp, &GLOBAL (ip_group_q));
+ }
+ SaveShipQueue (out_fp, &GLOBAL (built_ship_q), SHIP_Q_TAG);
+
+ // Save the game event chunk
+ SaveEvents (out_fp);
+
+ // Save the encounter chunk (black globes in HS/QS)
+ SaveEncounters (out_fp);
+
+ // Save out the data that used to be in state files
+ SaveStarInfo (out_fp);
+ SaveGroups (out_fp);
+
+ // Save out the Star Descriptor
+ SaveStarDesc (&SD, out_fp);
+
+ res_CloseResFile (out_fp);
+ if (!io_ok)
+ {
+ DeleteResFile(saveDir, file);
+ return FALSE;
+ }
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/src/uqm/save.h b/src/uqm/save.h
new file mode 100644
index 0000000..28852b8
--- /dev/null
+++ b/src/uqm/save.h
@@ -0,0 +1,78 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SAVE_H_
+#define UQM_SAVE_H_
+
+#include "sis.h" // SUMMARY_DESC includes SIS_STATE in it
+#include "globdata.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// XXX: Theoretically, a player can have 17 devices on board without
+// cheating. We only provide
+// room for 16 below, which is not really a problem since this
+// is only used for displaying savegame summaries. There is also
+// room for only 16 devices on screen.
+#define MAX_EXCLUSIVE_DEVICES 16
+#define SAVE_NAME_SIZE 64
+
+// The savefile tag numbers.
+#define SAVEFILE_TAG 0x01534d55 // "UMS\x01": UQM Save version 1
+#define SUMMARY_TAG 0x6d6d7553 // "Summ": Summary. Must be first!
+#define GLOBAL_STATE_TAG 0x74536c47 // "GlSt": Global State. Must be 2nd!
+#define GAME_STATE_TAG 0x74536d47 // "GmSt": Game State Bits. Must be 3rd!
+#define EVENTS_TAG 0x73747645 // "Evts": Events
+#define ENCOUNTERS_TAG 0x74636e45 // "Enct": Encounters
+#define RACE_Q_TAG 0x51636152 // "RacQ": avail_race_q
+#define IP_GRP_Q_TAG 0x51704749 // "IGpQ": ip_group_q
+#define NPC_SHIP_Q_TAG 0x5163704e // "NpcQ": npc_built_ship_q
+#define SHIP_Q_TAG 0x51706853 // "ShpQ": built_ship_q
+#define STAR_TAG 0x72617453 // "Star": STAR_DESC
+#define SCAN_TAG 0x6e616353 // "Scan": Scan Masks (stuff picked up)
+#define BATTLE_GROUP_TAG 0x70477442 // "BtGp": Battle Group definition
+#define GROUP_LIST_TAG 0x73707247 // "Grps": Group List
+
+typedef struct
+{
+ SIS_STATE SS;
+ BYTE Activity;
+ BYTE Flags;
+ BYTE day_index, month_index;
+ COUNT year_index;
+ BYTE MCreditLo, MCreditHi;
+ BYTE NumShips, NumDevices;
+ BYTE ShipList[MAX_BUILT_SHIPS];
+ BYTE DeviceList[MAX_EXCLUSIVE_DEVICES];
+ UNICODE SaveName[SAVE_NAME_SIZE];
+} SUMMARY_DESC;
+
+extern ACTIVITY NextActivity;
+
+extern BOOLEAN LoadGame (COUNT which_game, SUMMARY_DESC *summary_desc);
+extern BOOLEAN LoadLegacyGame (COUNT which_game, SUMMARY_DESC *summary_desc);
+
+extern void SaveProblem (void);
+extern BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *summary_desc, const char *name);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SAVE_H_ */
diff --git a/src/uqm/settings.c b/src/uqm/settings.c
new file mode 100644
index 0000000..3e959a1
--- /dev/null
+++ b/src/uqm/settings.c
@@ -0,0 +1,97 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "settings.h"
+
+#include "globdata.h"
+#include "libs/compiler.h"
+
+
+static MUSIC_REF LastMusicRef;
+static BOOLEAN LastContinuous;
+static BYTE LastPriority;
+
+void
+ToggleMusic (void)
+{
+ GLOBAL (glob_flags) ^= MUSIC_DISABLED;
+ if (LastPriority <= 1)
+ {
+ if (GLOBAL (glob_flags) & MUSIC_DISABLED)
+ PLRStop (LastMusicRef);
+ else if (LastMusicRef)
+ PLRPlaySong (LastMusicRef, LastContinuous, LastPriority);
+ }
+}
+
+void
+PlayMusic (MUSIC_REF MusicRef, BOOLEAN Continuous, BYTE Priority)
+{
+ LastMusicRef = MusicRef;
+ LastContinuous = Continuous;
+ LastPriority = Priority;
+
+ if (
+#ifdef NEVER
+ Priority > 1
+ ||
+#endif /* NEVER */
+ !(GLOBAL (glob_flags) & MUSIC_DISABLED)
+ )
+ {
+ PLRPlaySong (MusicRef, Continuous, Priority);
+ }
+}
+
+void
+StopMusic (void)
+{
+ PLRStop (LastMusicRef);
+ LastMusicRef = 0;
+}
+
+void
+ResumeMusic (void)
+{
+ PLRResume (LastMusicRef);
+}
+
+void
+PauseMusic (void)
+{
+ PLRPause (LastMusicRef);
+}
+
+void
+ToggleSoundEffect (void)
+{
+ GLOBAL (glob_flags) ^= SOUND_DISABLED;
+}
+
+void
+PlaySoundEffect (SOUND S, COUNT Channel, SoundPosition Pos,
+ void *PositionalObject, BYTE Priority)
+{
+ if (!(GLOBAL (glob_flags) & SOUND_DISABLED))
+ {
+ SetChannelVolume (Channel, MAX_VOLUME >> 1, Priority);
+ //SetChannelRate (Channel, GetSampleRate (S), Priority);
+ PlayChannel (Channel, S, Pos, PositionalObject, Priority);
+ }
+}
+
diff --git a/src/uqm/settings.h b/src/uqm/settings.h
new file mode 100644
index 0000000..f903bc8
--- /dev/null
+++ b/src/uqm/settings.h
@@ -0,0 +1,41 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SETTINGS_H_
+#define UQM_SETTINGS_H_
+
+#include "libs/sndlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern void ToggleMusic (void);
+extern void StopMusic (void);
+extern void ResumeMusic (void);
+extern void PauseMusic (void);
+extern void ToggleSoundEffect (void);
+
+extern void PlayMusic (MUSIC_REF MusicRef, BOOLEAN Continuous, BYTE Priority);
+extern void PlaySoundEffect (SOUND S, COUNT Channel, SoundPosition Pos,
+ void *PositionalObject, BYTE Priority);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SETTINGS_H_ */
diff --git a/src/uqm/setup.c b/src/uqm/setup.c
new file mode 100644
index 0000000..bfdf90a
--- /dev/null
+++ b/src/uqm/setup.c
@@ -0,0 +1,332 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "setup.h"
+
+#include "coderes.h"
+#include "controls.h"
+#include "options.h"
+#include "nameref.h"
+#ifdef NETPLAY
+# include "supermelee/netplay/netmelee.h"
+#endif
+#include "init.h"
+#include "intel.h"
+#include "status.h"
+#include "resinst.h"
+#include "sounds.h"
+#include "libs/compiler.h"
+#include "libs/uio.h"
+#include "libs/file.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/sound/sound.h"
+#include "libs/threadlib.h"
+#include "libs/vidlib.h"
+#include "libs/log.h"
+#include "libs/misc.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+
+ACTIVITY LastActivity;
+BYTE PlayerControl[NUM_PLAYERS];
+
+// XXX: These declarations should really go to the file they belong to.
+RESOURCE_INDEX hResIndex;
+CONTEXT ScreenContext;
+CONTEXT SpaceContext;
+CONTEXT StatusContext;
+CONTEXT OffScreenContext;
+SIZE screen_width, screen_height;
+FRAME Screen;
+FONT StarConFont;
+FONT MicroFont;
+FONT TinyFont;
+QUEUE race_q[NUM_PLAYERS];
+FRAME ActivityFrame;
+FRAME StatusFrame;
+FRAME FlagStatFrame;
+FRAME MiscDataFrame;
+FRAME FontGradFrame;
+STRING GameStrings;
+QUEUE disp_q;
+
+uio_Repository *repository;
+uio_DirHandle *rootDir;
+
+BOOLEAN usingSpeech;
+
+
+static void
+InitPlayerInput (void)
+{
+}
+
+void
+UninitPlayerInput (void)
+{
+#if DEMO_MODE
+ DestroyInputDevice (ReleaseInputDevice (DemoInput));
+#endif /* DEMO_MODE */
+}
+
+BOOLEAN
+LoadKernel (int argc, char *argv[])
+{
+ InitSound (argc, argv);
+ InitVideoPlayer (TRUE);
+
+ ScreenContext = CreateContext ("ScreenContext");
+ if (ScreenContext == NULL)
+ return FALSE;
+
+ Screen = CaptureDrawable (CreateDisplay (WANT_MASK | WANT_PIXMAP,
+ &screen_width, &screen_height));
+ if (Screen == NULL)
+ return FALSE;
+
+ SetContext (ScreenContext);
+ SetContextFGFrame (Screen);
+ SetContextOrigin (MAKE_POINT (0, 0));
+
+ hResIndex = (RESOURCE_INDEX) InitResourceSystem ();
+ if (hResIndex == 0)
+ return FALSE;
+
+ /* Load base content. */
+ if (loadIndices (contentDir) == 0)
+ return FALSE; // Must have at least one index in content dir
+
+ /* Load addons demanded by the current configuration. */
+ if (opt3doMusic)
+ {
+ loadAddon ("3domusic");
+ }
+
+ usingSpeech = optSpeech;
+ if (optSpeech && !loadAddon ("3dovoice"))
+ {
+ usingSpeech = FALSE;
+ }
+
+ if (optRemixMusic)
+ {
+ loadAddon ("remix");
+ }
+
+ if (optWhichIntro == OPT_3DO)
+ {
+ loadAddon ("3dovideo");
+ }
+
+ /* Now load the rest of the addons, in order. */
+ prepareAddons (optAddons);
+
+ {
+ COLORMAP ColorMapTab;
+
+ ColorMapTab = CaptureColorMap (LoadColorMap (STARCON_COLOR_MAP));
+ if (ColorMapTab == NULL)
+ return FALSE; // The most basic resource is missing
+ SetColorMap (GetColorMapAddress (ColorMapTab));
+ DestroyColorMap (ReleaseColorMap (ColorMapTab));
+ }
+
+ InitPlayerInput ();
+
+ GLOBAL (CurrentActivity) = (ACTIVITY)~0;
+ return TRUE;
+}
+
+BOOLEAN
+InitContexts (void)
+{
+ RECT r;
+
+ StatusContext = CreateContext ("StatusContext");
+ if (StatusContext == NULL)
+ return FALSE;
+
+ SetContext (StatusContext);
+ SetContextFGFrame (Screen);
+ r.corner.x = SPACE_WIDTH + SAFE_X;
+ r.corner.y = SAFE_Y;
+ r.extent.width = STATUS_WIDTH;
+ r.extent.height = STATUS_HEIGHT;
+ SetContextClipRect (&r);
+
+ SpaceContext = CreateContext ("SpaceContext");
+ if (SpaceContext == NULL)
+ return FALSE;
+
+ OffScreenContext = CreateContext ("OffScreenContext");
+ if (OffScreenContext == NULL)
+ return FALSE;
+
+ if (!InitQueue (&disp_q, MAX_DISPLAY_ELEMENTS, sizeof (ELEMENT)))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOLEAN
+InitKernel (void)
+{
+ COUNT counter;
+
+ for (counter = 0; counter < NUM_PLAYERS; ++counter)
+ InitQueue (&race_q[counter], MAX_SHIPS_PER_SIDE, sizeof (STARSHIP));
+
+ StarConFont = LoadFont (STARCON_FONT);
+ if (StarConFont == NULL)
+ return FALSE;
+
+ TinyFont = LoadFont (TINY_FONT);
+ if (TinyFont == NULL)
+ return FALSE;
+
+ ActivityFrame = CaptureDrawable (LoadGraphic (ACTIVITY_ANIM));
+ if (ActivityFrame == NULL)
+ return FALSE;
+
+ StatusFrame = CaptureDrawable (LoadGraphic (STATUS_MASK_PMAP_ANIM));
+ if (StatusFrame == NULL)
+ return FALSE;
+
+ GameStrings = CaptureStringTable (LoadStringTable (STARCON_GAME_STRINGS));
+ if (GameStrings == 0)
+ return FALSE;
+
+ MicroFont = LoadFont (MICRO_FONT);
+ if (MicroFont == NULL)
+ return FALSE;
+
+ MenuSounds = CaptureSound (LoadSound (MENU_SOUNDS));
+ if (MenuSounds == 0)
+ return FALSE;
+
+ InitStatusOffsets ();
+ InitSpace ();
+
+ return TRUE;
+}
+
+BOOLEAN
+InitGameKernel (void)
+{
+ if (ActivityFrame == 0)
+ {
+ InitKernel ();
+ InitContexts ();
+ }
+ return TRUE;
+}
+
+bool
+SetPlayerInput (COUNT playerI)
+{
+ assert (PlayerInput[playerI] == NULL);
+
+ switch (PlayerControl[playerI] & CONTROL_MASK) {
+ case HUMAN_CONTROL:
+ PlayerInput[playerI] =
+ (InputContext *) HumanInputContext_new (playerI);
+ break;
+ case COMPUTER_CONTROL:
+ case CYBORG_CONTROL:
+ // COMPUTER_CONTROL is used in SuperMelee; the computer chooses
+ // the ships and fights the battles.
+ // CYBORG_CONTROL is used in the full game; the computer only
+ // fights the battles. XXX: This will need to be handled
+ // separately in the future if we want to remove the special
+ // cases for ship selection with CYBORG_CONTROL from the
+ // computer handlers.
+ PlayerInput[playerI] =
+ (InputContext *) ComputerInputContext_new (playerI);
+ break;
+#ifdef NETPLAY
+ case NETWORK_CONTROL:
+ PlayerInput[playerI] =
+ (InputContext *) NetworkInputContext_new (playerI);
+ break;
+#endif
+ default:
+ log_add (log_Fatal,
+ "Invalid control method in SetPlayerInput().");
+ explode (); /* Does not return */
+ }
+
+ return PlayerInput[playerI] != NULL;
+}
+
+bool
+SetPlayerInputAll (void)
+{
+ COUNT playerI;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ if (!SetPlayerInput (playerI))
+ return false;
+ return true;
+}
+
+void
+ClearPlayerInput (COUNT playerI)
+{
+ if (PlayerInput[playerI] == NULL) {
+ log_add (log_Debug, "ClearPlayerInput(): PlayerInput[%d] was NULL.",
+ playerI);
+ return;
+ }
+
+ PlayerInput[playerI]->handlers->deleteContext (PlayerInput[playerI]);
+ PlayerInput[playerI] = NULL;
+}
+
+void
+ClearPlayerInputAll (void)
+{
+ COUNT playerI;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ ClearPlayerInput (playerI);
+}
+
+int
+initIO (void)
+{
+ uio_init ();
+ repository = uio_openRepository (0);
+
+ rootDir = uio_openDir (repository, "/", 0);
+ if (rootDir == NULL)
+ {
+ log_add (log_Fatal, "Could not open '/' dir.");
+ return -1;
+ }
+ return 0;
+}
+
+void
+uninitIO (void)
+{
+ uio_closeDir (rootDir);
+ uio_closeRepository (repository);
+ uio_unInit ();
+}
+
diff --git a/src/uqm/setup.h b/src/uqm/setup.h
new file mode 100644
index 0000000..ffdbff3
--- /dev/null
+++ b/src/uqm/setup.h
@@ -0,0 +1,89 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_SETUP_H_
+#define UQM_SETUP_H_
+
+#include "displist.h"
+#include "globdata.h"
+#include "libs/reslib.h"
+#include "libs/sndlib.h"
+#include "libs/gfxlib.h"
+#include "libs/threadlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern RESOURCE_INDEX hResIndex;
+
+extern FRAME Screen;
+extern FRAME ActivityFrame;
+extern FRAME StatusFrame;
+extern FRAME FlagStatFrame;
+extern FRAME MiscDataFrame;
+extern FRAME FontGradFrame;
+
+extern CONTEXT OffScreenContext;
+ // OffScreenContext can often refer to a deleted ForeGroundFrame
+ // Always call SetContextFGFrame() before drawing anything to it
+ // Neither is the state of its ClipRect guaranteed.
+extern CONTEXT ScreenContext;
+extern CONTEXT SpaceContext;
+extern CONTEXT StatusContext;
+
+extern SIZE screen_width, screen_height;
+
+extern FONT StarConFont;
+extern FONT MicroFont;
+extern FONT TinyFont;
+
+extern CondVar RenderingCond;
+
+extern QUEUE race_q[];
+ /* Array of lists of ships involved in a battle, one queue per side;
+ * queue element is STARSHIP */
+
+extern ACTIVITY LastActivity;
+
+extern BYTE PlayerControl[];
+
+extern BOOLEAN usingSpeech;
+ // Actual speech presence indicator which decouples reality from
+ // the user option, thus the user option remains as pure intent
+
+BOOLEAN InitContexts (void);
+void UninitPlayerInput (void);
+BOOLEAN InitGameKernel (void);
+void UninitGameKernel (void);
+
+extern BOOLEAN LoadKernel (int argc, char *argv[]);
+extern void FreeKernel (void);
+
+int initIO (void);
+void uninitIO (void);
+
+bool SetPlayerInput (COUNT playerI);
+bool SetPlayerInputAll (void);
+void ClearPlayerInput (COUNT playerI);
+void ClearPlayerInputAll (void);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SETUP_H_ */
diff --git a/src/uqm/setupmenu.c b/src/uqm/setupmenu.c
new file mode 100644
index 0000000..b858617
--- /dev/null
+++ b/src/uqm/setupmenu.c
@@ -0,0 +1,1613 @@
+// Copyright Michael Martin, 2004.
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "setupmenu.h"
+
+#include "controls.h"
+#include "options.h"
+#include "setup.h"
+#include "sounds.h"
+#include "colors.h"
+#include "libs/gfxlib.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/widgets.h"
+#include "libs/graphics/tfb_draw.h"
+#include "libs/strlib.h"
+#include "libs/reslib.h"
+#include "libs/inplib.h"
+#include "libs/vidlib.h"
+#include "libs/sound/sound.h"
+#include "libs/resource/stringbank.h"
+#include "libs/log.h"
+#include "libs/memlib.h"
+#include "resinst.h"
+#include "nameref.h"
+#include <math.h>
+
+
+static STRING SetupTab;
+
+typedef struct setup_menu_state {
+ BOOLEAN (*InputFunc) (struct setup_menu_state *pInputState);
+
+ BOOLEAN initialized;
+ int anim_frame_count;
+ DWORD NextTime;
+} SETUP_MENU_STATE;
+
+static BOOLEAN DoSetupMenu (SETUP_MENU_STATE *pInputState);
+static BOOLEAN done;
+static WIDGET *current, *next;
+
+static int quit_main_menu (WIDGET *self, int event);
+static int quit_sub_menu (WIDGET *self, int event);
+static int do_graphics (WIDGET *self, int event);
+static int do_audio (WIDGET *self, int event);
+static int do_engine (WIDGET *self, int event);
+static int do_resources (WIDGET *self, int event);
+static int do_keyconfig (WIDGET *self, int event);
+static int do_advanced (WIDGET *self, int event);
+static int do_editkeys (WIDGET *self, int event);
+static void change_template (WIDGET_CHOICE *self, int oldval);
+static void rename_template (WIDGET_TEXTENTRY *self);
+static void rebind_control (WIDGET_CONTROLENTRY *widget);
+static void clear_control (WIDGET_CONTROLENTRY *widget);
+
+#define MENU_COUNT 8
+#define CHOICE_COUNT 24
+#define SLIDER_COUNT 4
+#define BUTTON_COUNT 10
+#define LABEL_COUNT 4
+#define TEXTENTRY_COUNT 1
+#define CONTROLENTRY_COUNT 7
+
+/* The space for our widgets */
+static WIDGET_MENU_SCREEN menus[MENU_COUNT];
+static WIDGET_CHOICE choices[CHOICE_COUNT];
+static WIDGET_SLIDER sliders[SLIDER_COUNT];
+static WIDGET_BUTTON buttons[BUTTON_COUNT];
+static WIDGET_LABEL labels[LABEL_COUNT];
+static WIDGET_TEXTENTRY textentries[TEXTENTRY_COUNT];
+static WIDGET_CONTROLENTRY controlentries[CONTROLENTRY_COUNT];
+
+/* The hardcoded data that isn't strings */
+
+typedef int (*HANDLER)(WIDGET *, int);
+
+static int choice_widths[CHOICE_COUNT] = {
+ 3, 2, 3, 3, 2, 2, 2, 2, 2, 2,
+ 2, 2, 3, 2, 2, 3, 3, 2, 3, 3,
+ 3, 2, 2, 2 };
+
+static HANDLER button_handlers[BUTTON_COUNT] = {
+ quit_main_menu, quit_sub_menu, do_graphics, do_engine,
+ do_audio, do_resources, do_keyconfig, do_advanced, do_editkeys,
+ do_keyconfig };
+
+/* These refer to uninitialized widgets, but that's OK; we'll fill
+ * them in before we touch them */
+static WIDGET *main_widgets[] = {
+ (WIDGET *)(&buttons[2]),
+ (WIDGET *)(&buttons[3]),
+ (WIDGET *)(&buttons[4]),
+ (WIDGET *)(&buttons[5]),
+ (WIDGET *)(&buttons[6]),
+ (WIDGET *)(&buttons[7]),
+ (WIDGET *)(&buttons[0]),
+ NULL };
+
+static WIDGET *graphics_widgets[] = {
+ (WIDGET *)(&choices[0]),
+ (WIDGET *)(&choices[23]),
+ (WIDGET *)(&choices[10]),
+ (WIDGET *)(&sliders[3]),
+ (WIDGET *)(&choices[2]),
+ (WIDGET *)(&choices[3]),
+ (WIDGET *)(&buttons[1]),
+ NULL };
+
+static WIDGET *audio_widgets[] = {
+ (WIDGET *)(&sliders[0]),
+ (WIDGET *)(&sliders[1]),
+ (WIDGET *)(&sliders[2]),
+ (WIDGET *)(&choices[14]),
+ (WIDGET *)(&choices[9]),
+ (WIDGET *)(&choices[21]),
+ (WIDGET *)(&choices[22]),
+ (WIDGET *)(&buttons[1]),
+ NULL };
+
+static WIDGET *engine_widgets[] = {
+ (WIDGET *)(&choices[4]),
+ (WIDGET *)(&choices[5]),
+ (WIDGET *)(&choices[6]),
+ (WIDGET *)(&choices[7]),
+ (WIDGET *)(&choices[8]),
+ (WIDGET *)(&choices[13]),
+ (WIDGET *)(&choices[11]),
+ (WIDGET *)(&choices[17]),
+ (WIDGET *)(&buttons[1]),
+ NULL };
+
+static WIDGET *advanced_widgets[] = {
+#ifdef HAVE_OPENGL
+ (WIDGET *)(&choices[1]),
+#endif
+ (WIDGET *)(&choices[12]),
+ (WIDGET *)(&choices[15]),
+ (WIDGET *)(&choices[16]),
+ (WIDGET *)(&buttons[1]),
+ NULL };
+
+static WIDGET *keyconfig_widgets[] = {
+ (WIDGET *)(&choices[18]),
+ (WIDGET *)(&choices[19]),
+ (WIDGET *)(&labels[1]),
+ (WIDGET *)(&buttons[8]),
+ (WIDGET *)(&buttons[1]),
+ NULL };
+
+static WIDGET *editkeys_widgets[] = {
+ (WIDGET *)(&choices[20]),
+ (WIDGET *)(&labels[2]),
+ (WIDGET *)(&textentries[0]),
+ (WIDGET *)(&controlentries[0]),
+ (WIDGET *)(&controlentries[1]),
+ (WIDGET *)(&controlentries[2]),
+ (WIDGET *)(&controlentries[3]),
+ (WIDGET *)(&controlentries[4]),
+ (WIDGET *)(&controlentries[5]),
+ (WIDGET *)(&controlentries[6]),
+ (WIDGET *)(&buttons[9]),
+ NULL };
+
+static WIDGET *incomplete_widgets[] = {
+ (WIDGET *)(&labels[0]),
+ (WIDGET *)(&buttons[1]),
+ NULL };
+
+static const struct
+{
+ WIDGET **widgets;
+ int bgIndex;
+}
+menu_defs[] =
+{
+ {main_widgets, 0},
+ {graphics_widgets, 1},
+ {audio_widgets, 1},
+ {engine_widgets, 2},
+ {incomplete_widgets, 3},
+ {keyconfig_widgets, 1},
+ {advanced_widgets, 2},
+ {editkeys_widgets, 1},
+ {NULL, 0}
+};
+
+// Start with reasonable gamma bounds. These will get updated
+// as we find out the actual bounds.
+static float minGamma = 0.4f;
+static float maxGamma = 2.5f;
+// The gamma slider uses an exponential curve
+// We use y = e^(2.1972*(x-1)) curve to give us a nice spread of
+// gamma values 0.11 < g < 9.0 centered at g=1.0
+#define GAMMA_CURVE_B 2.1972f
+static float minGammaX;
+static float maxGammaX;
+
+
+static int
+number_res_options (void)
+{
+ if (TFB_SupportsHardwareScaling ())
+ {
+ return 5;
+ }
+ else
+ {
+ return 2;
+ }
+}
+
+static int
+quit_main_menu (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = NULL;
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+quit_sub_menu (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = (WIDGET *)(&menus[0]);
+ (*next->receiveFocus) (next, WIDGET_EVENT_SELECT);
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+do_graphics (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = (WIDGET *)(&menus[1]);
+ (*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+do_audio (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = (WIDGET *)(&menus[2]);
+ (*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+do_engine (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = (WIDGET *)(&menus[3]);
+ (*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+do_resources (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = (WIDGET *)(&menus[4]);
+ (*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+do_keyconfig (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = (WIDGET *)(&menus[5]);
+ (*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static int
+do_advanced (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = (WIDGET *)(&menus[6]);
+ (*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static void
+populate_editkeys (int templat)
+{
+ int i, j;
+
+ strncpy (textentries[0].value, input_templates[templat].name, textentries[0].maxlen);
+ textentries[0].value[textentries[0].maxlen-1] = 0;
+
+ for (i = 0; i < NUM_KEYS; i++)
+ {
+ for (j = 0; j < 2; j++)
+ {
+ InterrogateInputState (templat, i, j, controlentries[i].controlname[j], WIDGET_CONTROLENTRY_WIDTH);
+ }
+ }
+}
+
+static int
+do_editkeys (WIDGET *self, int event)
+{
+ if (event == WIDGET_EVENT_SELECT)
+ {
+ next = (WIDGET *)(&menus[7]);
+ /* Prepare the components */
+ choices[20].selected = 0;
+
+ populate_editkeys (0);
+ (*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
+ return TRUE;
+ }
+ (void)self;
+ return FALSE;
+}
+
+static void
+change_template (WIDGET_CHOICE *self, int oldval)
+{
+ (void) oldval;
+ populate_editkeys (self->selected);
+}
+
+static void
+rename_template (WIDGET_TEXTENTRY *self)
+{
+ /* TODO: This will have to change if the size of the
+ input_templates name is changed. It would probably be nice
+ to track this symbolically or ensure that self->value's
+ buffer is always at least this big; this will require some
+ reworking of widgets */
+ strncpy (input_templates[choices[20].selected].name, self->value, 30);
+ input_templates[choices[20].selected].name[29] = 0;
+}
+
+#define NUM_STEPS 20
+#define X_STEP (SCREEN_WIDTH / NUM_STEPS)
+#define Y_STEP (SCREEN_HEIGHT / NUM_STEPS)
+#define MENU_FRAME_RATE (ONE_SECOND / 20)
+
+static void
+SetDefaults (void)
+{
+ GLOBALOPTS opts;
+
+ GetGlobalOptions (&opts);
+ if (opts.res == OPTVAL_CUSTOM)
+ {
+ choices[0].numopts = number_res_options () + 1;
+ }
+ else
+ {
+ choices[0].numopts = number_res_options ();
+ }
+ choices[0].selected = opts.res;
+ choices[1].selected = opts.driver;
+ choices[2].selected = opts.scaler;
+ choices[3].selected = opts.scanlines;
+ choices[4].selected = opts.menu;
+ choices[5].selected = opts.text;
+ choices[6].selected = opts.cscan;
+ choices[7].selected = opts.scroll;
+ choices[8].selected = opts.subtitles;
+ choices[9].selected = opts.music3do;
+ choices[10].selected = opts.fullscreen;
+ choices[11].selected = opts.intro;
+ choices[12].selected = opts.fps;
+ choices[13].selected = opts.meleezoom;
+ choices[14].selected = opts.stereo;
+ choices[15].selected = opts.adriver;
+ choices[16].selected = opts.aquality;
+ choices[17].selected = opts.shield;
+ choices[18].selected = opts.player1;
+ choices[19].selected = opts.player2;
+ choices[20].selected = 0;
+ choices[21].selected = opts.musicremix;
+ choices[22].selected = opts.speech;
+ choices[23].selected = opts.keepaspect;
+
+ sliders[0].value = opts.musicvol;
+ sliders[1].value = opts.sfxvol;
+ sliders[2].value = opts.speechvol;
+ sliders[3].value = opts.gamma;
+}
+
+static void
+PropagateResults (void)
+{
+ GLOBALOPTS opts;
+ opts.res = choices[0].selected;
+ opts.driver = choices[1].selected;
+ opts.scaler = choices[2].selected;
+ opts.scanlines = choices[3].selected;
+ opts.menu = choices[4].selected;
+ opts.text = choices[5].selected;
+ opts.cscan = choices[6].selected;
+ opts.scroll = choices[7].selected;
+ opts.subtitles = choices[8].selected;
+ opts.music3do = choices[9].selected;
+ opts.fullscreen = choices[10].selected;
+ opts.intro = choices[11].selected;
+ opts.fps = choices[12].selected;
+ opts.meleezoom = choices[13].selected;
+ opts.stereo = choices[14].selected;
+ opts.adriver = choices[15].selected;
+ opts.aquality = choices[16].selected;
+ opts.shield = choices[17].selected;
+ opts.player1 = choices[18].selected;
+ opts.player2 = choices[19].selected;
+ opts.musicremix = choices[21].selected;
+ opts.speech = choices[22].selected;
+ opts.keepaspect = choices[23].selected;
+
+ opts.musicvol = sliders[0].value;
+ opts.sfxvol = sliders[1].value;
+ opts.speechvol = sliders[2].value;
+ opts.gamma = sliders[3].value;
+ SetGlobalOptions (&opts);
+}
+
+static BOOLEAN
+DoSetupMenu (SETUP_MENU_STATE *pInputState)
+{
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (!pInputState->initialized)
+ {
+ SetDefaultMenuRepeatDelay ();
+ pInputState->NextTime = GetTimeCounter ();
+ SetDefaults ();
+ Widget_SetFont (StarConFont);
+ Widget_SetWindowColors (SHADOWBOX_BACKGROUND_COLOR,
+ SHADOWBOX_DARK_COLOR, SHADOWBOX_MEDIUM_COLOR);
+
+ current = NULL;
+ next = (WIDGET *)(&menus[0]);
+ (*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
+
+ pInputState->initialized = TRUE;
+ }
+ if (current != next)
+ {
+ SetTransitionSource (NULL);
+ }
+
+ BatchGraphics ();
+ (*next->draw)(next, 0, 0);
+
+ if (current != next)
+ {
+ ScreenTransition (3, NULL);
+ current = next;
+ }
+
+ UnbatchGraphics ();
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ Widget_Event (WIDGET_EVENT_UP);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ Widget_Event (WIDGET_EVENT_DOWN);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ Widget_Event (WIDGET_EVENT_LEFT);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ Widget_Event (WIDGET_EVENT_RIGHT);
+ }
+ if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ Widget_Event (WIDGET_EVENT_SELECT);
+ }
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ Widget_Event (WIDGET_EVENT_CANCEL);
+ }
+ if (PulsedInputState.menu[KEY_MENU_DELETE])
+ {
+ Widget_Event (WIDGET_EVENT_DELETE);
+ }
+
+ SleepThreadUntil (pInputState->NextTime + MENU_FRAME_RATE);
+ pInputState->NextTime = GetTimeCounter ();
+ return !((GLOBAL (CurrentActivity) & CHECK_ABORT) ||
+ (next == NULL));
+}
+
+static void
+redraw_menu (void)
+{
+ BatchGraphics ();
+ (*next->draw)(next, 0, 0);
+ UnbatchGraphics ();
+}
+
+static BOOLEAN
+OnTextEntryChange (TEXTENTRY_STATE *pTES)
+{
+ WIDGET_TEXTENTRY *widget = (WIDGET_TEXTENTRY *) pTES->CbParam;
+
+ widget->cursor_pos = pTES->CursorPos;
+ if (pTES->JoystickMode)
+ widget->state |= WTE_BLOCKCUR;
+ else
+ widget->state &= ~WTE_BLOCKCUR;
+
+ // XXX TODO: Here, we can examine the text entered so far
+ // to make sure it fits on the screen, for example,
+ // and return FALSE to disallow the last change
+
+ return TRUE; // allow change
+}
+
+static BOOLEAN
+OnTextEntryFrame (TEXTENTRY_STATE *pTES)
+{
+ redraw_menu ();
+
+ SleepThreadUntil (pTES->NextTime);
+ pTES->NextTime = GetTimeCounter () + MENU_FRAME_RATE;
+
+ return TRUE; // continue
+}
+
+static int
+OnTextEntryEvent (WIDGET_TEXTENTRY *widget)
+{ // Going to edit the text
+ TEXTENTRY_STATE tes;
+ UNICODE revert_buf[256];
+
+ // position cursor at the end of text
+ widget->cursor_pos = utf8StringCount (widget->value);
+ widget->state = WTE_EDITING;
+ redraw_menu ();
+
+ // make a backup copy for revert on cancel
+ utf8StringCopy (revert_buf, sizeof (revert_buf), widget->value);
+
+ // text entry setup
+ tes.Initialized = FALSE;
+ tes.NextTime = GetTimeCounter () + MENU_FRAME_RATE;
+ tes.BaseStr = widget->value;
+ tes.MaxSize = widget->maxlen;
+ tes.CursorPos = widget->cursor_pos;
+ tes.CbParam = widget;
+ tes.ChangeCallback = OnTextEntryChange;
+ tes.FrameCallback = OnTextEntryFrame;
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_SELECT);
+ if (!DoTextEntry (&tes))
+ { // editing failed (canceled) -- revert the changes
+ utf8StringCopy (widget->value, widget->maxlen, revert_buf);
+ }
+ else
+ {
+ if (widget->onChange)
+ {
+ (*(widget->onChange))(widget);
+ }
+ }
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ widget->state = WTE_NORMAL;
+ redraw_menu ();
+
+ return TRUE; // event handled
+}
+
+static inline float
+gammaCurve (float x)
+{
+ // The slider uses an exponential curve
+ return exp ((x - 1) * GAMMA_CURVE_B);
+}
+
+static inline float
+solveGammaCurve (float y)
+{
+ return log (y) / GAMMA_CURVE_B + 1;
+}
+
+static int
+gammaToSlider (float gamma)
+{
+ const float x = solveGammaCurve (gamma);
+ const float step = (maxGammaX - minGammaX) / 100;
+ return (int) ((x - minGammaX) / step + 0.5);
+}
+
+static float
+sliderToGamma (int value)
+{
+ const float step = (maxGammaX - minGammaX) / 100;
+ const float x = minGammaX + step * value;
+ const float g = gammaCurve (x);
+ // report any value that is close enough as 1.0
+ return (fabs (g - 1.0f) < 0.001f) ? 1.0f : g;
+}
+
+static void
+updateGammaBounds (bool useUpper)
+{
+ float g, x;
+ int slider;
+
+ // The slider uses an exponential curve.
+ // Calculate where on the curve the min and max gamma values are
+ minGammaX = solveGammaCurve (minGamma);
+ maxGammaX = solveGammaCurve (maxGamma);
+
+ // We have 100 discrete steps through the range, so the slider may
+ // skip over a 1.0 gamma. We need to ensure that there always is
+ // a 1.0 on the slider by tweaking the range (expanding/contracting).
+ slider = gammaToSlider (1.0f);
+ g = sliderToGamma (slider);
+ if (g == 1.0f)
+ return; // no adjustment needed
+
+ x = solveGammaCurve (g);
+ if (useUpper)
+ { // Move the upper bound up or down to land on 1.0
+ const float d = (x - 1.0f) * 100 / slider;
+ maxGammaX -= d;
+ maxGamma = gammaCurve (maxGammaX);
+ }
+ else
+ { // Move the lower bound up or down to land on 1.0
+ const float d = (x - 1.0f) * 100 / (100 - slider);
+ minGammaX -= d;
+ minGamma = gammaCurve (minGammaX);
+ }
+}
+
+static int
+gamma_HandleEventSlider (WIDGET *_self, int event)
+{
+ WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self;
+ int prevValue = self->value;
+ float gamma;
+ bool set;
+
+ switch (event)
+ {
+ case WIDGET_EVENT_LEFT:
+ self->value -= self->step;
+ break;
+ case WIDGET_EVENT_RIGHT:
+ self->value += self->step;
+ break;
+ default:
+ return FALSE;
+ }
+
+ // Limit the slider to values accepted by gfx subsys
+ gamma = sliderToGamma (self->value);
+ set = TFB_SetGamma (gamma);
+ if (!set)
+ { // revert
+ self->value = prevValue;
+ gamma = sliderToGamma (self->value);
+ }
+
+ // Grow or shrink the range based on accepted values
+ if (gamma < minGamma || (!set && event == WIDGET_EVENT_LEFT))
+ {
+ minGamma = gamma;
+ updateGammaBounds (true);
+ // at the lowest end
+ self->value = 0;
+ }
+ else if (gamma > maxGamma || (!set && event == WIDGET_EVENT_RIGHT))
+ {
+ maxGamma = gamma;
+ updateGammaBounds (false);
+ // at the highest end
+ self->value = 100;
+ }
+ return TRUE;
+}
+
+static void
+gamma_DrawValue (WIDGET_SLIDER *self, int x, int y)
+{
+ TEXT t;
+ char buf[16];
+ float gamma = sliderToGamma (self->value);
+ snprintf (buf, sizeof buf, "%.4f", gamma);
+
+ t.baseline.x = x;
+ t.baseline.y = y;
+ t.align = ALIGN_CENTER;
+ t.CharCount = ~0;
+ t.pStr = buf;
+
+ font_DrawText (&t);
+}
+
+static void
+rebind_control (WIDGET_CONTROLENTRY *widget)
+{
+ int templat = choices[20].selected;
+ int control = widget->controlindex;
+ int index = widget->highlighted;
+
+ FlushInput ();
+ DrawLabelAsWindow (&labels[3], NULL);
+ RebindInputState (templat, control, index);
+ populate_editkeys (templat);
+ FlushInput ();
+}
+
+static void
+clear_control (WIDGET_CONTROLENTRY *widget)
+{
+ int templat = choices[20].selected;
+ int control = widget->controlindex;
+ int index = widget->highlighted;
+
+ RemoveInputState (templat, control, index);
+ populate_editkeys (templat);
+}
+
+static int
+count_widgets (WIDGET **widgets)
+{
+ int count;
+
+ for (count = 0; *widgets != NULL; ++widgets, ++count)
+ ;
+ return count;
+}
+
+static stringbank *bank = NULL;
+static FRAME setup_frame = NULL;
+
+static void
+init_widgets (void)
+{
+ const char *buffer[100], *str, *title;
+ int count, i, index;
+
+ if (bank == NULL)
+ {
+ bank = StringBank_Create ();
+ }
+
+ if (setup_frame == NULL)
+ {
+ setup_frame = CaptureDrawable (LoadGraphic (MENUBKG_PMAP_ANIM));
+ }
+
+ count = GetStringTableCount (SetupTab);
+
+ if (count < 3)
+ {
+ log_add (log_Fatal, "PANIC: Setup string table too short to even hold all indices!");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Menus */
+ title = StringBank_AddOrFindString (bank, GetStringAddress (SetAbsStringTableIndex (SetupTab, 0)));
+ if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, 1)), '\n', 100, buffer, bank) != MENU_COUNT)
+ {
+ /* TODO: Ignore extras instead of dying. */
+ log_add (log_Fatal, "PANIC: Incorrect number of Menu Subtitles");
+ exit (EXIT_FAILURE);
+ }
+
+ for (i = 0; i < MENU_COUNT; i++)
+ {
+ menus[i].tag = WIDGET_TYPE_MENU_SCREEN;
+ menus[i].parent = NULL;
+ menus[i].handleEvent = Widget_HandleEventMenuScreen;
+ menus[i].receiveFocus = Widget_ReceiveFocusMenuScreen;
+ menus[i].draw = Widget_DrawMenuScreen;
+ menus[i].height = Widget_HeightFullScreen;
+ menus[i].width = Widget_WidthFullScreen;
+ menus[i].title = title;
+ menus[i].subtitle = buffer[i];
+ menus[i].bgStamp.origin.x = 0;
+ menus[i].bgStamp.origin.y = 0;
+ menus[i].bgStamp.frame = SetAbsFrameIndex (setup_frame, menu_defs[i].bgIndex);
+ menus[i].num_children = count_widgets (menu_defs[i].widgets);
+ menus[i].child = menu_defs[i].widgets;
+ menus[i].highlighted = 0;
+ }
+ if (menu_defs[i].widgets != NULL)
+ {
+ log_add (log_Error, "Menu definition array has more items!");
+ }
+
+ /* Options */
+ if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, 2)), '\n', 100, buffer, bank) != CHOICE_COUNT)
+ {
+ log_add (log_Fatal, "PANIC: Incorrect number of Choice Options");
+ exit (EXIT_FAILURE);
+ }
+
+ for (i = 0; i < CHOICE_COUNT; i++)
+ {
+ choices[i].tag = WIDGET_TYPE_CHOICE;
+ choices[i].parent = NULL;
+ choices[i].handleEvent = Widget_HandleEventChoice;
+ choices[i].receiveFocus = Widget_ReceiveFocusChoice;
+ choices[i].draw = Widget_DrawChoice;
+ choices[i].height = Widget_HeightChoice;
+ choices[i].width = Widget_WidthFullScreen;
+ choices[i].category = buffer[i];
+ choices[i].numopts = 0;
+ choices[i].options = NULL;
+ choices[i].selected = 0;
+ choices[i].highlighted = 0;
+ choices[i].maxcolumns = choice_widths[i];
+ choices[i].onChange = NULL;
+ }
+
+ /* Fill in the options now */
+ index = 3; /* Index into string table */
+ for (i = 0; i < CHOICE_COUNT; i++)
+ {
+ int j, optcount;
+
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading choices");
+ exit (EXIT_FAILURE);
+ }
+ str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
+ optcount = SplitString (str, '\n', 100, buffer, bank);
+ choices[i].numopts = optcount;
+ choices[i].options = HMalloc (optcount * sizeof (CHOICE_OPTION));
+ for (j = 0; j < optcount; j++)
+ {
+ choices[i].options[j].optname = buffer[j];
+ choices[i].options[j].tooltip[0] = "";
+ choices[i].options[j].tooltip[1] = "";
+ choices[i].options[j].tooltip[2] = "";
+ }
+ for (j = 0; j < optcount; j++)
+ {
+ int k, tipcount;
+
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading choices");
+ exit (EXIT_FAILURE);
+ }
+ str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
+ tipcount = SplitString (str, '\n', 100, buffer, bank);
+ if (tipcount > 3)
+ {
+ tipcount = 3;
+ }
+ for (k = 0; k < tipcount; k++)
+ {
+ choices[i].options[j].tooltip[k] = buffer[k];
+ }
+ }
+ }
+
+ /* The first choice is resolution, and is handled specially */
+ choices[0].numopts = number_res_options ();
+
+ /* Choices 18-20 are also special, being the names of the key configurations */
+ for (i = 0; i < 6; i++)
+ {
+ choices[18].options[i].optname = input_templates[i].name;
+ choices[19].options[i].optname = input_templates[i].name;
+ choices[20].options[i].optname = input_templates[i].name;
+ }
+
+ /* Choice 20 has a special onChange handler, too. */
+ choices[20].onChange = change_template;
+
+ /* Sliders */
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading sliders");
+ exit (EXIT_FAILURE);
+ }
+
+ if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != SLIDER_COUNT)
+ {
+ /* TODO: Ignore extras instead of dying. */
+ log_add (log_Fatal, "PANIC: Incorrect number of Slider Options");
+ exit (EXIT_FAILURE);
+ }
+
+ for (i = 0; i < SLIDER_COUNT; i++)
+ {
+ sliders[i].tag = WIDGET_TYPE_SLIDER;
+ sliders[i].parent = NULL;
+ sliders[i].handleEvent = Widget_HandleEventSlider;
+ sliders[i].receiveFocus = Widget_ReceiveFocusSimple;
+ sliders[i].draw = Widget_DrawSlider;
+ sliders[i].height = Widget_HeightOneLine;
+ sliders[i].width = Widget_WidthFullScreen;
+ sliders[i].draw_value = Widget_Slider_DrawValue;
+ sliders[i].min = 0;
+ sliders[i].max = 100;
+ sliders[i].step = 5;
+ sliders[i].value = 75;
+ sliders[i].category = buffer[i];
+ sliders[i].tooltip[0] = "";
+ sliders[i].tooltip[1] = "";
+ sliders[i].tooltip[2] = "";
+ }
+ // gamma is a special case
+ sliders[3].step = 1;
+ sliders[3].handleEvent = gamma_HandleEventSlider;
+ sliders[3].draw_value = gamma_DrawValue;
+
+ for (i = 0; i < SLIDER_COUNT; i++)
+ {
+ int j, tipcount;
+
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading sliders");
+ exit (EXIT_FAILURE);
+ }
+ str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
+ tipcount = SplitString (str, '\n', 100, buffer, bank);
+ if (tipcount > 3)
+ {
+ tipcount = 3;
+ }
+ for (j = 0; j < tipcount; j++)
+ {
+ sliders[i].tooltip[j] = buffer[j];
+ }
+ }
+
+ /* Buttons */
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading buttons");
+ exit (EXIT_FAILURE);
+ }
+
+ if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != BUTTON_COUNT)
+ {
+ /* TODO: Ignore extras instead of dying. */
+ log_add (log_Fatal, "PANIC: Incorrect number of Button Options");
+ exit (EXIT_FAILURE);
+ }
+
+ for (i = 0; i < BUTTON_COUNT; i++)
+ {
+ buttons[i].tag = WIDGET_TYPE_BUTTON;
+ buttons[i].parent = NULL;
+ buttons[i].handleEvent = button_handlers[i];
+ buttons[i].receiveFocus = Widget_ReceiveFocusSimple;
+ buttons[i].draw = Widget_DrawButton;
+ buttons[i].height = Widget_HeightOneLine;
+ buttons[i].width = Widget_WidthFullScreen;
+ buttons[i].name = buffer[i];
+ buttons[i].tooltip[0] = "";
+ buttons[i].tooltip[1] = "";
+ buttons[i].tooltip[2] = "";
+ }
+
+ for (i = 0; i < BUTTON_COUNT; i++)
+ {
+ int j, tipcount;
+
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading buttons");
+ exit (EXIT_FAILURE);
+ }
+ str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
+ tipcount = SplitString (str, '\n', 100, buffer, bank);
+ if (tipcount > 3)
+ {
+ tipcount = 3;
+ }
+ for (j = 0; j < tipcount; j++)
+ {
+ buttons[i].tooltip[j] = buffer[j];
+ }
+ }
+
+ /* Labels */
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading labels");
+ exit (EXIT_FAILURE);
+ }
+
+ if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != LABEL_COUNT)
+ {
+ /* TODO: Ignore extras instead of dying. */
+ log_add (log_Fatal, "PANIC: Incorrect number of Label Options");
+ exit (EXIT_FAILURE);
+ }
+
+ for (i = 0; i < LABEL_COUNT; i++)
+ {
+ labels[i].tag = WIDGET_TYPE_LABEL;
+ labels[i].parent = NULL;
+ labels[i].handleEvent = Widget_HandleEventIgnoreAll;
+ labels[i].receiveFocus = Widget_ReceiveFocusRefuseFocus;
+ labels[i].draw = Widget_DrawLabel;
+ labels[i].height = Widget_HeightLabel;
+ labels[i].width = Widget_WidthFullScreen;
+ labels[i].line_count = 0;
+ labels[i].lines = NULL;
+ }
+
+ for (i = 0; i < LABEL_COUNT; i++)
+ {
+ int j, linecount;
+
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading labels");
+ exit (EXIT_FAILURE);
+ }
+ str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
+ linecount = SplitString (str, '\n', 100, buffer, bank);
+ labels[i].line_count = linecount;
+ labels[i].lines = (const char **)HMalloc(linecount * sizeof(const char *));
+ for (j = 0; j < linecount; j++)
+ {
+ labels[i].lines[j] = buffer[j];
+ }
+ }
+
+ /* Text Entry boxes */
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading text entries");
+ exit (EXIT_FAILURE);
+ }
+
+ if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != TEXTENTRY_COUNT)
+ {
+ log_add (log_Fatal, "PANIC: Incorrect number of Text Entries");
+ exit (EXIT_FAILURE);
+ }
+ for (i = 0; i < TEXTENTRY_COUNT; i++)
+ {
+ textentries[i].tag = WIDGET_TYPE_TEXTENTRY;
+ textentries[i].parent = NULL;
+ textentries[i].handleEvent = Widget_HandleEventTextEntry;
+ textentries[i].receiveFocus = Widget_ReceiveFocusSimple;
+ textentries[i].draw = Widget_DrawTextEntry;
+ textentries[i].height = Widget_HeightOneLine;
+ textentries[i].width = Widget_WidthFullScreen;
+ textentries[i].handleEventSelect = OnTextEntryEvent;
+ textentries[i].category = buffer[i];
+ textentries[i].value[0] = 0;
+ textentries[i].maxlen = WIDGET_TEXTENTRY_WIDTH-1;
+ textentries[i].state = WTE_NORMAL;
+ textentries[i].cursor_pos = 0;
+ }
+
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading text entries");
+ exit (EXIT_FAILURE);
+ }
+
+ if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != TEXTENTRY_COUNT)
+ {
+ /* TODO: Ignore extras instead of dying. */
+ log_add (log_Fatal, "PANIC: Incorrect number of Text Entries");
+ exit (EXIT_FAILURE);
+ }
+ for (i = 0; i < TEXTENTRY_COUNT; i++)
+ {
+ strncpy (textentries[i].value, buffer[i], textentries[i].maxlen);
+ textentries[i].value[textentries[i].maxlen] = 0;
+ }
+ textentries[0].onChange = rename_template;
+
+ /* Control Entry boxes */
+ if (index >= count)
+ {
+ log_add (log_Fatal, "PANIC: String table cut short while reading control entries");
+ exit (EXIT_FAILURE);
+ }
+
+ if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != CONTROLENTRY_COUNT)
+ {
+ log_add (log_Fatal, "PANIC: Incorrect number of Control Entries");
+ exit (EXIT_FAILURE);
+ }
+ for (i = 0; i < CONTROLENTRY_COUNT; i++)
+ {
+ controlentries[i].tag = WIDGET_TYPE_CONTROLENTRY;
+ controlentries[i].parent = NULL;
+ controlentries[i].handleEvent = Widget_HandleEventControlEntry;
+ controlentries[i].receiveFocus = Widget_ReceiveFocusControlEntry;
+ controlentries[i].draw = Widget_DrawControlEntry;
+ controlentries[i].height = Widget_HeightOneLine;
+ controlentries[i].width = Widget_WidthFullScreen;
+ controlentries[i].category = buffer[i];
+ controlentries[i].highlighted = 0;
+ controlentries[i].controlname[0][0] = 0;
+ controlentries[i].controlname[1][0] = 0;
+ controlentries[i].controlindex = i;
+ controlentries[i].onChange = rebind_control;
+ controlentries[i].onDelete = clear_control;
+ }
+
+ /* Check for garbage at the end */
+ if (index < count)
+ {
+ log_add (log_Warning, "WARNING: Setup strings had %d garbage entries at the end.",
+ count - index);
+ }
+}
+
+static void
+clean_up_widgets (void)
+{
+ int i;
+
+ for (i = 0; i < CHOICE_COUNT; i++)
+ {
+ if (choices[i].options)
+ {
+ HFree (choices[i].options);
+ }
+ }
+
+ for (i = 0; i < LABEL_COUNT; i++)
+ {
+ if (labels[i].lines)
+ {
+ HFree ((void *)labels[i].lines);
+ }
+ }
+
+ /* Clear out the master tables */
+
+ if (SetupTab)
+ {
+ DestroyStringTable (ReleaseStringTable (SetupTab));
+ SetupTab = 0;
+ }
+ if (bank)
+ {
+ StringBank_Free (bank);
+ bank = NULL;
+ }
+ if (setup_frame)
+ {
+ DestroyDrawable (ReleaseDrawable (setup_frame));
+ setup_frame = NULL;
+ }
+}
+
+void
+SetupMenu (void)
+{
+ SETUP_MENU_STATE s;
+
+ s.InputFunc = DoSetupMenu;
+ s.initialized = FALSE;
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ SetupTab = CaptureStringTable (LoadStringTable (SETUP_MENU_STRTAB));
+ if (SetupTab)
+ {
+ init_widgets ();
+ }
+ else
+ {
+ log_add (log_Fatal, "PANIC: Could not find strings for the setup menu!");
+ exit (EXIT_FAILURE);
+ }
+ done = FALSE;
+
+ DoInput (&s, TRUE);
+ GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
+ PropagateResults ();
+ if (SetupTab)
+ {
+ clean_up_widgets ();
+ }
+}
+
+void
+GetGlobalOptions (GLOBALOPTS *opts)
+{
+ bool whichBound;
+
+ if (GfxFlags & TFB_GFXFLAGS_SCALE_BILINEAR)
+ {
+ opts->scaler = OPTVAL_BILINEAR_SCALE;
+ }
+ else if (GfxFlags & TFB_GFXFLAGS_SCALE_BIADAPT)
+ {
+ opts->scaler = OPTVAL_BIADAPT_SCALE;
+ }
+ else if (GfxFlags & TFB_GFXFLAGS_SCALE_BIADAPTADV)
+ {
+ opts->scaler = OPTVAL_BIADV_SCALE;
+ }
+ else if (GfxFlags & TFB_GFXFLAGS_SCALE_TRISCAN)
+ {
+ opts->scaler = OPTVAL_TRISCAN_SCALE;
+ }
+ else if (GfxFlags & TFB_GFXFLAGS_SCALE_HQXX)
+ {
+ opts->scaler = OPTVAL_HQXX_SCALE;
+ }
+ else
+ {
+ opts->scaler = OPTVAL_NO_SCALE;
+ }
+ opts->fullscreen = (GfxFlags & TFB_GFXFLAGS_FULLSCREEN) ?
+ OPTVAL_ENABLED : OPTVAL_DISABLED;
+ opts->subtitles = optSubtitles ? OPTVAL_ENABLED : OPTVAL_DISABLED;
+ opts->scanlines = (GfxFlags & TFB_GFXFLAGS_SCANLINES) ?
+ OPTVAL_ENABLED : OPTVAL_DISABLED;
+ opts->menu = (optWhichMenu == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
+ opts->text = (optWhichFonts == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
+ opts->cscan = (optWhichCoarseScan == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
+ opts->scroll = (optSmoothScroll == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
+ opts->intro = (optWhichIntro == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
+ opts->shield = (optWhichShield == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
+ opts->fps = (GfxFlags & TFB_GFXFLAGS_SHOWFPS) ?
+ OPTVAL_ENABLED : OPTVAL_DISABLED;
+ opts->meleezoom = (optMeleeScale == TFB_SCALE_STEP) ?
+ OPTVAL_PC : OPTVAL_3DO;
+ opts->stereo = optStereoSFX ? OPTVAL_ENABLED : OPTVAL_DISABLED;
+ /* These values are read in, but won't change during a run. */
+ opts->music3do = opt3doMusic ? OPTVAL_ENABLED : OPTVAL_DISABLED;
+ opts->musicremix = optRemixMusic ? OPTVAL_ENABLED : OPTVAL_DISABLED;
+ opts->speech = optSpeech ? OPTVAL_ENABLED : OPTVAL_DISABLED;
+ opts->keepaspect = optKeepAspectRatio ? OPTVAL_ENABLED : OPTVAL_DISABLED;
+ switch (snddriver) {
+ case audio_DRIVER_OPENAL:
+ opts->adriver = OPTVAL_OPENAL;
+ break;
+ case audio_DRIVER_MIXSDL:
+ opts->adriver = OPTVAL_MIXSDL;
+ break;
+ default:
+ opts->adriver = OPTVAL_SILENCE;
+ break;
+ }
+ if (soundflags & audio_QUALITY_HIGH)
+ {
+ opts->aquality = OPTVAL_HIGH;
+ }
+ else if (soundflags & audio_QUALITY_LOW)
+ {
+ opts->aquality = OPTVAL_LOW;
+ }
+ else
+ {
+ opts->aquality = OPTVAL_MEDIUM;
+ }
+
+ /* Work out resolution. On the way, try to guess a good default
+ * for config.alwaysgl, then overwrite it if it was set previously. */
+ opts->driver = OPTVAL_PURE_IF_POSSIBLE;
+ switch (ScreenWidthActual)
+ {
+ case 320:
+ if (GraphicsDriver == TFB_GFXDRIVER_SDL_PURE)
+ {
+ opts->res = OPTVAL_320_240;
+ }
+ else
+ {
+ if (ScreenHeightActual != 240)
+ {
+ opts->res = OPTVAL_CUSTOM;
+ }
+ else
+ {
+ opts->res = OPTVAL_320_240;
+ opts->driver = OPTVAL_ALWAYS_GL;
+ }
+ }
+ break;
+ case 640:
+ if (GraphicsDriver == TFB_GFXDRIVER_SDL_PURE)
+ {
+ opts->res = OPTVAL_640_480;
+ }
+ else
+ {
+ if (ScreenHeightActual != 480)
+ {
+ opts->res = OPTVAL_CUSTOM;
+ }
+ else
+ {
+ opts->res = OPTVAL_640_480;
+ opts->driver = OPTVAL_ALWAYS_GL;
+ }
+ }
+ break;
+ case 800:
+ if (ScreenHeightActual != 600)
+ {
+ opts->res = OPTVAL_CUSTOM;
+ }
+ else
+ {
+ opts->res = OPTVAL_800_600;
+ }
+ break;
+ case 1024:
+ if (ScreenHeightActual != 768)
+ {
+ opts->res = OPTVAL_CUSTOM;
+ }
+ else
+ {
+ opts->res = OPTVAL_1024_768;
+ }
+ break;
+ case 1280:
+ if (ScreenHeightActual != 960)
+ {
+ opts->res = OPTVAL_CUSTOM;
+ }
+ else
+ {
+ opts->res = OPTVAL_1280_960;
+ }
+ break;
+ default:
+ opts->res = OPTVAL_CUSTOM;
+ break;
+ }
+
+ if (res_IsBoolean ("config.alwaysgl"))
+ {
+ if (res_GetBoolean ("config.alwaysgl"))
+ {
+ opts->driver = OPTVAL_ALWAYS_GL;
+ }
+ else
+ {
+ opts->driver = OPTVAL_PURE_IF_POSSIBLE;
+ }
+ }
+
+ whichBound = (optGamma < maxGamma);
+ // The option supplied by the user may be beyond our starting range
+ // but valid nonetheless. We need to account for that.
+ if (optGamma <= minGamma)
+ minGamma = optGamma - 0.03f;
+ else if (optGamma >= maxGamma)
+ maxGamma = optGamma + 0.3f;
+ updateGammaBounds (whichBound);
+ opts->gamma = gammaToSlider (optGamma);
+
+ opts->player1 = PlayerControls[0];
+ opts->player2 = PlayerControls[1];
+
+ opts->musicvol = (((int)(musicVolumeScale * 100.0f) + 2) / 5) * 5;
+ opts->sfxvol = (((int)(sfxVolumeScale * 100.0f) + 2) / 5) * 5;
+ opts->speechvol = (((int)(speechVolumeScale * 100.0f) + 2) / 5) * 5;
+}
+
+void
+SetGlobalOptions (GLOBALOPTS *opts)
+{
+ int NewGfxFlags = GfxFlags;
+ int NewWidth = ScreenWidthActual;
+ int NewHeight = ScreenHeightActual;
+ int NewDriver = GraphicsDriver;
+
+ NewGfxFlags &= ~TFB_GFXFLAGS_SCALE_ANY;
+
+ switch (opts->res) {
+ case OPTVAL_320_240:
+ NewWidth = 320;
+ NewHeight = 240;
+#ifdef HAVE_OPENGL
+ NewDriver = (opts->driver == OPTVAL_ALWAYS_GL ? TFB_GFXDRIVER_SDL_OPENGL : TFB_GFXDRIVER_SDL_PURE);
+#else
+ NewDriver = TFB_GFXDRIVER_SDL_PURE;
+#endif
+ break;
+ case OPTVAL_640_480:
+ NewWidth = 640;
+ NewHeight = 480;
+#ifdef HAVE_OPENGL
+ NewDriver = (opts->driver == OPTVAL_ALWAYS_GL ? TFB_GFXDRIVER_SDL_OPENGL : TFB_GFXDRIVER_SDL_PURE);
+#else
+ NewDriver = TFB_GFXDRIVER_SDL_PURE;
+#endif
+ break;
+ case OPTVAL_800_600:
+ NewWidth = 800;
+ NewHeight = 600;
+ NewDriver = TFB_GFXDRIVER_SDL_OPENGL;
+ break;
+ case OPTVAL_1024_768:
+ NewWidth = 1024;
+ NewHeight = 768;
+ NewDriver = TFB_GFXDRIVER_SDL_OPENGL;
+ break;
+ case OPTVAL_1280_960:
+ NewWidth = 1280;
+ NewHeight = 960;
+ NewDriver = TFB_GFXDRIVER_SDL_OPENGL;
+ break;
+ default:
+ /* Don't mess with the custom value */
+ break;
+ }
+
+ res_PutInteger ("config.reswidth", NewWidth);
+ res_PutInteger ("config.resheight", NewHeight);
+ res_PutBoolean ("config.alwaysgl", opts->driver == OPTVAL_ALWAYS_GL);
+ res_PutBoolean ("config.usegl", NewDriver == TFB_GFXDRIVER_SDL_OPENGL);
+
+ switch (opts->scaler) {
+ case OPTVAL_BILINEAR_SCALE:
+ NewGfxFlags |= TFB_GFXFLAGS_SCALE_BILINEAR;
+ res_PutString ("config.scaler", "bilinear");
+ break;
+ case OPTVAL_BIADAPT_SCALE:
+ NewGfxFlags |= TFB_GFXFLAGS_SCALE_BIADAPT;
+ res_PutString ("config.scaler", "biadapt");
+ break;
+ case OPTVAL_BIADV_SCALE:
+ NewGfxFlags |= TFB_GFXFLAGS_SCALE_BIADAPTADV;
+ res_PutString ("config.scaler", "biadv");
+ break;
+ case OPTVAL_TRISCAN_SCALE:
+ NewGfxFlags |= TFB_GFXFLAGS_SCALE_TRISCAN;
+ res_PutString ("config.scaler", "triscan");
+ break;
+ case OPTVAL_HQXX_SCALE:
+ NewGfxFlags |= TFB_GFXFLAGS_SCALE_HQXX;
+ res_PutString ("config.scaler", "hq");
+ break;
+ default:
+ /* OPTVAL_NO_SCALE has no equivalent in gfxflags. */
+ res_PutString ("config.scaler", "no");
+ break;
+ }
+ if (opts->scanlines) {
+ NewGfxFlags |= TFB_GFXFLAGS_SCANLINES;
+ } else {
+ NewGfxFlags &= ~TFB_GFXFLAGS_SCANLINES;
+ }
+ if (opts->fullscreen)
+ NewGfxFlags |= TFB_GFXFLAGS_FULLSCREEN;
+ else
+ NewGfxFlags &= ~TFB_GFXFLAGS_FULLSCREEN;
+
+ res_PutBoolean ("config.scanlines", (BOOLEAN)opts->scanlines);
+ res_PutBoolean ("config.fullscreen", (BOOLEAN)opts->fullscreen);
+
+
+ if ((NewWidth != ScreenWidthActual) ||
+ (NewHeight != ScreenHeightActual) ||
+ (NewDriver != GraphicsDriver) ||
+ (NewGfxFlags != GfxFlags))
+ {
+ FlushGraphics ();
+ UninitVideoPlayer ();
+ TFB_DrawScreen_ReinitVideo (NewDriver, NewGfxFlags, NewWidth, NewHeight);
+ FlushGraphics ();
+ InitVideoPlayer (TRUE);
+ }
+
+ // Avoid setting gamma when it is not necessary
+ if (optGamma != 1.0f || sliderToGamma (opts->gamma) != 1.0f)
+ {
+ optGamma = sliderToGamma (opts->gamma);
+ setGammaCorrection (optGamma);
+ }
+
+ optSubtitles = (opts->subtitles == OPTVAL_ENABLED) ? TRUE : FALSE;
+ optWhichMenu = (opts->menu == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
+ optWhichFonts = (opts->text == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
+ optWhichCoarseScan = (opts->cscan == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
+ optSmoothScroll = (opts->scroll == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
+ optWhichShield = (opts->shield == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
+ optMeleeScale = (opts->meleezoom == OPTVAL_3DO) ? TFB_SCALE_TRILINEAR : TFB_SCALE_STEP;
+ opt3doMusic = (opts->music3do == OPTVAL_ENABLED);
+ optRemixMusic = (opts->musicremix == OPTVAL_ENABLED);
+ optSpeech = (opts->speech == OPTVAL_ENABLED);
+ optWhichIntro = (opts->intro == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
+ optStereoSFX = (opts->stereo == OPTVAL_ENABLED);
+ optKeepAspectRatio = (opts->keepaspect == OPTVAL_ENABLED);
+ PlayerControls[0] = opts->player1;
+ PlayerControls[1] = opts->player2;
+
+ res_PutBoolean ("config.subtitles", opts->subtitles == OPTVAL_ENABLED);
+ res_PutBoolean ("config.textmenu", opts->menu == OPTVAL_PC);
+ res_PutBoolean ("config.textgradients", opts->text == OPTVAL_PC);
+ res_PutBoolean ("config.iconicscan", opts->cscan == OPTVAL_3DO);
+ res_PutBoolean ("config.smoothscroll", opts->scroll == OPTVAL_3DO);
+
+ res_PutBoolean ("config.3domusic", opts->music3do == OPTVAL_ENABLED);
+ res_PutBoolean ("config.remixmusic", opts->musicremix == OPTVAL_ENABLED);
+ res_PutBoolean ("config.speech", opts->speech == OPTVAL_ENABLED);
+ res_PutBoolean ("config.3domovies", opts->intro == OPTVAL_3DO);
+ res_PutBoolean ("config.showfps", opts->fps == OPTVAL_ENABLED);
+ res_PutBoolean ("config.smoothmelee", opts->meleezoom == OPTVAL_3DO);
+ res_PutBoolean ("config.positionalsfx", opts->stereo == OPTVAL_ENABLED);
+ res_PutBoolean ("config.pulseshield", opts->shield == OPTVAL_3DO);
+ res_PutBoolean ("config.keepaspectratio", opts->keepaspect == OPTVAL_ENABLED);
+ res_PutInteger ("config.gamma", (int) (optGamma * GAMMA_SCALE + 0.5));
+ res_PutInteger ("config.player1control", opts->player1);
+ res_PutInteger ("config.player2control", opts->player2);
+
+ switch (opts->adriver) {
+ case OPTVAL_SILENCE:
+ res_PutString ("config.audiodriver", "none");
+ break;
+ case OPTVAL_MIXSDL:
+ res_PutString ("config.audiodriver", "mixsdl");
+ break;
+ case OPTVAL_OPENAL:
+ res_PutString ("config.audiodriver", "openal");
+ default:
+ /* Shouldn't happen; leave config untouched */
+ break;
+ }
+
+ switch (opts->aquality) {
+ case OPTVAL_LOW:
+ res_PutString ("config.audioquality", "low");
+ break;
+ case OPTVAL_MEDIUM:
+ res_PutString ("config.audioquality", "medium");
+ break;
+ case OPTVAL_HIGH:
+ res_PutString ("config.audioquality", "high");
+ break;
+ default:
+ /* Shouldn't happen; leave config untouched */
+ break;
+ }
+
+ res_PutInteger ("config.musicvol", opts->musicvol);
+ res_PutInteger ("config.sfxvol", opts->sfxvol);
+ res_PutInteger ("config.speechvol", opts->speechvol);
+ musicVolumeScale = opts->musicvol / 100.0f;
+ sfxVolumeScale = opts->sfxvol / 100.0f;
+ speechVolumeScale = opts->speechvol / 100.0f;
+ // update actual volumes
+ SetMusicVolume (musicVolume);
+ SetSpeechVolume (speechVolumeScale);
+
+ res_PutString ("keys.1.name", input_templates[0].name);
+ res_PutString ("keys.2.name", input_templates[1].name);
+ res_PutString ("keys.3.name", input_templates[2].name);
+ res_PutString ("keys.4.name", input_templates[3].name);
+ res_PutString ("keys.5.name", input_templates[4].name);
+ res_PutString ("keys.6.name", input_templates[5].name);
+
+ SaveResourceIndex (configDir, "uqm.cfg", "config.", TRUE);
+ SaveKeyConfiguration (configDir, "flight.cfg");
+}
diff --git a/src/uqm/setupmenu.h b/src/uqm/setupmenu.h
new file mode 100644
index 0000000..4dc74c0
--- /dev/null
+++ b/src/uqm/setupmenu.h
@@ -0,0 +1,100 @@
+// Copyright Michael Martin, 2004.
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_SETUPMENU_H_
+#define UQM_SETUPMENU_H_
+
+#include "controls.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef enum {
+ OPTVAL_DISABLED,
+ OPTVAL_ENABLED
+} OPT_ENABLABLE;
+
+typedef enum {
+ OPTVAL_PC,
+ OPTVAL_3DO
+} OPT_CONSOLETYPE;
+
+typedef enum {
+ OPTVAL_NO_SCALE,
+ OPTVAL_BILINEAR_SCALE,
+ OPTVAL_BIADAPT_SCALE,
+ OPTVAL_BIADV_SCALE,
+ OPTVAL_TRISCAN_SCALE,
+ OPTVAL_HQXX_SCALE,
+} OPT_SCALETYPE;
+
+typedef enum {
+ OPTVAL_320_240,
+ OPTVAL_640_480,
+ OPTVAL_800_600,
+ OPTVAL_1024_768,
+ OPTVAL_1280_960,
+ OPTVAL_CUSTOM
+} OPT_RESTYPE;
+
+typedef enum {
+ OPTVAL_PURE_IF_POSSIBLE,
+ OPTVAL_ALWAYS_GL
+} OPT_DRIVERTYPE;
+
+typedef enum {
+ OPTVAL_SILENCE,
+ OPTVAL_MIXSDL,
+ OPTVAL_OPENAL
+} OPT_ADRIVERTYPE;
+
+typedef enum {
+ OPTVAL_LOW,
+ OPTVAL_MEDIUM,
+ OPTVAL_HIGH
+} OPT_AQUALITYTYPE;
+
+/* At the moment, CONTROL_TEMPLATE is directly in this structure. If
+ * CONTROL_TEMPLATE and the options available diverge, this will need
+ * to change */
+typedef struct globalopts_struct {
+ OPT_SCALETYPE scaler;
+ OPT_RESTYPE res;
+ OPT_DRIVERTYPE driver;
+ OPT_ADRIVERTYPE adriver;
+ OPT_AQUALITYTYPE aquality;
+ OPT_ENABLABLE fullscreen, subtitles, scanlines, fps, stereo;
+ OPT_ENABLABLE music3do, musicremix, speech;
+ OPT_ENABLABLE keepaspect;
+ OPT_CONSOLETYPE menu, text, cscan, scroll, intro, meleezoom, shield;
+ CONTROL_TEMPLATE player1, player2;
+ int speechvol, musicvol, sfxvol;
+ int gamma;
+} GLOBALOPTS;
+
+void SetupMenu (void);
+
+void GetGlobalOptions (GLOBALOPTS *opts);
+void SetGlobalOptions (GLOBALOPTS *opts);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // UQM_SETUPMENU_H_
diff --git a/src/uqm/ship.c b/src/uqm/ship.c
new file mode 100644
index 0000000..747c929
--- /dev/null
+++ b/src/uqm/ship.c
@@ -0,0 +1,574 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "ship.h"
+
+#include "build.h"
+#include "collide.h"
+#include "colors.h"
+#include "globdata.h"
+#include "status.h"
+#include "hyper.h"
+#include "tactrans.h"
+#include "pickship.h"
+#include "intel.h"
+#include "init.h"
+#include "battle.h"
+#include "supermelee/pickmele.h"
+#include "races.h"
+#include "setup.h"
+#include "sounds.h"
+#include "libs/mathlib.h"
+
+
+void
+animation_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = ElementPtr->next_turn;
+ }
+}
+
+
+STATUS_FLAGS
+inertial_thrust (ELEMENT *ElementPtr)
+{
+#define MAX_ALLOWED_SPEED WORLD_TO_VELOCITY (DISPLAY_TO_WORLD (18))
+#define MAX_ALLOWED_SPEED_SQR ((DWORD)MAX_ALLOWED_SPEED * MAX_ALLOWED_SPEED)
+
+ COUNT CurrentAngle, TravelAngle;
+ COUNT max_thrust, thrust_increment;
+ VELOCITY_DESC *VelocityPtr;
+ STARSHIP *StarShipPtr;
+
+ VelocityPtr = &ElementPtr->velocity;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ CurrentAngle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+ TravelAngle = GetVelocityTravelAngle (VelocityPtr);
+ thrust_increment = StarShipPtr->RaceDescPtr->characteristics.thrust_increment;
+ max_thrust = StarShipPtr->RaceDescPtr->characteristics.max_thrust;
+ if (thrust_increment == max_thrust)
+ { // inertialess acceleration (Skiff)
+ SetVelocityVector (VelocityPtr,
+ max_thrust, StarShipPtr->ShipFacing);
+ return (SHIP_AT_MAX_SPEED);
+ }
+ else if (TravelAngle == CurrentAngle
+ && (StarShipPtr->cur_status_flags
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED))
+ && !(StarShipPtr->cur_status_flags & SHIP_IN_GRAVITY_WELL))
+ { // already maxed-out acceleration
+ return (StarShipPtr->cur_status_flags
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED));
+ }
+ else
+ {
+ SIZE delta_x, delta_y;
+ SIZE cur_delta_x, cur_delta_y;
+ DWORD desired_speed, max_speed;
+ DWORD current_speed;
+
+ thrust_increment = WORLD_TO_VELOCITY (thrust_increment);
+ GetCurrentVelocityComponents (VelocityPtr, &cur_delta_x, &cur_delta_y);
+ current_speed = VelocitySquared (cur_delta_x, cur_delta_y);
+ delta_x = cur_delta_x + COSINE (CurrentAngle, thrust_increment);
+ delta_y = cur_delta_y + SINE (CurrentAngle, thrust_increment);
+ desired_speed = VelocitySquared (delta_x, delta_y);
+ max_speed = VelocitySquared (WORLD_TO_VELOCITY (max_thrust), 0);
+
+ if (desired_speed <= max_speed)
+ { // normal acceleration
+ SetVelocityComponents (VelocityPtr, delta_x, delta_y);
+ }
+ else if (((StarShipPtr->cur_status_flags & SHIP_IN_GRAVITY_WELL)
+ && desired_speed <= MAX_ALLOWED_SPEED_SQR)
+ || desired_speed < current_speed)
+ { // acceleration in a gravity well within max allowed
+ // deceleration after gravity whip
+ SetVelocityComponents (VelocityPtr, delta_x, delta_y);
+ return (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+ }
+ else if (TravelAngle == CurrentAngle)
+ { // normal max acceleration, same vector
+ if (current_speed <= max_speed)
+ SetVelocityVector (VelocityPtr, max_thrust,
+ StarShipPtr->ShipFacing);
+ return (SHIP_AT_MAX_SPEED);
+ }
+ else
+ { // maxed-out acceleration at an angle to current travel vector
+ // thrusting at an angle while at max velocity only changes
+ // the travel vector, but does not really change the velocity
+
+ VELOCITY_DESC v = *VelocityPtr;
+
+ DeltaVelocityComponents (&v,
+ COSINE (CurrentAngle, thrust_increment >> 1)
+ - COSINE (TravelAngle, thrust_increment),
+ SINE (CurrentAngle, thrust_increment >> 1)
+ - SINE (TravelAngle, thrust_increment));
+ GetCurrentVelocityComponents (&v, &cur_delta_x, &cur_delta_y);
+ desired_speed = VelocitySquared (cur_delta_x, cur_delta_y);
+ if (desired_speed > max_speed)
+ {
+ if (desired_speed < current_speed)
+ *VelocityPtr = v;
+ return (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+ }
+
+ *VelocityPtr = v;
+ }
+
+ return (0);
+ }
+}
+
+void
+ship_preprocess (ELEMENT *ElementPtr)
+{
+ STATUS_FLAGS cur_status_flags;
+ STARSHIP *StarShipPtr;
+ RACE_DESC *RDPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ RDPtr = StarShipPtr->RaceDescPtr;
+
+ cur_status_flags =
+ StarShipPtr->cur_status_flags
+ & ~(LEFT | RIGHT | THRUST | WEAPON | SPECIAL);
+ if (!(ElementPtr->state_flags & APPEARING))
+ {
+ cur_status_flags |= StarShipPtr->ship_input_state
+ & (LEFT | RIGHT | THRUST | WEAPON | SPECIAL);
+ }
+ else
+ { // Preprocessing for the first time
+ ElementPtr->crew_level = RDPtr->ship_info.crew_level;
+
+ if (ElementPtr->playerNr == NPC_PLAYER_NUM
+ && LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ { // Sa-Matra
+ STAMP s;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+ s.origin.x = s.origin.y = 0;
+ s.frame = RDPtr->ship_data.captain_control.background;
+ DrawStamp (&s);
+ DestroyDrawable (ReleaseDrawable (s.frame));
+ RDPtr->ship_data.captain_control.background = 0;
+ SetContext (OldContext);
+ }
+ else if (LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER)
+ {
+ CONTEXT OldContext;
+
+ InitShipStatus (&RDPtr->ship_info, StarShipPtr, NULL);
+ OldContext = SetContext (StatusContext);
+ DrawCaptainsWindow (StarShipPtr);
+ SetContext (OldContext);
+ if (RDPtr->preprocess_func)
+ (*RDPtr->preprocess_func) (ElementPtr);
+
+ // XXX: Hack: Pkunk sets hTarget!=0 when it reincarnates. In that
+ // case there is no ship_transition() but a Phoenix transition
+ // instead.
+ if (ElementPtr->hTarget == 0)
+ {
+ ship_transition (ElementPtr);
+ }
+ else
+ {
+ ElementPtr->hTarget = 0;
+ if (!PLRPlaying ((MUSIC_REF)~0) && OpponentAlive (StarShipPtr))
+ BattleSong (TRUE);
+ }
+ return;
+ }
+ else
+ {
+ ElementPtr->current.location.x = LOG_SPACE_WIDTH >> 1;
+ ElementPtr->current.location.y = LOG_SPACE_HEIGHT >> 1;
+ ElementPtr->next.location = ElementPtr->current.location;
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+
+ if (hyper_transition (ElementPtr))
+ return;
+ }
+ }
+ StarShipPtr->cur_status_flags = cur_status_flags;
+
+ if (StarShipPtr->energy_counter)
+ --StarShipPtr->energy_counter;
+ else if (RDPtr->ship_info.energy_level < (BYTE)RDPtr->ship_info.max_energy
+ || (SBYTE)RDPtr->characteristics.energy_regeneration < 0)
+ DeltaEnergy (ElementPtr,
+ (SBYTE)RDPtr->characteristics.energy_regeneration);
+
+ if (RDPtr->preprocess_func)
+ {
+ (*RDPtr->preprocess_func) (ElementPtr);
+ cur_status_flags = StarShipPtr->cur_status_flags;
+ }
+
+ if (ElementPtr->turn_wait)
+ --ElementPtr->turn_wait;
+ else if (cur_status_flags & (LEFT | RIGHT))
+ {
+ if (cur_status_flags & LEFT)
+ StarShipPtr->ShipFacing =
+ NORMALIZE_FACING (StarShipPtr->ShipFacing - 1);
+ else
+ StarShipPtr->ShipFacing =
+ NORMALIZE_FACING (StarShipPtr->ShipFacing + 1);
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->next.image.frame,
+ StarShipPtr->ShipFacing);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = RDPtr->characteristics.turn_wait;
+ }
+
+ if (ElementPtr->thrust_wait)
+ --ElementPtr->thrust_wait;
+ else if (cur_status_flags & THRUST)
+ {
+ STATUS_FLAGS thrust_status;
+
+ thrust_status = inertial_thrust (ElementPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED
+ | SHIP_BEYOND_MAX_SPEED
+ | SHIP_IN_GRAVITY_WELL);
+ StarShipPtr->cur_status_flags |= thrust_status;
+
+ ElementPtr->thrust_wait = RDPtr->characteristics.thrust_wait;
+
+ if (!OBJECT_CLOAKED (ElementPtr)
+ && LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER)
+ {
+ spawn_ion_trail (ElementPtr);
+ }
+ }
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER)
+ PreProcessStatus (ElementPtr);
+}
+
+void
+ship_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ RACE_DESC *RDPtr;
+
+ if (ElementPtr->crew_level == 0)
+ return;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ RDPtr = StarShipPtr->RaceDescPtr;
+
+ if (StarShipPtr->weapon_counter)
+ --StarShipPtr->weapon_counter;
+ else if ((StarShipPtr->cur_status_flags
+ & WEAPON) && DeltaEnergy (ElementPtr,
+ -RDPtr->characteristics.weapon_energy_cost))
+ {
+ COUNT num_weapons;
+ HELEMENT Weapon[6];
+
+ num_weapons = (*RDPtr->init_weapon_func) (ElementPtr, Weapon);
+
+ if (num_weapons)
+ {
+ HELEMENT *WeaponPtr;
+ STARSHIP *StarShipPtr;
+ BOOLEAN played_sfx = FALSE;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ WeaponPtr = &Weapon[0];
+ do
+ {
+ HELEMENT w;
+ w = *WeaponPtr++;
+ if (w)
+ {
+ ELEMENT *EPtr;
+
+ LockElement (w, &EPtr);
+ SetElementStarShip (EPtr, StarShipPtr);
+ if (!played_sfx)
+ {
+ ProcessSound (RDPtr->ship_data.ship_sounds, EPtr);
+ played_sfx = TRUE;
+ }
+ UnlockElement (w);
+
+ PutElement (w);
+ }
+ } while (--num_weapons);
+
+ if (!played_sfx)
+ ProcessSound (RDPtr->ship_data.ship_sounds, ElementPtr);
+ }
+
+ StarShipPtr->weapon_counter =
+ RDPtr->characteristics.weapon_wait;
+ }
+
+ if (StarShipPtr->special_counter)
+ --StarShipPtr->special_counter;
+
+ if (RDPtr->postprocess_func)
+ (*RDPtr->postprocess_func) (ElementPtr);
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER)
+ PostProcessStatus (ElementPtr);
+}
+
+void
+collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (!(ElementPtr1->state_flags & FINITE_LIFE))
+ {
+ ElementPtr0->state_flags |= COLLISION;
+ if (GRAVITY_MASS (ElementPtr1->mass_points))
+ {
+ // Collision with a planet.
+ SIZE damage;
+
+ damage = ElementPtr0->hit_points >> 2;
+ if (damage == 0)
+ damage = 1;
+ do_damage (ElementPtr0, damage);
+
+ damage = TARGET_DAMAGED_FOR_1_PT + (damage >> 1);
+ if (damage > TARGET_DAMAGED_FOR_6_PLUS_PT)
+ damage = TARGET_DAMAGED_FOR_6_PLUS_PT;
+ ProcessSound (SetAbsSoundIndex (GameSounds, damage), ElementPtr0);
+ }
+ }
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static BOOLEAN
+spawn_ship (STARSHIP *StarShipPtr)
+{
+ HELEMENT hShip;
+ RACE_DESC *RDPtr;
+
+ RDPtr = load_ship (StarShipPtr->SpeciesID, TRUE);
+ if (!RDPtr)
+ return FALSE;
+
+ StarShipPtr->RaceDescPtr = RDPtr;
+
+ StarShipPtr->ship_input_state = 0;
+ StarShipPtr->cur_status_flags = 0;
+ StarShipPtr->old_status_flags = 0;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER
+ || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ {
+ if (StarShipPtr->crew_level == 0)
+ {
+ // SIS, already handled from sis_ship.c.
+ // RDPtr->ship_info.crew_level = GLOBAL_SIS (CrewEnlisted);
+ }
+ else
+ RDPtr->ship_info.crew_level = StarShipPtr->crew_level;
+
+ if (RDPtr->ship_info.crew_level > RDPtr->ship_info.max_crew)
+ RDPtr->ship_info.crew_level = RDPtr->ship_info.max_crew;
+ }
+
+ StarShipPtr->energy_counter = 0;
+ StarShipPtr->weapon_counter = 0;
+ StarShipPtr->special_counter = 0;
+
+ hShip = StarShipPtr->hShip;
+ if (hShip == 0)
+ {
+ hShip = AllocElement ();
+ if (hShip != 0)
+ InsertElement (hShip, GetHeadElement ());
+ }
+
+ StarShipPtr->hShip = hShip;
+ if (StarShipPtr->hShip != 0)
+ {
+ // Construct an ELEMENT for the STARSHIP
+ ELEMENT *ShipElementPtr;
+
+ LockElement (hShip, &ShipElementPtr);
+
+ ShipElementPtr->playerNr = StarShipPtr->playerNr;
+ ShipElementPtr->crew_level = 0;
+ ShipElementPtr->mass_points = RDPtr->characteristics.ship_mass;
+ ShipElementPtr->state_flags = APPEARING | PLAYER_SHIP | IGNORE_SIMILAR;
+ ShipElementPtr->turn_wait = 0;
+ ShipElementPtr->thrust_wait = 0;
+ ShipElementPtr->life_span = NORMAL_LIFE;
+ ShipElementPtr->colorCycleIndex = 0;
+
+ SetPrimType (&DisplayArray[ShipElementPtr->PrimIndex], STAMP_PRIM);
+ ShipElementPtr->current.image.farray = RDPtr->ship_data.ship;
+
+ if (ShipElementPtr->playerNr == NPC_PLAYER_NUM
+ && LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ {
+ // This is the Sa-Matra
+ StarShipPtr->ShipFacing = 0;
+ ShipElementPtr->current.image.frame =
+ SetAbsFrameIndex (RDPtr->ship_data.ship[0],
+ StarShipPtr->ShipFacing);
+ ShipElementPtr->current.location.x = LOG_SPACE_WIDTH >> 1;
+ ShipElementPtr->current.location.y = LOG_SPACE_HEIGHT >> 1;
+ ++ShipElementPtr->life_span;
+ }
+ else
+ {
+ StarShipPtr->ShipFacing = NORMALIZE_FACING (TFB_Random ());
+ if (inHQSpace ())
+ { // Only one ship is ever spawned in HyperSpace -- flagship
+ COUNT facing = GLOBAL (ShipFacing);
+ // XXX: Solar system reentry test depends on ShipFacing != 0
+ if (facing > 0)
+ --facing;
+
+ // XXX: This appears to set the facing to a random value
+ // for when the ship returns from an encounter back
+ // to HyperSpace. However, it is overwritten later
+ // in sis.c. See also r1614.
+ //GLOBAL (ShipFacing) = StarShipPtr->ShipFacing + 1;
+ StarShipPtr->ShipFacing = facing;
+ }
+ ShipElementPtr->current.image.frame =
+ SetAbsFrameIndex (RDPtr->ship_data.ship[0],
+ StarShipPtr->ShipFacing);
+ do
+ {
+ ShipElementPtr->current.location.x =
+ WRAP_X (DISPLAY_ALIGN_X (TFB_Random ()));
+ ShipElementPtr->current.location.y =
+ WRAP_Y (DISPLAY_ALIGN_Y (TFB_Random ()));
+ } while (CalculateGravity (ShipElementPtr)
+ || TimeSpaceMatterConflict (ShipElementPtr));
+ }
+
+ ShipElementPtr->preprocess_func = ship_preprocess;
+ ShipElementPtr->postprocess_func = ship_postprocess;
+ ShipElementPtr->death_func = ship_death;
+ ShipElementPtr->collision_func = collision;
+ ZeroVelocityComponents (&ShipElementPtr->velocity);
+
+ SetElementStarShip (ShipElementPtr, StarShipPtr);
+ ShipElementPtr->hTarget = 0;
+
+ UnlockElement (hShip);
+ }
+
+ return (hShip != 0);
+}
+
+// Select a new ship and spawn it.
+BOOLEAN
+GetNextStarShip (STARSHIP *LastStarShipPtr, COUNT which_side)
+{
+ HSTARSHIP hBattleShip;
+
+ hBattleShip = GetEncounterStarShip (LastStarShipPtr, which_side);
+ if (hBattleShip)
+ {
+ STARSHIP *StarShipPtr;
+
+ StarShipPtr = LockStarShip (&race_q[which_side], hBattleShip);
+ if (LastStarShipPtr)
+ {
+ if (StarShipPtr == LastStarShipPtr)
+ {
+ // Ship has been recycled (on infinite ship worlds).
+ LastStarShipPtr = 0;
+ }
+ else
+ StarShipPtr->hShip = LastStarShipPtr->hShip;
+ }
+
+ if (!spawn_ship (StarShipPtr))
+ {
+ UnlockStarShip (&race_q[which_side], hBattleShip);
+ return (FALSE);
+ }
+ UnlockStarShip (&race_q[which_side], hBattleShip);
+ }
+
+ if (LastStarShipPtr)
+ LastStarShipPtr->hShip = 0;
+
+ return (hBattleShip != 0);
+}
+
+BOOLEAN
+GetInitialStarShips (void)
+{
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ {
+ HSTARSHIP ships[NUM_PLAYERS];
+ COUNT i;
+
+ if (!GetInitialMeleeStarShips (ships))
+ return FALSE;
+
+ for (i = 0; i < NUM_PLAYERS; i++)
+ {
+ STARSHIP *StarShipPtr;
+ COUNT playerI = GetPlayerOrder (i);
+
+ StarShipPtr = LockStarShip (&race_q[playerI], ships[playerI]);
+ if (!spawn_ship (StarShipPtr))
+ {
+ UnlockStarShip (&race_q[playerI], ships[playerI]);
+ return FALSE;
+ }
+ UnlockStarShip (&race_q[playerI], ships[playerI]);
+ }
+ return TRUE;
+ }
+ else
+ {
+ int i;
+
+ for (i = NUM_PLAYERS; i > 0; --i)
+ {
+ if (!GetNextStarShip (NULL, i - 1))
+ return FALSE;
+ }
+ return TRUE;
+ }
+}
+
diff --git a/src/uqm/ship.h b/src/uqm/ship.h
new file mode 100644
index 0000000..8f0c72c
--- /dev/null
+++ b/src/uqm/ship.h
@@ -0,0 +1,43 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SHIP_H_INCL_
+#define UQM_SHIP_H_INCL_
+
+#include "libs/compiler.h"
+#include "races.h"
+#include "element.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern BOOLEAN GetNextStarShip (STARSHIP *LastStarShipPtr, COUNT which_side);
+extern BOOLEAN GetInitialStarShips (void);
+
+extern void animation_preprocess (ELEMENT *ElementPtr);
+extern void ship_preprocess (ELEMENT *ElementPtr);
+extern void ship_postprocess (ELEMENT *ElementPtr);
+extern void collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1);
+
+extern STATUS_FLAGS inertial_thrust (ELEMENT *ElementPtr);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SHIP_H_INCL_ */
diff --git a/src/uqm/shipcont.h b/src/uqm/shipcont.h
new file mode 100644
index 0000000..e265e7d
--- /dev/null
+++ b/src/uqm/shipcont.h
@@ -0,0 +1,44 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_SHIPCONT_H_
+#define UQM_SHIPCONT_H_
+
+#include "menustat.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define FIELD_WIDTH (STATUS_WIDTH - 5)
+
+extern void CargoMenu (void);
+extern BOOLEAN RosterMenu (void);
+extern BOOLEAN DevicesMenu (void);
+extern BOOLEAN StarMap (void);
+
+extern void DrawCargoStrings (BYTE OldElement, BYTE NewElement);
+extern void ShowRemainingCapacity (void);
+
+extern SIZE InventoryDevices (BYTE *pDeviceMap, COUNT Size);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SHIPCONT_H_ */
diff --git a/src/uqm/ships/Makeinfo b/src/uqm/ships/Makeinfo
new file mode 100644
index 0000000..e77d2db
--- /dev/null
+++ b/src/uqm/ships/Makeinfo
@@ -0,0 +1,5 @@
+uqm_SUBDIRS="androsyn arilou blackurq chenjesu chmmr druuge human ilwrath
+ lastbat melnorme mmrnmhrm mycon orz pkunk probe shofixti sis_ship
+ slylandr spathi supox syreen thradd umgah urquan utwig vux yehat
+ zoqfot"
+uqm_HFILES="ship.h"
diff --git a/src/uqm/ships/androsyn/Makeinfo b/src/uqm/ships/androsyn/Makeinfo
new file mode 100644
index 0000000..2f2f511
--- /dev/null
+++ b/src/uqm/ships/androsyn/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="androsyn.c"
+uqm_HFILES="androsyn.h icode.h resinst.h"
diff --git a/src/uqm/ships/androsyn/androsyn.c b/src/uqm/ships/androsyn/androsyn.c
new file mode 100644
index 0000000..1139d8d
--- /dev/null
+++ b/src/uqm/ships/androsyn/androsyn.c
@@ -0,0 +1,528 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "androsyn.h"
+#include "resinst.h"
+
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 20
+#define MAX_ENERGY 24
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 8
+#define MAX_THRUST 24
+#define THRUST_INCREMENT 3
+#define TURN_WAIT 4
+#define THRUST_WAIT 0
+#define SHIP_MASS 6
+
+// Bubbles
+#define WEAPON_ENERGY_COST 3
+#define WEAPON_WAIT 0
+#define ANDROSYNTH_OFFSET 14
+#define MISSILE_OFFSET 3
+#define MISSILE_SPEED DISPLAY_TO_WORLD (8)
+#define MISSILE_LIFE 200
+#define MISSILE_HITS 3
+#define MISSILE_DAMAGE 2
+#define TRACK_WAIT 2
+
+// Blazer
+#define SPECIAL_ENERGY_COST 2
+#define BLAZER_DEGENERATION (-1)
+#define SPECIAL_WAIT 0
+#define BLAZER_OFFSET 10
+#define BLAZER_THRUST 60
+#define BLAZER_TURN_WAIT 1
+#define BLAZER_DAMAGE 3
+#define BLAZER_MASS 1
+
+static RACE_DESC androsynth_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | SEEKING_WEAPON,
+ 15, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ ANDROSYNTH_RACE_STRINGS,
+ ANDROSYNTH_ICON_MASK_PMAP_ANIM,
+ ANDROSYNTH_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ INFINITE_RADIUS, /* Initial sphere of influence radius */
+ // XXX: Why infinite radius? Bug?
+ { /* Known location (center of SoI) */
+ MAX_X_UNIVERSE >> 1, MAX_Y_UNIVERSE >> 1,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ ANDROSYNTH_BIG_MASK_PMAP_ANIM,
+ ANDROSYNTH_MED_MASK_PMAP_ANIM,
+ ANDROSYNTH_SML_MASK_PMAP_ANIM,
+ },
+ {
+ BUBBLE_BIG_MASK_PMAP_ANIM,
+ BUBBLE_MED_MASK_PMAP_ANIM,
+ BUBBLE_SML_MASK_PMAP_ANIM,
+ },
+ {
+ BLAZER_BIG_MASK_PMAP_ANIM,
+ BLAZER_MED_MASK_PMAP_ANIM,
+ BLAZER_SML_MASK_PMAP_ANIM,
+ },
+ {
+ ANDROSYNTH_CAPT_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ ANDROSYNTH_VICTORY_SONG,
+ ANDROSYNTH_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ LONG_RANGE_WEAPON >> 2,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+
+// Private per-instance ship data
+typedef struct
+{
+ ElementCollisionFunc* collision_func;
+} ANDROSYNTH_DATA;
+
+// Local typedef
+typedef ANDROSYNTH_DATA CustomShipData_t;
+
+// Retrieve race-specific ship data from a race desc
+static CustomShipData_t *
+GetCustomShipData (RACE_DESC *pRaceDesc)
+{
+ return pRaceDesc->data;
+}
+
+// Set the race-specific data in a race desc
+// (Re)Allocates its own storage for the data.
+static void
+SetCustomShipData (RACE_DESC *pRaceDesc, const CustomShipData_t *data)
+{
+ if (pRaceDesc->data == data)
+ return; // no-op
+
+ if (pRaceDesc->data) // Out with the old
+ {
+ HFree (pRaceDesc->data);
+ pRaceDesc->data = NULL;
+ }
+
+ if (data) // In with the new
+ {
+ CustomShipData_t* newData = HMalloc (sizeof (*data));
+ *newData = *data;
+ pRaceDesc->data = newData;
+ }
+}
+
+static void
+blazer_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ BYTE old_offs;
+ COUNT old_crew_level;
+ COUNT old_life;
+
+ old_crew_level = ElementPtr0->crew_level;
+ old_life = ElementPtr0->life_span;
+ old_offs = ElementPtr0->blast_offset;
+ ElementPtr0->blast_offset = BLAZER_OFFSET;
+ ElementPtr0->mass_points = BLAZER_DAMAGE;
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ ElementPtr0->mass_points = BLAZER_MASS;
+ ElementPtr0->blast_offset = old_offs;
+ ElementPtr0->life_span = old_life;
+ ElementPtr0->crew_level = old_crew_level;
+
+ ElementPtr0->state_flags &= ~(DISAPPEARING | NONSOLID);
+ collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+}
+
+static void
+bubble_preprocess (ELEMENT *ElementPtr)
+{
+ BYTE thrust_wait, turn_wait;
+
+ thrust_wait = HINIBBLE (ElementPtr->turn_wait);
+ turn_wait = LONIBBLE (ElementPtr->turn_wait);
+ if (thrust_wait > 0)
+ --thrust_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ thrust_wait = (BYTE)((COUNT)TFB_Random () & 3);
+ }
+
+ if (turn_wait > 0)
+ --turn_wait;
+ else
+ {
+ COUNT facing;
+ SIZE delta_facing;
+
+ facing = NORMALIZE_FACING (ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&ElementPtr->velocity)));
+ if ((delta_facing = TrackShip (ElementPtr, &facing)) == -1)
+ facing = (COUNT)TFB_Random ();
+ else if (delta_facing <= ANGLE_TO_FACING (HALF_CIRCLE))
+ facing += (COUNT)TFB_Random () & (ANGLE_TO_FACING (HALF_CIRCLE) - 1);
+ else
+ facing -= (COUNT)TFB_Random () & (ANGLE_TO_FACING (HALF_CIRCLE) - 1);
+ SetVelocityVector (&ElementPtr->velocity,
+ MISSILE_SPEED, facing);
+ turn_wait = TRACK_WAIT;
+ }
+
+ ElementPtr->turn_wait = MAKE_BYTE (turn_wait, thrust_wait);
+}
+
+static COUNT
+initialize_bubble (ELEMENT *ShipPtr, HELEMENT BubbleArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = ANDROSYNTH_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = bubble_preprocess;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ BubbleArray[0] = initialize_missile (&MissileBlock);
+
+ if (BubbleArray[0])
+ {
+ ELEMENT *BubblePtr;
+
+ LockElement (BubbleArray[0], &BubblePtr);
+ BubblePtr->turn_wait = 0;
+ UnlockElement (BubbleArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+androsynth_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ /* in blazer form */
+ if (ShipPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.special)
+ {
+ ObjectsOfConcern[CREW_OBJECT_INDEX].ObjectPtr = 0;
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->MoveState == ENTICE)
+ {
+ if ((lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE)
+ && !(lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT))
+ lpEvalDesc->MoveState = AVOID;
+ else
+ lpEvalDesc->ObjectPtr = 0;
+ }
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+ }
+ else
+ {
+ STARSHIP *pEnemyStarShip = NULL;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEvalDesc->ObjectPtr)
+ {
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &pEnemyStarShip);
+ if (lpEvalDesc->which_turn <= 16
+ && (StarShipPtr->special_counter > 0
+ || StarShipPtr->RaceDescPtr->ship_info.energy_level < MAX_ENERGY / 3
+ || ((WEAPON_RANGE (&pEnemyStarShip->RaceDescPtr->cyborg_control) <= CLOSE_RANGE_WEAPON
+ && lpEvalDesc->ObjectPtr->crew_level > BLAZER_DAMAGE)
+ || (lpEvalDesc->ObjectPtr->crew_level > (BLAZER_DAMAGE * 3)
+ && MANEUVERABILITY (&pEnemyStarShip->RaceDescPtr->cyborg_control) > SLOW_SHIP))))
+ lpEvalDesc->MoveState = ENTICE;
+ }
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ if (StarShipPtr->special_counter == 0)
+ {
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if ((ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn <= 4)
+ || (lpEvalDesc->ObjectPtr
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >= MAX_ENERGY / 3
+ && (WEAPON_RANGE (&pEnemyStarShip->RaceDescPtr->cyborg_control) >=
+ WEAPON_RANGE (&StarShipPtr->RaceDescPtr->cyborg_control) << 1
+ || (lpEvalDesc->which_turn < 16
+ && (WEAPON_RANGE (&pEnemyStarShip->RaceDescPtr->cyborg_control) > CLOSE_RANGE_WEAPON
+ || lpEvalDesc->ObjectPtr->crew_level <= BLAZER_DAMAGE)
+ && (lpEvalDesc->ObjectPtr->crew_level <= (BLAZER_DAMAGE * 3)
+ || MANEUVERABILITY (&pEnemyStarShip->RaceDescPtr->cyborg_control) <=
+ SLOW_SHIP)))))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+
+ if (!(StarShipPtr->ship_input_state & SPECIAL)
+ && StarShipPtr->weapon_counter == 0
+ && lpEvalDesc->ObjectPtr)
+ {
+ if (lpEvalDesc->which_turn <= 4)
+ StarShipPtr->ship_input_state |= WEAPON;
+ else if (lpEvalDesc->MoveState != PURSUE
+ && lpEvalDesc->which_turn <= 12)
+ {
+ COUNT travel_facing, direction_facing;
+ SIZE delta_x, delta_y,
+ ship_delta_x, ship_delta_y,
+ other_delta_x, other_delta_y;
+
+ GetCurrentVelocityComponents (&ShipPtr->velocity,
+ &ship_delta_x, &ship_delta_y);
+ GetCurrentVelocityComponents (&lpEvalDesc->ObjectPtr->velocity,
+ &other_delta_x, &other_delta_y);
+ delta_x = ship_delta_x - other_delta_x;
+ delta_y = ship_delta_y - other_delta_y;
+ travel_facing = ARCTAN (delta_x, delta_y);
+
+ delta_x =
+ lpEvalDesc->ObjectPtr->next.location.x -
+ ShipPtr->next.location.x;
+ delta_y =
+ lpEvalDesc->ObjectPtr->next.location.y -
+ ShipPtr->next.location.y;
+ direction_facing = ARCTAN (delta_x, delta_y);
+
+ if (NORMALIZE_ANGLE (travel_facing
+ - direction_facing + OCTANT) <= QUADRANT)
+ StarShipPtr->ship_input_state |= WEAPON;
+ }
+ }
+ }
+}
+
+static void
+androsynth_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ /* take care of blazer effect */
+ if (ElementPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.special)
+ {
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ || StarShipPtr->RaceDescPtr->ship_info.energy_level == 0)
+ {
+ StarShipPtr->RaceDescPtr->characteristics.energy_regeneration =
+ (BYTE)BLAZER_DEGENERATION;
+ StarShipPtr->energy_counter = ENERGY_WAIT;
+
+ if (StarShipPtr->cur_status_flags & SPECIAL)
+ {
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1),
+ ElementPtr); /* COMET_ON */
+ ElementPtr->turn_wait = 0;
+ ElementPtr->thrust_wait = 0;
+ StarShipPtr->RaceDescPtr->characteristics.special_wait =
+ StarShipPtr->RaceDescPtr->characteristics.turn_wait;
+ ElementPtr->mass_points = BLAZER_MASS;
+ StarShipPtr->RaceDescPtr->characteristics.turn_wait
+ = BLAZER_TURN_WAIT;
+
+ /* Save the current collision func because we were not the
+ * ones who set it */
+ {
+ const ANDROSYNTH_DATA shipData = { ElementPtr->collision_func };
+ SetCustomShipData (StarShipPtr->RaceDescPtr, &shipData);
+ ElementPtr->collision_func = blazer_collision;
+ }
+ }
+ }
+
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level == 0)
+ /* if blazer wasn't able to change back into ship
+ * give it a little more juice to try to get out
+ * of its predicament.
+ */
+ {
+ DeltaEnergy (ElementPtr, -BLAZER_DEGENERATION);
+ StarShipPtr->energy_counter = 1;
+ }
+ }
+}
+
+static void
+androsynth_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ STATUS_FLAGS cur_status_flags;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ cur_status_flags = StarShipPtr->cur_status_flags;
+ if (ElementPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.ship)
+ {
+ if (cur_status_flags & SPECIAL)
+ {
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level < SPECIAL_ENERGY_COST)
+ DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST); /* so text will flash */
+ else
+ {
+ cur_status_flags &= ~WEAPON;
+
+ ElementPtr->next.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (StarShipPtr->RaceDescPtr->ship_data.special[0],
+ ElementPtr->next.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+ }
+ }
+ }
+ else
+ {
+ cur_status_flags &= ~(THRUST | WEAPON | SPECIAL);
+
+ /* protection against vux */
+ if (StarShipPtr->RaceDescPtr->characteristics.turn_wait > BLAZER_TURN_WAIT)
+ {
+ StarShipPtr->RaceDescPtr->characteristics.special_wait +=
+ StarShipPtr->RaceDescPtr->characteristics.turn_wait
+ - BLAZER_TURN_WAIT;
+ StarShipPtr->RaceDescPtr->characteristics.turn_wait = BLAZER_TURN_WAIT;
+ }
+
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level == 0)
+ {
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ cur_status_flags &= ~(LEFT | RIGHT
+ | SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+
+ StarShipPtr->RaceDescPtr->characteristics.turn_wait =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ StarShipPtr->RaceDescPtr->characteristics.energy_regeneration = ENERGY_REGENERATION;
+ ElementPtr->mass_points = SHIP_MASS;
+ ElementPtr->collision_func =
+ GetCustomShipData (StarShipPtr->RaceDescPtr)->collision_func;
+ ElementPtr->next.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.ship;
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (StarShipPtr->RaceDescPtr->ship_data.ship[0],
+ ElementPtr->next.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+ }
+ else
+ {
+ if (ElementPtr->thrust_wait)
+ --ElementPtr->thrust_wait;
+ else
+ {
+ COUNT facing;
+
+ facing = StarShipPtr->ShipFacing;
+ if (ElementPtr->turn_wait == 0
+ && (cur_status_flags & (LEFT | RIGHT)))
+ {
+ if (cur_status_flags & LEFT)
+ --facing;
+ else
+ ++facing;
+ }
+
+ SetVelocityVector (&ElementPtr->velocity,
+ BLAZER_THRUST, NORMALIZE_FACING (facing));
+ cur_status_flags |= SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED;
+ }
+ }
+ }
+ StarShipPtr->cur_status_flags = cur_status_flags;
+}
+
+static void
+uninit_androsynth (RACE_DESC *pRaceDesc)
+{
+ SetCustomShipData (pRaceDesc, NULL);
+}
+
+
+RACE_DESC*
+init_androsynth (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ androsynth_desc.uninit_func = uninit_androsynth;
+ androsynth_desc.preprocess_func = androsynth_preprocess;
+ androsynth_desc.postprocess_func = androsynth_postprocess;
+ androsynth_desc.init_weapon_func = initialize_bubble;
+ androsynth_desc.cyborg_control.intelligence_func = androsynth_intelligence;
+
+ RaceDescPtr = &androsynth_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/androsyn/androsyn.h b/src/uqm/ships/androsyn/androsyn.h
new file mode 100644
index 0000000..43fe88d
--- /dev/null
+++ b/src/uqm/ships/androsyn/androsyn.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ANDROSYN_H
+#define ANDROSYN_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_androsynth (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* ANDROSYN_H */
+
diff --git a/src/uqm/ships/androsyn/icode.h b/src/uqm/ships/androsyn/icode.h
new file mode 100644
index 0000000..67f053a
--- /dev/null
+++ b/src/uqm/ships/androsyn/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ANDROSYNTH_CODE "ship.androsynth.code"
diff --git a/src/uqm/ships/androsyn/resinst.h b/src/uqm/ships/androsyn/resinst.h
new file mode 100644
index 0000000..94b4a3f
--- /dev/null
+++ b/src/uqm/ships/androsyn/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ANDROSYNTH_BIG_MASK_PMAP_ANIM "ship.androsynth.graphics.guardian.large"
+#define ANDROSYNTH_CAPT_MASK_PMAP_ANIM "ship.androsynth.graphics.captain"
+#define ANDROSYNTH_ICON_MASK_PMAP_ANIM "ship.androsynth.icons"
+#define ANDROSYNTH_MED_MASK_PMAP_ANIM "ship.androsynth.graphics.guardian.medium"
+#define ANDROSYNTH_MICON_MASK_PMAP_ANIM "ship.androsynth.meleeicons"
+#define ANDROSYNTH_RACE_STRINGS "ship.androsynth.text"
+#define ANDROSYNTH_SHIP_SOUNDS "ship.androsynth.sounds"
+#define ANDROSYNTH_SML_MASK_PMAP_ANIM "ship.androsynth.graphics.guardian.small"
+#define ANDROSYNTH_VICTORY_SONG "ship.androsynth.ditty"
+#define BLAZER_BIG_MASK_PMAP_ANIM "ship.androsynth.graphics.blazer.large"
+#define BLAZER_MED_MASK_PMAP_ANIM "ship.androsynth.graphics.blazer.medium"
+#define BLAZER_SML_MASK_PMAP_ANIM "ship.androsynth.graphics.blazer.small"
+#define BUBBLE_BIG_MASK_PMAP_ANIM "ship.androsynth.graphics.bubble.large"
+#define BUBBLE_MED_MASK_PMAP_ANIM "ship.androsynth.graphics.bubble.medium"
+#define BUBBLE_SML_MASK_PMAP_ANIM "ship.androsynth.graphics.bubble.small"
diff --git a/src/uqm/ships/arilou/Makeinfo b/src/uqm/ships/arilou/Makeinfo
new file mode 100644
index 0000000..f132bdd
--- /dev/null
+++ b/src/uqm/ships/arilou/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="arilou.c"
+uqm_HFILES="arilou.h icode.h resinst.h"
diff --git a/src/uqm/ships/arilou/arilou.c b/src/uqm/ships/arilou/arilou.c
new file mode 100644
index 0000000..78340a3
--- /dev/null
+++ b/src/uqm/ships/arilou/arilou.c
@@ -0,0 +1,303 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "arilou.h"
+#include "resinst.h"
+
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 6
+#define MAX_ENERGY 20
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 6
+#define MAX_THRUST /* DISPLAY_TO_WORLD (10) */ 40
+#define THRUST_INCREMENT MAX_THRUST
+#define THRUST_WAIT 0
+#define TURN_WAIT 0
+#define SHIP_MASS 1
+
+// Tracking Laser
+#define WEAPON_ENERGY_COST 2
+#define WEAPON_WAIT 1
+#define ARILOU_OFFSET 9
+#define LASER_RANGE DISPLAY_TO_WORLD (100 + ARILOU_OFFSET)
+
+// Teleporter
+#define SPECIAL_ENERGY_COST 3
+#define SPECIAL_WAIT 2
+#define HYPER_LIFE 5
+
+static RACE_DESC arilou_desc =
+{
+ { /* SHIP_INFO */
+ /* FIRES_FORE | */ IMMEDIATE_WEAPON,
+ 16, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ ARILOU_RACE_STRINGS,
+ ARILOU_ICON_MASK_PMAP_ANIM,
+ ARILOU_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 250 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 438, 6372,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ ARILOU_BIG_MASK_PMAP_ANIM,
+ ARILOU_MED_MASK_PMAP_ANIM,
+ ARILOU_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ WARP_BIG_MASK_PMAP_ANIM,
+ WARP_MED_MASK_PMAP_ANIM,
+ WARP_SML_MASK_PMAP_ANIM,
+ },
+ {
+ ARILOU_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ ARILOU_VICTORY_SONG,
+ ARILOU_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ LASER_RANGE >> 1,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static COUNT
+initialize_autoaim_laser (ELEMENT *ShipPtr, HELEMENT LaserArray[])
+{
+ COUNT orig_facing;
+ SIZE delta_facing;
+ STARSHIP *StarShipPtr;
+ LASER_BLOCK LaserBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ LaserBlock.face = orig_facing = StarShipPtr->ShipFacing;
+ if ((delta_facing = TrackShip (ShipPtr, &LaserBlock.face)) > 0)
+ LaserBlock.face = NORMALIZE_FACING (orig_facing + delta_facing);
+ ShipPtr->hTarget = 0;
+
+ LaserBlock.cx = ShipPtr->next.location.x;
+ LaserBlock.cy = ShipPtr->next.location.y;
+ LaserBlock.ex = COSINE (FACING_TO_ANGLE (LaserBlock.face), LASER_RANGE);
+ LaserBlock.ey = SINE (FACING_TO_ANGLE (LaserBlock.face), LASER_RANGE);
+ LaserBlock.sender = ShipPtr->playerNr;
+ LaserBlock.flags = IGNORE_SIMILAR;
+ LaserBlock.pixoffs = ARILOU_OFFSET;
+ LaserBlock.color = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E);
+ LaserArray[0] = initialize_laser (&LaserBlock);
+
+ return (1);
+}
+
+static void
+arilou_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ StarShipPtr->ship_input_state |= THRUST;
+
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].MoveState = ENTICE;
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ if (StarShipPtr->special_counter == 0)
+ {
+ EVALUATE_DESC *lpEvalDesc;
+
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->which_turn <= 6)
+ {
+ BOOLEAN IsTrackingWeapon;
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if (((EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags
+ & SEEKING_WEAPON) &&
+ lpEvalDesc->ObjectPtr->next.image.farray ==
+ EnemyStarShipPtr->RaceDescPtr->ship_data.weapon) ||
+ ((EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags
+ & SEEKING_SPECIAL) &&
+ lpEvalDesc->ObjectPtr->next.image.farray ==
+ EnemyStarShipPtr->RaceDescPtr->ship_data.special))
+ IsTrackingWeapon = TRUE;
+ else
+ IsTrackingWeapon = FALSE;
+
+ if (((lpEvalDesc->ObjectPtr->state_flags & PLAYER_SHIP) /* means IMMEDIATE WEAPON */
+ || (IsTrackingWeapon && (lpEvalDesc->which_turn == 1
+ || (lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT))) /* FIGHTERS!!! */
+ || PlotIntercept (lpEvalDesc->ObjectPtr, ShipPtr, 3, 0))
+ && !(TFB_Random () & 3))
+ {
+ StarShipPtr->ship_input_state &= ~(LEFT | RIGHT | THRUST | WEAPON);
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ }
+ }
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level <= SPECIAL_ENERGY_COST << 1)
+ StarShipPtr->ship_input_state &= ~WEAPON;
+}
+
+static void
+arilou_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (!(ElementPtr->state_flags & NONSOLID))
+ {
+ if (ElementPtr->thrust_wait == 0)
+ {
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ StarShipPtr->cur_status_flags &= ~SHIP_AT_MAX_SPEED;
+ }
+
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ /* Special key is pressed; start teleport */
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | LEFT | RIGHT | THRUST | WEAPON);
+
+ ElementPtr->state_flags |= NONSOLID | FINITE_LIFE | CHANGING;
+ ElementPtr->life_span = HYPER_LIFE;
+
+ ElementPtr->next.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ ElementPtr->next.image.frame =
+ StarShipPtr->RaceDescPtr->ship_data.special[0];
+
+ ProcessSound (SetAbsSoundIndex (
+ /* HYPERJUMP */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+ }
+ else if (ElementPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.special)
+ {
+ COUNT life_span;
+
+ StarShipPtr->cur_status_flags =
+ (StarShipPtr->cur_status_flags
+ & ~(LEFT | RIGHT | THRUST | WEAPON | SPECIAL))
+ | (StarShipPtr->old_status_flags
+ & (LEFT | RIGHT | THRUST | WEAPON | SPECIAL));
+ ++StarShipPtr->weapon_counter;
+ ++StarShipPtr->special_counter;
+ ++StarShipPtr->energy_counter;
+ ++ElementPtr->turn_wait;
+ ++ElementPtr->thrust_wait;
+
+ if ((life_span = ElementPtr->life_span) == NORMAL_LIFE)
+ {
+ /* Ending teleport */
+ ElementPtr->state_flags &= ~(NONSOLID | FINITE_LIFE);
+ ElementPtr->state_flags |= APPEARING;
+ ElementPtr->current.image.farray =
+ ElementPtr->next.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.ship;
+ ElementPtr->current.image.frame =
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (StarShipPtr->RaceDescPtr->ship_data.ship[0],
+ StarShipPtr->ShipFacing);
+ InitIntersectStartPoint (ElementPtr);
+ }
+ else
+ {
+ /* Teleporting in progress */
+ --life_span;
+ if (life_span != 2)
+ {
+ if (life_span < 2)
+ ElementPtr->next.image.frame =
+ DecFrameIndex (ElementPtr->next.image.frame);
+ else
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->next.image.frame);
+ }
+ else
+ {
+ ElementPtr->next.location.x =
+ WRAP_X (DISPLAY_ALIGN_X (TFB_Random ()));
+ ElementPtr->next.location.y =
+ WRAP_Y (DISPLAY_ALIGN_Y (TFB_Random ()));
+ }
+ }
+
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+RACE_DESC*
+init_arilou (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ arilou_desc.preprocess_func = arilou_preprocess;
+ arilou_desc.init_weapon_func = initialize_autoaim_laser;
+ arilou_desc.cyborg_control.intelligence_func = arilou_intelligence;
+
+ RaceDescPtr = &arilou_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/arilou/arilou.h b/src/uqm/ships/arilou/arilou.h
new file mode 100644
index 0000000..8e65d58
--- /dev/null
+++ b/src/uqm/ships/arilou/arilou.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ARILOU_H
+#define ARILOU_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_arilou (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* ARILOU_H */
+
diff --git a/src/uqm/ships/arilou/icode.h b/src/uqm/ships/arilou/icode.h
new file mode 100644
index 0000000..81d4fff
--- /dev/null
+++ b/src/uqm/ships/arilou/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ARILOU_CODE "ship.arilou.code"
diff --git a/src/uqm/ships/arilou/resinst.h b/src/uqm/ships/arilou/resinst.h
new file mode 100644
index 0000000..173e222
--- /dev/null
+++ b/src/uqm/ships/arilou/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ARILOU_BIG_MASK_PMAP_ANIM "ship.arilou.graphics.skiff.large"
+#define ARILOU_CAPTAIN_MASK_PMAP_ANIM "ship.arilou.graphics.captain"
+#define ARILOU_ICON_MASK_PMAP_ANIM "ship.arilou.icons"
+#define ARILOU_MED_MASK_PMAP_ANIM "ship.arilou.graphics.skiff.medium"
+#define ARILOU_MICON_MASK_PMAP_ANIM "ship.arilou.meleeicons"
+#define ARILOU_RACE_STRINGS "ship.arilou.text"
+#define ARILOU_SHIP_SOUNDS "ship.arilou.sounds"
+#define ARILOU_SML_MASK_PMAP_ANIM "ship.arilou.graphics.skiff.small"
+#define ARILOU_VICTORY_SONG "ship.arilou.ditty"
+#define WARP_BIG_MASK_PMAP_ANIM "ship.arilou.graphics.warp.large"
+#define WARP_MED_MASK_PMAP_ANIM "ship.arilou.graphics.warp.medium"
+#define WARP_SML_MASK_PMAP_ANIM "ship.arilou.graphics.warp.small"
diff --git a/src/uqm/ships/blackurq/Makeinfo b/src/uqm/ships/blackurq/Makeinfo
new file mode 100644
index 0000000..62ca12e
--- /dev/null
+++ b/src/uqm/ships/blackurq/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="blackurq.c"
+uqm_HFILES="blackurq.h icode.h resinst.h"
diff --git a/src/uqm/ships/blackurq/blackurq.c b/src/uqm/ships/blackurq/blackurq.c
new file mode 100644
index 0000000..286191d
--- /dev/null
+++ b/src/uqm/ships/blackurq/blackurq.c
@@ -0,0 +1,567 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "blackurq.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+
+// Core characteristics
+#define MAX_CREW MAX_CREW_SIZE
+#define MAX_ENERGY MAX_ENERGY_SIZE
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 4
+#define MAX_THRUST 30
+#define THRUST_INCREMENT 6
+#define TURN_WAIT 4
+#define THRUST_WAIT 6
+#define SHIP_MASS 10
+
+// Buzzsaw
+#define WEAPON_ENERGY_COST 6
+#define WEAPON_WAIT 6
+#define MISSILE_OFFSET 9
+#define KOHR_AH_OFFSET 28
+#define MISSILE_SPEED 64
+#define MISSILE_LIFE 64
+ /* actually, it's as long as you hold the button down.*/
+#define MISSILE_HITS 10
+#define MISSILE_DAMAGE 4
+#define SAW_RATE 0
+#define MAX_SAWS 8
+#define ACTIVATE_RANGE 224
+ /* Originally SPACE_WIDTH - the distance within which
+ * stationary sawblades will home */
+#define TRACK_WAIT 4
+#define FRAGMENT_SPEED MISSILE_SPEED
+#define FRAGMENT_LIFE 10
+#define FRAGMENT_RANGE (FRAGMENT_LIFE * FRAGMENT_SPEED)
+
+// F.R.I.E.D.
+#define SPECIAL_ENERGY_COST (MAX_ENERGY_SIZE / 2)
+#define SPECIAL_WAIT 9
+#define GAS_OFFSET 2
+#define GAS_SPEED 16
+#define GAS_RATE 2 /* Controls animation of the gas cloud decay - the decay
+ * animation advances one frame every GAS_RATE frames. */
+#define GAS_HITS 100
+#define GAS_DAMAGE 3
+#define GAS_ALT_DAMAGE 50
+#define NUM_GAS_CLOUDS 16
+
+static RACE_DESC black_urquan_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 30, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ KOHR_AH_RACE_STRINGS,
+ KOHR_AH_ICON_MASK_PMAP_ANIM,
+ KOHR_AH_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 2666 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 6000, 6250,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ KOHR_AH_BIG_MASK_PMAP_ANIM,
+ KOHR_AH_MED_MASK_PMAP_ANIM,
+ KOHR_AH_SML_MASK_PMAP_ANIM,
+ },
+ {
+ BUZZSAW_BIG_MASK_PMAP_ANIM,
+ BUZZSAW_MED_MASK_PMAP_ANIM,
+ BUZZSAW_SML_MASK_PMAP_ANIM,
+ },
+ {
+ GAS_BIG_MASK_PMAP_ANIM,
+ GAS_MED_MASK_PMAP_ANIM,
+ GAS_SML_MASK_PMAP_ANIM,
+ },
+ {
+ KOHR_AH_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ KOHR_AH_VICTORY_SONG,
+ KOHR_AH_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ CLOSE_RANGE_WEAPON,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+spin_preprocess (ELEMENT *ElementPtr)
+{
+ ELEMENT *ShipPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ if (ShipPtr->crew_level
+ && ++StarShipPtr->RaceDescPtr->characteristics.special_wait > MAX_SAWS)
+ {
+ ElementPtr->life_span = 1;
+ ElementPtr->state_flags |= DISAPPEARING;
+ }
+ else
+ {
+ ++ElementPtr->life_span;
+ if (ElementPtr->turn_wait)
+ --ElementPtr->turn_wait;
+ else
+ {
+#define LAST_SPIN_INDEX 1
+ if (GetFrameIndex (
+ ElementPtr->current.image.frame
+ ) < LAST_SPIN_INDEX)
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ else
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame, 0);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = SAW_RATE;
+ }
+ }
+ UnlockElement (StarShipPtr->hShip);
+}
+
+static void
+buzztrack_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->thrust_wait)
+ --ElementPtr->thrust_wait;
+ else
+ {
+ COUNT facing = 0;
+
+ if (ElementPtr->hTarget == 0
+ && TrackShip (ElementPtr, &facing) < 0)
+ {
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ }
+ else
+ {
+ SIZE delta_x, delta_y;
+ ELEMENT *eptr;
+
+ LockElement (ElementPtr->hTarget, &eptr);
+ delta_x = eptr->current.location.x
+ - ElementPtr->current.location.x;
+ delta_y = eptr->current.location.y
+ - ElementPtr->current.location.y;
+ UnlockElement (ElementPtr->hTarget);
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = WRAP_DELTA_Y (delta_y);
+ facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
+ );
+
+ if (delta_x < 0)
+ delta_x = -delta_x;
+ if (delta_y < 0)
+ delta_y = -delta_y;
+ delta_x = WORLD_TO_DISPLAY (delta_x);
+ delta_y = WORLD_TO_DISPLAY (delta_y);
+ if (delta_x >= ACTIVATE_RANGE
+ || delta_y >= ACTIVATE_RANGE
+ || (DWORD)((UWORD)delta_x * delta_x)
+ + (DWORD)((UWORD)delta_y * delta_y) >=
+ (DWORD)ACTIVATE_RANGE * ACTIVATE_RANGE)
+ {
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ }
+ else
+ {
+ ElementPtr->thrust_wait = TRACK_WAIT;
+ SetVelocityVector (&ElementPtr->velocity,
+ DISPLAY_TO_WORLD (2), facing);
+ }
+ }
+ }
+
+ spin_preprocess (ElementPtr);
+}
+
+static void
+decelerate_preprocess (ELEMENT *ElementPtr)
+{
+ SIZE dx, dy;
+
+ GetCurrentVelocityComponents (&ElementPtr->velocity, &dx, &dy);
+ dx /= 2;
+ dy /= 2;
+ SetVelocityComponents (&ElementPtr->velocity, dx, dy);
+ if (dx == 0 && dy == 0)
+ {
+ ElementPtr->preprocess_func = buzztrack_preprocess;
+ }
+
+ spin_preprocess (ElementPtr);
+}
+
+static void
+splinter_preprocess (ELEMENT *ElementPtr)
+{
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+}
+
+static void
+buzzsaw_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+
+ if (ElementPtr0->state_flags & DISAPPEARING)
+ {
+ ElementPtr0->state_flags &= ~DISAPPEARING;
+ ElementPtr0->state_flags |= NONSOLID | CHANGING;
+ ElementPtr0->life_span = 5;
+ ElementPtr0->next.image.frame =
+ SetAbsFrameIndex (ElementPtr0->current.image.frame, 2);
+
+ ElementPtr0->preprocess_func = splinter_preprocess;
+ }
+}
+
+static void
+buzzsaw_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (!(StarShipPtr->cur_status_flags & WEAPON))
+ {
+ ElementPtr->life_span >>= 1;
+ ElementPtr->preprocess_func = decelerate_preprocess;
+ }
+
+ spin_preprocess (ElementPtr);
+}
+
+static void
+buzzsaw_postprocess (ELEMENT *ElementPtr)
+{
+ HELEMENT hElement;
+
+ ElementPtr->postprocess_func = 0;
+ hElement = AllocElement ();
+ if (hElement)
+ {
+ COUNT primIndex;
+ ELEMENT *ListElementPtr;
+ STARSHIP *StarShipPtr;
+
+ LockElement (hElement, &ListElementPtr);
+ primIndex = ListElementPtr->PrimIndex;
+ *ListElementPtr = *ElementPtr;
+ ListElementPtr->PrimIndex = primIndex;
+ (GLOBAL (DisplayArray))[primIndex] =
+ (GLOBAL (DisplayArray))[ElementPtr->PrimIndex];
+ ListElementPtr->current = ListElementPtr->next;
+ InitIntersectStartPoint (ListElementPtr);
+ InitIntersectEndPoint (ListElementPtr);
+ ListElementPtr->state_flags = (ListElementPtr->state_flags
+ & ~(PRE_PROCESS | CHANGING | APPEARING))
+ | POST_PROCESS;
+ UnlockElement (hElement);
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LockElement (StarShipPtr->hShip, &ListElementPtr);
+ InsertElement (hElement, GetSuccElement (ListElementPtr));
+ UnlockElement (StarShipPtr->hShip);
+
+ ElementPtr->life_span = 0;
+ }
+}
+
+static COUNT
+initialize_buzzsaw (ELEMENT *ShipPtr, HELEMENT SawArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = KOHR_AH_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = buzzsaw_preprocess;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ SawArray[0] = initialize_missile (&MissileBlock);
+
+ if (SawArray[0])
+ {
+ ELEMENT *SawPtr;
+
+ LockElement (SawArray[0], &SawPtr);
+ SawPtr->turn_wait = SAW_RATE;
+ SawPtr->thrust_wait = 0;
+ SawPtr->postprocess_func = buzzsaw_postprocess;
+ SawPtr->collision_func = buzzsaw_collision;
+ UnlockElement (SawArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+black_urquan_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (lpEvalDesc->ObjectPtr
+ && lpEvalDesc->MoveState == ENTICE
+ && (lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT)
+ && lpEvalDesc->which_turn <= 8)
+ lpEvalDesc->MoveState = PURSUE;
+
+ ship_intelligence (ShipPtr,
+ ObjectsOfConcern, ConcernCounter);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+
+ if (StarShipPtr->special_counter == 0
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >= SPECIAL_ENERGY_COST
+ && lpEvalDesc->ObjectPtr
+ && lpEvalDesc->which_turn <= 8)
+ StarShipPtr->ship_input_state |= SPECIAL;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEvalDesc->ObjectPtr)
+ {
+ HELEMENT h, hNext;
+ ELEMENT *BuzzSawPtr;
+
+ h = (StarShipPtr->old_status_flags & WEAPON) ?
+ GetSuccElement (ShipPtr) : (HELEMENT)0;
+ for (; h; h = hNext)
+ {
+ LockElement (h, &BuzzSawPtr);
+ hNext = GetSuccElement (BuzzSawPtr);
+ if (!(BuzzSawPtr->state_flags & NONSOLID)
+ && BuzzSawPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.weapon
+ && BuzzSawPtr->life_span > MISSILE_LIFE * 3 / 4
+ && elementsOfSamePlayer (BuzzSawPtr, ShipPtr))
+ {
+ {
+ //COUNT which_turn;
+
+ if (!PlotIntercept (BuzzSawPtr,
+ lpEvalDesc->ObjectPtr, BuzzSawPtr->life_span,
+ FRAGMENT_RANGE / 2))
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ else if (StarShipPtr->weapon_counter == 0)
+ StarShipPtr->ship_input_state |= WEAPON;
+
+ UnlockElement (h);
+ break;
+ }
+ hNext = 0;
+ }
+ UnlockElement (h);
+ }
+
+ if (h == 0)
+ {
+ if (StarShipPtr->old_status_flags & WEAPON)
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ else if (StarShipPtr->weapon_counter == 0
+ && ship_weapons (ShipPtr, lpEvalDesc->ObjectPtr, FRAGMENT_RANGE / 2))
+ StarShipPtr->ship_input_state |= WEAPON;
+
+ if (StarShipPtr->special_counter == 0
+ && !(StarShipPtr->ship_input_state & WEAPON)
+ && lpEvalDesc->which_turn <= 8
+ && (StarShipPtr->ship_input_state & (LEFT | RIGHT))
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >=
+ SPECIAL_ENERGY_COST)
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ }
+}
+
+static void
+gas_cloud_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = GAS_RATE;
+ }
+}
+
+static void
+gas_cloud_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (ElementPtr1->state_flags & PLAYER_SHIP)
+ ElementPtr0->mass_points = GAS_DAMAGE;
+ else
+ ElementPtr0->mass_points = GAS_ALT_DAMAGE;
+
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+}
+
+static void
+spawn_gas_cloud (ELEMENT *ElementPtr)
+{
+ SIZE dx, dy;
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ MissileBlock.cx = ElementPtr->next.location.x;
+ MissileBlock.cy = ElementPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ElementPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = 20;
+ MissileBlock.speed = GAS_SPEED;
+ MissileBlock.hit_points = GAS_HITS;
+ MissileBlock.damage = GAS_DAMAGE;
+ MissileBlock.life =
+ GetFrameCount (MissileBlock.farray[0]) * (GAS_RATE + 1) - 1;
+ MissileBlock.preprocess_func = gas_cloud_preprocess;
+ MissileBlock.blast_offs = GAS_OFFSET;
+
+ GetCurrentVelocityComponents (&ElementPtr->velocity, &dx, &dy);
+ for (MissileBlock.face = 0;
+ MissileBlock.face < ANGLE_TO_FACING (FULL_CIRCLE);
+ MissileBlock.face +=
+ (ANGLE_TO_FACING (FULL_CIRCLE) / NUM_GAS_CLOUDS))
+ {
+ HELEMENT hGasCloud;
+
+ hGasCloud = initialize_missile (&MissileBlock);
+ if (hGasCloud)
+ {
+ ELEMENT *GasCloudPtr;
+
+ LockElement (hGasCloud, &GasCloudPtr);
+ SetElementStarShip (GasCloudPtr, StarShipPtr);
+ GasCloudPtr->hTarget = 0;
+ GasCloudPtr->turn_wait = GAS_RATE - 1;
+ GasCloudPtr->collision_func = gas_cloud_collision;
+ DeltaVelocityComponents (&GasCloudPtr->velocity, dx, dy);
+ UnlockElement (hGasCloud);
+ PutElement (hGasCloud);
+ }
+ }
+}
+
+static void
+black_urquan_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ spawn_gas_cloud (ElementPtr);
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+
+ StarShipPtr->special_counter = SPECIAL_WAIT;
+ }
+}
+
+static void
+black_urquan_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ /* no spinning disks */
+ StarShipPtr->RaceDescPtr->characteristics.special_wait = 0;
+ if (StarShipPtr->weapon_counter == 0
+ && (StarShipPtr->cur_status_flags
+ & StarShipPtr->old_status_flags & WEAPON))
+ ++StarShipPtr->weapon_counter;
+}
+
+RACE_DESC*
+init_black_urquan (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ black_urquan_desc.preprocess_func = black_urquan_preprocess;
+ black_urquan_desc.postprocess_func = black_urquan_postprocess;
+ black_urquan_desc.init_weapon_func = initialize_buzzsaw;
+ black_urquan_desc.cyborg_control.intelligence_func = black_urquan_intelligence;
+
+ RaceDescPtr = &black_urquan_desc;
+
+ return (RaceDescPtr);
+}
diff --git a/src/uqm/ships/blackurq/blackurq.h b/src/uqm/ships/blackurq/blackurq.h
new file mode 100644
index 0000000..4b2b5d9
--- /dev/null
+++ b/src/uqm/ships/blackurq/blackurq.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef BLACKURQ_H
+#define BLACKURQ_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_black_urquan (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* BLACKURQ_H */
+
diff --git a/src/uqm/ships/blackurq/icode.h b/src/uqm/ships/blackurq/icode.h
new file mode 100644
index 0000000..99c0ca2
--- /dev/null
+++ b/src/uqm/ships/blackurq/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define KOHR_AH_CODE "ship.kohrah.code"
diff --git a/src/uqm/ships/blackurq/resinst.h b/src/uqm/ships/blackurq/resinst.h
new file mode 100644
index 0000000..840f519
--- /dev/null
+++ b/src/uqm/ships/blackurq/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define BUZZSAW_BIG_MASK_PMAP_ANIM "ship.kohrah.graphics.buzzsaw.large"
+#define BUZZSAW_MED_MASK_PMAP_ANIM "ship.kohrah.graphics.buzzsaw.medium"
+#define BUZZSAW_SML_MASK_PMAP_ANIM "ship.kohrah.graphics.buzzsaw.small"
+#define GAS_BIG_MASK_PMAP_ANIM "ship.kohrah.graphics.gas.large"
+#define GAS_MED_MASK_PMAP_ANIM "ship.kohrah.graphics.gas.medium"
+#define GAS_SML_MASK_PMAP_ANIM "ship.kohrah.graphics.gas.small"
+#define KOHR_AH_BIG_MASK_PMAP_ANIM "ship.kohrah.graphics.marauder.large"
+#define KOHR_AH_CAPTAIN_MASK_PMAP_ANIM "ship.kohrah.graphics.captain"
+#define KOHR_AH_ICON_MASK_PMAP_ANIM "ship.kohrah.icons"
+#define KOHR_AH_MED_MASK_PMAP_ANIM "ship.kohrah.graphics.marauder.medium"
+#define KOHR_AH_MICON_MASK_PMAP_ANIM "ship.kohrah.meleeicons"
+#define KOHR_AH_RACE_STRINGS "ship.kohrah.text"
+#define KOHR_AH_SHIP_SOUNDS "ship.kohrah.sounds"
+#define KOHR_AH_SML_MASK_PMAP_ANIM "ship.kohrah.graphics.marauder.small"
+#define KOHR_AH_VICTORY_SONG "ship.kohrah.ditty"
diff --git a/src/uqm/ships/chenjesu/Makeinfo b/src/uqm/ships/chenjesu/Makeinfo
new file mode 100644
index 0000000..3f10d6a
--- /dev/null
+++ b/src/uqm/ships/chenjesu/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="chenjesu.c"
+uqm_HFILES="chenjesu.h icode.h resinst.h"
diff --git a/src/uqm/ships/chenjesu/chenjesu.c b/src/uqm/ships/chenjesu/chenjesu.c
new file mode 100644
index 0000000..85a1014
--- /dev/null
+++ b/src/uqm/ships/chenjesu/chenjesu.c
@@ -0,0 +1,588 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "chenjesu.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 36
+#define MAX_ENERGY 30
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 4
+#define MAX_THRUST /* DISPLAY_TO_WORLD (7) */ 27
+#define THRUST_INCREMENT /* DISPLAY_TO_WORLD (2) */ 3
+#define THRUST_WAIT 4
+#define TURN_WAIT 6
+#define SHIP_MASS 10
+
+// Photon Shard
+#define WEAPON_ENERGY_COST 5
+#define WEAPON_WAIT 0
+#define CHENJESU_OFFSET 16
+#define MISSILE_OFFSET 0
+#define MISSILE_SPEED DISPLAY_TO_WORLD (16)
+#define MISSILE_LIFE 90
+ /* actually, it's as long as you hold the button down. */
+#define MISSILE_HITS 10
+#define MISSILE_DAMAGE 6
+#define NUM_SPARKLES 8
+
+// Shrapnel
+#define FRAGMENT_OFFSET 2
+#define NUM_FRAGMENTS 8
+#define FRAGMENT_LIFE 10
+#define FRAGMENT_SPEED MISSILE_SPEED
+#define FRAGMENT_RANGE (FRAGMENT_LIFE * FRAGMENT_SPEED)
+ /* This bit is for the cyborg only. */
+#define FRAGMENT_HITS 1
+#define FRAGMENT_DAMAGE 2
+
+// DOGI
+#define SPECIAL_ENERGY_COST MAX_ENERGY
+#define SPECIAL_WAIT 0
+#define DOGGY_OFFSET 18
+#define DOGGY_SPEED DISPLAY_TO_WORLD (8)
+#define ENERGY_DRAIN 10
+#define MAX_DOGGIES 4
+#define DOGGY_HITS 3
+#define DOGGY_MASS 4
+
+static RACE_DESC chenjesu_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | SEEKING_SPECIAL | SEEKING_WEAPON,
+ 28, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ CHENJESU_RACE_STRINGS,
+ CHENJESU_ICON_MASK_PMAP_ANIM,
+ CHENJESU_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 0, 0,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ CHENJESU_BIG_MASK_PMAP_ANIM,
+ CHENJESU_MED_MASK_PMAP_ANIM,
+ CHENJESU_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SPARK_BIG_MASK_PMAP_ANIM,
+ SPARK_MED_MASK_PMAP_ANIM,
+ SPARK_SML_MASK_PMAP_ANIM,
+ },
+ {
+ DOGGY_BIG_MASK_PMAP_ANIM,
+ DOGGY_MED_MASK_PMAP_ANIM,
+ DOGGY_SML_MASK_PMAP_ANIM,
+ },
+ {
+ CHENJESU_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ CHENJESU_VICTORY_SONG,
+ CHENJESU_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ LONG_RANGE_WEAPON,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+crystal_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ MissileBlock.cx = ElementPtr->next.location.x;
+ MissileBlock.cy = ElementPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.index = 1;
+ MissileBlock.sender = ElementPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = 0;
+ MissileBlock.speed = FRAGMENT_SPEED;
+ MissileBlock.hit_points = FRAGMENT_HITS;
+ MissileBlock.damage = FRAGMENT_DAMAGE;
+ MissileBlock.life = FRAGMENT_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = FRAGMENT_OFFSET;
+
+ for (MissileBlock.face = 0;
+ MissileBlock.face < ANGLE_TO_FACING (FULL_CIRCLE);
+ MissileBlock.face +=
+ (ANGLE_TO_FACING (FULL_CIRCLE) / NUM_FRAGMENTS))
+ {
+ HELEMENT hFragment;
+
+ hFragment = initialize_missile (&MissileBlock);
+ if (hFragment)
+ {
+ ELEMENT *FragPtr;
+
+ LockElement (hFragment, &FragPtr);
+ SetElementStarShip (FragPtr, StarShipPtr);
+ UnlockElement (hFragment);
+ PutElement (hFragment);
+ }
+ }
+
+ ProcessSound (SetAbsSoundIndex (
+ /* CRYSTAL_FRAGMENTS */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+}
+
+static void
+crystal_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->cur_status_flags & WEAPON)
+ ++ElementPtr->life_span; /* keep it going while key pressed */
+ else
+ {
+ ElementPtr->life_span = 1;
+
+ ElementPtr->postprocess_func = crystal_postprocess;
+ }
+}
+
+static void
+animate (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = ElementPtr->next_turn;
+ }
+}
+
+static void
+crystal_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ HELEMENT hBlastElement;
+
+ hBlastElement =
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ if (hBlastElement)
+ {
+ ELEMENT *BlastElementPtr;
+
+ LockElement (hBlastElement, &BlastElementPtr);
+ BlastElementPtr->current.location = ElementPtr1->current.location;
+
+ BlastElementPtr->life_span = NUM_SPARKLES;
+ BlastElementPtr->turn_wait = BlastElementPtr->next_turn = 0;
+ {
+ BlastElementPtr->preprocess_func = animate;
+ }
+
+ BlastElementPtr->current.image.farray = ElementPtr0->next.image.farray;
+ BlastElementPtr->current.image.frame =
+ SetAbsFrameIndex (BlastElementPtr->current.image.farray[0],
+ 2); /* skip stones */
+
+ UnlockElement (hBlastElement);
+ }
+}
+
+static void
+doggy_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ ++StarShipPtr->special_counter;
+ if (ElementPtr->thrust_wait > 0) /* could be non-zero after a collision */
+ --ElementPtr->thrust_wait;
+ else
+ {
+ COUNT facing, orig_facing;
+ SIZE delta_facing;
+
+ facing = orig_facing =
+ NORMALIZE_FACING (ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&ElementPtr->velocity)
+ ));
+ if ((delta_facing = TrackShip (ElementPtr, &facing)) < 0)
+ facing = NORMALIZE_FACING (TFB_Random ());
+ else
+ {
+ ELEMENT *ShipPtr;
+
+ LockElement (ElementPtr->hTarget, &ShipPtr);
+ facing = NORMALIZE_FACING (ANGLE_TO_FACING (
+ ARCTAN (ShipPtr->current.location.x -
+ ElementPtr->current.location.x,
+ ShipPtr->current.location.y -
+ ElementPtr->current.location.y)
+ ));
+ delta_facing = NORMALIZE_FACING (facing -
+ GetFrameIndex (ShipPtr->current.image.frame));
+ UnlockElement (ElementPtr->hTarget);
+
+ if (delta_facing > ANGLE_TO_FACING (HALF_CIRCLE - OCTANT) &&
+ delta_facing < ANGLE_TO_FACING (HALF_CIRCLE + OCTANT))
+ {
+ if (delta_facing >= ANGLE_TO_FACING (HALF_CIRCLE))
+ facing -= ANGLE_TO_FACING (QUADRANT);
+ else
+ facing += ANGLE_TO_FACING (QUADRANT);
+ }
+
+ facing = NORMALIZE_FACING (facing);
+ }
+
+ if (facing != orig_facing)
+ SetVelocityVector (&ElementPtr->velocity,
+ DOGGY_SPEED, facing);
+ }
+}
+
+static void
+doggy_death (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ ProcessSound (SetAbsSoundIndex (
+ /* DOGGY_DIES */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 3), ElementPtr);
+
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->state_flags |= NONSOLID | FINITE_LIFE;
+ ElementPtr->life_span = 6;
+ {
+ ElementPtr->preprocess_func = animate;
+ }
+ ElementPtr->death_func = NULL;
+ ElementPtr->collision_func = NULL;
+ ZeroVelocityComponents (&ElementPtr->velocity);
+
+ ElementPtr->turn_wait = ElementPtr->next_turn = 0;
+}
+
+static void
+doggy_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ if ((ElementPtr1->state_flags & PLAYER_SHIP)
+ && !elementsOfSamePlayer (ElementPtr0, ElementPtr1))
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ ProcessSound (SetAbsSoundIndex (
+ /* DOGGY_STEALS_ENERGY */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2), ElementPtr0);
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level < ENERGY_DRAIN)
+ DeltaEnergy (ElementPtr1, -StarShipPtr->RaceDescPtr->ship_info.energy_level);
+ else
+ DeltaEnergy (ElementPtr1, -ENERGY_DRAIN);
+ }
+ if (ElementPtr0->thrust_wait <= COLLISION_THRUST_WAIT)
+ ElementPtr0->thrust_wait += COLLISION_THRUST_WAIT << 1;
+}
+
+static void
+spawn_doggy (ELEMENT *ElementPtr)
+{
+ HELEMENT hDoggyElement;
+
+ if ((hDoggyElement = AllocElement ()) != 0)
+ {
+ COUNT angle;
+ ELEMENT *DoggyElementPtr;
+ STARSHIP *StarShipPtr;
+
+ ElementPtr->state_flags |= DEFY_PHYSICS;
+
+ PutElement (hDoggyElement);
+ LockElement (hDoggyElement, &DoggyElementPtr);
+ DoggyElementPtr->hit_points = DOGGY_HITS;
+ DoggyElementPtr->mass_points = DOGGY_MASS;
+ DoggyElementPtr->thrust_wait = 0;
+ DoggyElementPtr->playerNr = ElementPtr->playerNr;
+ DoggyElementPtr->state_flags = APPEARING;
+ DoggyElementPtr->life_span = NORMAL_LIFE;
+ SetPrimType (&(GLOBAL (DisplayArray))[DoggyElementPtr->PrimIndex],
+ STAMP_PRIM);
+ {
+ DoggyElementPtr->preprocess_func = doggy_preprocess;
+ DoggyElementPtr->postprocess_func = NULL;
+ DoggyElementPtr->collision_func = doggy_collision;
+ DoggyElementPtr->death_func = doggy_death;
+ }
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing) + HALF_CIRCLE;
+ DoggyElementPtr->current.location.x = ElementPtr->next.location.x
+ + COSINE (angle, DISPLAY_TO_WORLD (CHENJESU_OFFSET + DOGGY_OFFSET));
+ DoggyElementPtr->current.location.y = ElementPtr->next.location.y
+ + SINE (angle, DISPLAY_TO_WORLD (CHENJESU_OFFSET + DOGGY_OFFSET));
+ DoggyElementPtr->current.image.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ DoggyElementPtr->current.image.frame = StarShipPtr->RaceDescPtr->ship_data.special[0];
+
+ SetVelocityVector (&DoggyElementPtr->velocity,
+ DOGGY_SPEED, NORMALIZE_FACING (ANGLE_TO_FACING (angle)));
+
+ SetElementStarShip (DoggyElementPtr, StarShipPtr);
+
+ ProcessSound (SetAbsSoundIndex (
+ /* RELEASE_DOGGY */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 4), DoggyElementPtr);
+
+ UnlockElement (hDoggyElement);
+ }
+}
+
+static void
+chenjesu_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEvalDesc->ObjectPtr)
+ {
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if ((lpEvalDesc->which_turn <= 16
+ && MANEUVERABILITY (
+ &EnemyStarShipPtr->RaceDescPtr->cyborg_control
+ ) >= MEDIUM_SHIP)
+ || (MANEUVERABILITY (
+ &EnemyStarShipPtr->RaceDescPtr->cyborg_control
+ ) <= SLOW_SHIP
+ && WEAPON_RANGE (
+ &EnemyStarShipPtr->RaceDescPtr->cyborg_control
+ ) >= LONG_RANGE_WEAPON * 3 / 4
+ && (EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags & SEEKING_WEAPON)))
+ lpEvalDesc->MoveState = PURSUE;
+ }
+
+ if (StarShipPtr->special_counter == 1
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].MoveState == ENTICE
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn <= 8)
+ {
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ }
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ if (lpEvalDesc->ObjectPtr)
+ {
+ HELEMENT h, hNext;
+ ELEMENT *CrystalPtr;
+
+ h = (StarShipPtr->old_status_flags & WEAPON) ?
+ GetTailElement () : (HELEMENT)0;
+ for (; h; h = hNext)
+ {
+ LockElement (h, &CrystalPtr);
+ hNext = GetPredElement (CrystalPtr);
+ if (!(CrystalPtr->state_flags & NONSOLID)
+ && CrystalPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.weapon
+ && CrystalPtr->preprocess_func
+ && CrystalPtr->life_span > 0
+ && elementsOfSamePlayer (CrystalPtr, ShipPtr))
+ {
+ if (ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr)
+ {
+ COUNT which_turn;
+
+ if ((which_turn = PlotIntercept (CrystalPtr,
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr,
+ CrystalPtr->life_span,
+ FRAGMENT_RANGE / 2)) == 0
+ || (which_turn == 1
+ && PlotIntercept (CrystalPtr,
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr,
+ CrystalPtr->life_span, 0) == 0))
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ else if (StarShipPtr->weapon_counter == 0)
+ {
+ StarShipPtr->ship_input_state |= WEAPON;
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ }
+
+ UnlockElement (h);
+ break;
+ }
+ hNext = 0;
+ }
+ UnlockElement (h);
+ }
+
+ if (h == 0)
+ {
+ if (StarShipPtr->old_status_flags & WEAPON)
+ {
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ if (lpEvalDesc == &ObjectsOfConcern[ENEMY_WEAPON_INDEX])
+ StarShipPtr->weapon_counter = 3;
+ }
+ else if (StarShipPtr->weapon_counter == 0
+ && ship_weapons (ShipPtr, lpEvalDesc->ObjectPtr, FRAGMENT_RANGE / 2))
+ StarShipPtr->ship_input_state |= WEAPON;
+ }
+ }
+
+ if (StarShipPtr->special_counter < MAX_DOGGIES)
+ {
+ if (lpEvalDesc->ObjectPtr
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level <= SPECIAL_ENERGY_COST
+ && !(StarShipPtr->ship_input_state & WEAPON))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+}
+
+static COUNT
+initialize_crystal (ELEMENT *ShipPtr, HELEMENT CrystalArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = CHENJESU_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = crystal_preprocess;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ CrystalArray[0] = initialize_missile (&MissileBlock);
+
+ if (CrystalArray[0])
+ {
+ ELEMENT *CrystalPtr;
+
+ LockElement (CrystalArray[0], &CrystalPtr);
+ CrystalPtr->collision_func = crystal_collision;
+ UnlockElement (CrystalArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+chenjesu_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter < MAX_DOGGIES
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ spawn_doggy (ElementPtr);
+ }
+
+ StarShipPtr->special_counter = 1;
+ /* say there is one doggy, because ship_postprocess will
+ * decrement special_counter */
+}
+
+static void
+chenjesu_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->special_counter > 1) /* only when STANDARD
+ * computer opponent
+ */
+ StarShipPtr->special_counter += MAX_DOGGIES;
+ if (StarShipPtr->cur_status_flags
+ & StarShipPtr->old_status_flags
+ & WEAPON)
+ ++StarShipPtr->weapon_counter;
+}
+
+RACE_DESC*
+init_chenjesu (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ chenjesu_desc.preprocess_func = chenjesu_preprocess;
+ chenjesu_desc.postprocess_func = chenjesu_postprocess;
+ chenjesu_desc.init_weapon_func = initialize_crystal;
+ chenjesu_desc.cyborg_control.intelligence_func = chenjesu_intelligence;
+
+ RaceDescPtr = &chenjesu_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/chenjesu/chenjesu.h b/src/uqm/ships/chenjesu/chenjesu.h
new file mode 100644
index 0000000..37ac9d6
--- /dev/null
+++ b/src/uqm/ships/chenjesu/chenjesu.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CHENJESU_H
+#define CHENJESU_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_chenjesu (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* CHENJESU_H */
+
diff --git a/src/uqm/ships/chenjesu/icode.h b/src/uqm/ships/chenjesu/icode.h
new file mode 100644
index 0000000..1f4b693
--- /dev/null
+++ b/src/uqm/ships/chenjesu/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define CHENJESU_CODE "ship.chenjesu.code"
diff --git a/src/uqm/ships/chenjesu/resinst.h b/src/uqm/ships/chenjesu/resinst.h
new file mode 100644
index 0000000..966029a
--- /dev/null
+++ b/src/uqm/ships/chenjesu/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define CHENJESU_BIG_MASK_PMAP_ANIM "ship.chenjesu.graphics.broodhome.large"
+#define CHENJESU_CAPTAIN_MASK_PMAP_ANIM "ship.chenjesu.graphics.captain"
+#define CHENJESU_ICON_MASK_PMAP_ANIM "ship.chenjesu.icons"
+#define CHENJESU_MED_MASK_PMAP_ANIM "ship.chenjesu.graphics.broodhome.medium"
+#define CHENJESU_MICON_MASK_PMAP_ANIM "ship.chenjesu.meleeicons"
+#define CHENJESU_RACE_STRINGS "ship.chenjesu.text"
+#define CHENJESU_SHIP_SOUNDS "ship.chenjesu.sounds"
+#define CHENJESU_SML_MASK_PMAP_ANIM "ship.chenjesu.graphics.broodhome.small"
+#define CHENJESU_VICTORY_SONG "ship.chenjesu.ditty"
+#define DOGGY_BIG_MASK_PMAP_ANIM "ship.chenjesu.graphics.doggy.large"
+#define DOGGY_MED_MASK_PMAP_ANIM "ship.chenjesu.graphics.doggy.medium"
+#define DOGGY_SML_MASK_PMAP_ANIM "ship.chenjesu.graphics.doggy.small"
+#define SPARK_BIG_MASK_PMAP_ANIM "ship.chenjesu.graphics.spark.large"
+#define SPARK_MED_MASK_PMAP_ANIM "ship.chenjesu.graphics.spark.medium"
+#define SPARK_SML_MASK_PMAP_ANIM "ship.chenjesu.graphics.spark.small"
diff --git a/src/uqm/ships/chmmr/Makeinfo b/src/uqm/ships/chmmr/Makeinfo
new file mode 100644
index 0000000..70b8bc6
--- /dev/null
+++ b/src/uqm/ships/chmmr/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="chmmr.c"
+uqm_HFILES="chmmr.h icode.h resinst.h"
diff --git a/src/uqm/ships/chmmr/chmmr.c b/src/uqm/ships/chmmr/chmmr.c
new file mode 100644
index 0000000..8f6abf0
--- /dev/null
+++ b/src/uqm/ships/chmmr/chmmr.c
@@ -0,0 +1,790 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "chmmr.h"
+#include "resinst.h"
+
+#include "uqm/colors.h"
+#include "uqm/globdata.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW MAX_CREW_SIZE
+#define MAX_ENERGY MAX_ENERGY_SIZE
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 1
+#define MAX_THRUST 35
+#define THRUST_INCREMENT 7
+#define THRUST_WAIT 5
+#define TURN_WAIT 3
+#define SHIP_MASS 10
+
+// Laser
+#define WEAPON_ENERGY_COST 2
+#define WEAPON_WAIT 0
+#define CHMMR_OFFSET 18
+#define LASER_RANGE DISPLAY_TO_WORLD (150)
+#define NUM_CYCLES 4
+
+// Tractor Beam
+#define SPECIAL_ENERGY_COST 1
+#define SPECIAL_WAIT 0
+#define NUM_SHADOWS 5
+
+// Satellites
+#define NUM_SATELLITES 3
+#define SATELLITE_OFFSET DISPLAY_TO_WORLD (64)
+#define SATELLITE_HITPOINTS 10
+#define SATELLITE_MASS 10
+#define DEFENSE_RANGE (UWORD)64
+#define DEFENSE_WAIT 2
+
+static RACE_DESC chmmr_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | IMMEDIATE_WEAPON | SEEKING_SPECIAL | POINT_DEFENSE,
+ 30, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ CHMMR_RACE_STRINGS,
+ CHMMR_ICON_MASK_PMAP_ANIM,
+ CHMMR_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 0, 0,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ CHMMR_BIG_MASK_PMAP_ANIM,
+ CHMMR_MED_MASK_PMAP_ANIM,
+ CHMMR_SML_MASK_PMAP_ANIM,
+ },
+ {
+ MUZZLE_BIG_MASK_PMAP_ANIM,
+ MUZZLE_MED_MASK_PMAP_ANIM,
+ MUZZLE_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SATELLITE_BIG_MASK_PMAP_ANIM,
+ SATELLITE_MED_MASK_PMAP_ANIM,
+ SATELLITE_SML_MASK_PMAP_ANIM,
+ },
+ {
+ CHMMR_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ CHMMR_VICTORY_SONG,
+ CHMMR_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ CLOSE_RANGE_WEAPON,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+animate (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = ElementPtr->next_turn;
+ }
+}
+
+static void
+laser_death (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ StarShipPtr->special_counter = ElementPtr->turn_wait;
+
+ if (StarShipPtr->hShip)
+ {
+ SIZE dx, dy;
+ long dist;
+ HELEMENT hIonSpots;
+ ELEMENT *ShipPtr;
+
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+
+ dx = ElementPtr->current.location.x
+ - ShipPtr->current.location.x;
+ dy = ElementPtr->current.location.y
+ - ShipPtr->current.location.y;
+ if (((BYTE)TFB_Random () & 0x07)
+ && (dist = (long)dx * dx + (long)dy * dy) >=
+ (long)DISPLAY_TO_WORLD (CHMMR_OFFSET + 10)
+ * DISPLAY_TO_WORLD (CHMMR_OFFSET + 10)
+ && (hIonSpots = AllocElement ()))
+ {
+ COUNT angle, magnitude;
+ ELEMENT *IonSpotsPtr;
+
+ LockElement (hIonSpots, &IonSpotsPtr);
+ IonSpotsPtr->playerNr = ElementPtr->playerNr;
+ IonSpotsPtr->state_flags = FINITE_LIFE | NONSOLID
+ | IGNORE_SIMILAR | APPEARING;
+ IonSpotsPtr->turn_wait = IonSpotsPtr->next_turn = 0;
+ IonSpotsPtr->life_span = 9;
+
+ angle = ARCTAN (dx, dy);
+ magnitude = ((COUNT)TFB_Random ()
+ % ((square_root (dist) + 1)
+ - DISPLAY_TO_WORLD (CHMMR_OFFSET + 10)))
+ + DISPLAY_TO_WORLD (CHMMR_OFFSET + 10);
+ IonSpotsPtr->current.location.x =
+ ShipPtr->current.location.x
+ + COSINE (angle, magnitude);
+ IonSpotsPtr->current.location.y =
+ ShipPtr->current.location.y
+ + SINE (angle, magnitude);
+ IonSpotsPtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.weapon;
+ IonSpotsPtr->current.image.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.weapon[0],
+ ANGLE_TO_FACING (FULL_CIRCLE) << 1
+ );
+
+ IonSpotsPtr->preprocess_func = animate;
+
+ SetElementStarShip (IonSpotsPtr, StarShipPtr);
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ IonSpotsPtr->PrimIndex
+ ], STAMP_PRIM);
+
+ UnlockElement (hIonSpots);
+ PutElement (hIonSpots);
+ }
+
+ UnlockElement (StarShipPtr->hShip);
+ }
+}
+
+static COUNT
+initialize_megawatt_laser (ELEMENT *ShipPtr, HELEMENT LaserArray[])
+{
+ RECT r;
+ STARSHIP *StarShipPtr;
+ LASER_BLOCK LaserBlock;
+ static const Color cycle_array[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7F),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7F),
+ };
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ LaserBlock.face = StarShipPtr->ShipFacing;
+ GetFrameRect (SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.weapon[0], LaserBlock.face
+ ), &r);
+ LaserBlock.cx = DISPLAY_ALIGN (ShipPtr->next.location.x)
+ + DISPLAY_TO_WORLD (r.corner.x);
+ LaserBlock.cy = DISPLAY_ALIGN (ShipPtr->next.location.y)
+ + DISPLAY_TO_WORLD (r.corner.y);
+ LaserBlock.ex = COSINE (FACING_TO_ANGLE (LaserBlock.face), LASER_RANGE);
+ LaserBlock.ey = SINE (FACING_TO_ANGLE (LaserBlock.face), LASER_RANGE);
+ LaserBlock.sender = ShipPtr->playerNr;
+ LaserBlock.flags = IGNORE_SIMILAR;
+ LaserBlock.pixoffs = 0;
+ LaserBlock.color = cycle_array[StarShipPtr->special_counter];
+ LaserArray[0] = initialize_laser (&LaserBlock);
+
+ if (LaserArray[0])
+ {
+ ELEMENT *LaserPtr;
+
+ LockElement (LaserArray[0], &LaserPtr);
+
+ LaserPtr->mass_points = 2;
+ LaserPtr->death_func = laser_death;
+ LaserPtr->turn_wait = (BYTE)((StarShipPtr->special_counter + 1)
+ % NUM_CYCLES);
+
+ UnlockElement (LaserArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+chmmr_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (StarShipPtr->special_counter == 0
+ && lpEvalDesc->ObjectPtr
+ && !(StarShipPtr->ship_input_state & WEAPON)
+ && lpEvalDesc->which_turn > 12
+ && NORMALIZE_ANGLE (
+ GetVelocityTravelAngle (&ShipPtr->velocity)
+ - (GetVelocityTravelAngle (&lpEvalDesc->ObjectPtr->velocity)
+ + HALF_CIRCLE) + QUADRANT
+ ) > HALF_CIRCLE)
+ StarShipPtr->ship_input_state |= SPECIAL;
+}
+
+static void
+chmmr_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ if (StarShipPtr->cur_status_flags & WEAPON)
+ {
+ HELEMENT hMuzzleFlash;
+ ELEMENT *MuzzleFlashPtr;
+
+ LockElement (GetTailElement (), &MuzzleFlashPtr);
+ if (MuzzleFlashPtr != ElementPtr
+ && elementsOfSamePlayer (MuzzleFlashPtr, ElementPtr)
+ && (MuzzleFlashPtr->state_flags & APPEARING)
+ && GetPrimType (&(GLOBAL (DisplayArray))[
+ MuzzleFlashPtr->PrimIndex
+ ]) == LINE_PRIM
+ && !(StarShipPtr->special_counter & 1)
+ && (hMuzzleFlash = AllocElement ()))
+ {
+ LockElement (hMuzzleFlash, &MuzzleFlashPtr);
+ MuzzleFlashPtr->playerNr = ElementPtr->playerNr;
+ MuzzleFlashPtr->state_flags = FINITE_LIFE | NONSOLID
+ | IGNORE_SIMILAR | APPEARING;
+ MuzzleFlashPtr->life_span = 1;
+
+ MuzzleFlashPtr->current = ElementPtr->next;
+ MuzzleFlashPtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MuzzleFlashPtr->current.image.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.weapon[0],
+ StarShipPtr->ShipFacing + ANGLE_TO_FACING (FULL_CIRCLE)
+ );
+
+ SetElementStarShip (MuzzleFlashPtr, StarShipPtr);
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ MuzzleFlashPtr->PrimIndex
+ ], STAMP_PRIM);
+
+ UnlockElement (hMuzzleFlash);
+ PutElement (hMuzzleFlash);
+ }
+ UnlockElement (GetTailElement ());
+ }
+
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ COUNT facing;
+ ELEMENT *ShipElementPtr;
+
+ LockElement (ElementPtr->hTarget, &ShipElementPtr);
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1),
+ ShipElementPtr);
+
+ UnlockElement (ElementPtr->hTarget);
+
+ facing = 0;
+ if (TrackShip (ElementPtr, &facing) >= 0)
+ {
+ ELEMENT *ShipElementPtr;
+
+ LockElement (ElementPtr->hTarget, &ShipElementPtr);
+ if (!GRAVITY_MASS (ShipElementPtr->mass_points + 1))
+ {
+ SIZE i, dx, dy;
+ COUNT angle, magnitude;
+ STARSHIP *EnemyStarShipPtr;
+ static const SIZE shadow_offs[] =
+ {
+ DISPLAY_TO_WORLD (8),
+ DISPLAY_TO_WORLD (8 + 9),
+ DISPLAY_TO_WORLD (8 + 9 + 11),
+ DISPLAY_TO_WORLD (8 + 9 + 11 + 14),
+ DISPLAY_TO_WORLD (8 + 9 + 11 + 14 + 18),
+ };
+ static const Color color_tab[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x10), 0x53),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x0E), 0x54),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x0C), 0x55),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x09), 0x56),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x00, 0x07), 0x57),
+ };
+ DWORD current_speed, max_speed;
+
+ // calculate tractor beam effect
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+ dx = (ElementPtr->next.location.x
+ + COSINE (angle, (LASER_RANGE / 3)
+ + DISPLAY_TO_WORLD (CHMMR_OFFSET)))
+ - ShipElementPtr->next.location.x;
+ dy = (ElementPtr->next.location.y
+ + SINE (angle, (LASER_RANGE / 3)
+ + DISPLAY_TO_WORLD (CHMMR_OFFSET)))
+ - ShipElementPtr->next.location.y;
+ angle = ARCTAN (dx, dy);
+ magnitude = WORLD_TO_VELOCITY (12) /
+ ShipElementPtr->mass_points;
+ DeltaVelocityComponents (&ShipElementPtr->velocity,
+ COSINE (angle, magnitude), SINE (angle, magnitude));
+
+ GetCurrentVelocityComponents (&ShipElementPtr->velocity,
+ &dx, &dy);
+ GetElementStarShip (ShipElementPtr, &EnemyStarShipPtr);
+
+ // set the effected ship's speed flags
+ current_speed = VelocitySquared (dx, dy);
+ max_speed = VelocitySquared (WORLD_TO_VELOCITY (
+ EnemyStarShipPtr->RaceDescPtr->characteristics.max_thrust),
+ 0);
+ EnemyStarShipPtr->cur_status_flags &= ~(SHIP_AT_MAX_SPEED
+ | SHIP_BEYOND_MAX_SPEED);
+ if (current_speed > max_speed)
+ EnemyStarShipPtr->cur_status_flags |= (SHIP_AT_MAX_SPEED
+ | SHIP_BEYOND_MAX_SPEED);
+ else if (current_speed == max_speed)
+ EnemyStarShipPtr->cur_status_flags |= SHIP_AT_MAX_SPEED;
+
+ // add tractor beam graphical effects
+ for (i = 0; i < NUM_SHADOWS; ++i)
+ {
+ HELEMENT hShadow;
+
+ hShadow = AllocElement ();
+ if (hShadow)
+ {
+ ELEMENT *ShadowElementPtr;
+
+ LockElement (hShadow, &ShadowElementPtr);
+ ShadowElementPtr->playerNr = ShipElementPtr->playerNr;
+ ShadowElementPtr->state_flags = FINITE_LIFE | NONSOLID
+ | IGNORE_SIMILAR | POST_PROCESS;
+ ShadowElementPtr->life_span = 1;
+
+ ShadowElementPtr->current = ShipElementPtr->next;
+ ShadowElementPtr->current.location.x +=
+ COSINE (angle, shadow_offs[i]);
+ ShadowElementPtr->current.location.y +=
+ SINE (angle, shadow_offs[i]);
+ ShadowElementPtr->next = ShadowElementPtr->current;
+
+ SetElementStarShip (ShadowElementPtr, EnemyStarShipPtr);
+ SetVelocityComponents (&ShadowElementPtr->velocity,
+ dx, dy);
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ ShadowElementPtr->PrimIndex
+ ], STAMPFILL_PRIM);
+ SetPrimColor (&(GLOBAL (DisplayArray))[
+ ShadowElementPtr->PrimIndex
+ ], color_tab[i]);
+
+ UnlockElement (hShadow);
+ InsertElement (hShadow, GetHeadElement ());
+ }
+ }
+ }
+ UnlockElement (ElementPtr->hTarget);
+ }
+ }
+
+ StarShipPtr->special_counter = 0;
+}
+
+static void
+satellite_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ ++ElementPtr->life_span;
+
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame,
+ (GetFrameIndex (ElementPtr->current.image.frame) + 1) & 7);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = (BYTE)NORMALIZE_ANGLE (
+ ElementPtr->turn_wait + 1
+ );
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->hShip)
+ {
+ SIZE dx, dy;
+ ELEMENT *ShipPtr;
+
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags |= POINT_DEFENSE;
+
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+
+ dx = (ShipPtr->next.location.x
+ + COSINE (ElementPtr->turn_wait, SATELLITE_OFFSET))
+ - ElementPtr->current.location.x;
+ dy = (ShipPtr->next.location.y
+ + SINE (ElementPtr->turn_wait, SATELLITE_OFFSET))
+ - ElementPtr->current.location.y;
+ dx = WRAP_DELTA_X (dx);
+ dy = WRAP_DELTA_Y (dy);
+ if ((long)dx * dx + (long)dy * dy
+ <= DISPLAY_TO_WORLD (20L) * DISPLAY_TO_WORLD (20L))
+ SetVelocityComponents (&ElementPtr->velocity,
+ WORLD_TO_VELOCITY (dx),
+ WORLD_TO_VELOCITY (dy));
+ else
+ {
+ COUNT angle;
+
+ angle = ARCTAN (dx, dy);
+ SetVelocityComponents (&ElementPtr->velocity,
+ COSINE (angle, WORLD_TO_VELOCITY (DISPLAY_TO_WORLD (20))),
+ SINE (angle, WORLD_TO_VELOCITY (DISPLAY_TO_WORLD (20))));
+ }
+
+ UnlockElement (StarShipPtr->hShip);
+ }
+}
+
+static void
+spawn_point_defense (ELEMENT *ElementPtr)
+{
+ BYTE weakest;
+ UWORD best_dist;
+ STARSHIP *StarShipPtr;
+ HELEMENT hObject, hNextObject, hBestObject;
+ ELEMENT *ShipPtr;
+ ELEMENT *SattPtr;
+ ELEMENT *ObjectPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ hBestObject = 0;
+ best_dist = DEFENSE_RANGE + 1;
+ weakest = 255;
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ LockElement (ElementPtr->hTarget, &SattPtr);
+ for (hObject = GetPredElement (ElementPtr);
+ hObject; hObject = hNextObject)
+ {
+ LockElement (hObject, &ObjectPtr);
+ hNextObject = GetPredElement (ObjectPtr);
+ if (!elementsOfSamePlayer (ObjectPtr, ShipPtr)
+ && ObjectPtr->playerNr != NEUTRAL_PLAYER_NUM
+ && CollisionPossible (ObjectPtr, ShipPtr)
+ && !OBJECT_CLOAKED (ObjectPtr))
+ {
+ SIZE delta_x, delta_y;
+ UWORD dist;
+
+ delta_x = ObjectPtr->next.location.x
+ - SattPtr->next.location.x;
+ delta_y = ObjectPtr->next.location.y
+ - SattPtr->next.location.y;
+ if (delta_x < 0)
+ delta_x = -delta_x;
+ if (delta_y < 0)
+ delta_y = -delta_y;
+ delta_x = WORLD_TO_DISPLAY (delta_x);
+ delta_y = WORLD_TO_DISPLAY (delta_y);
+ if ((UWORD)delta_x <= DEFENSE_RANGE &&
+ (UWORD)delta_y <= DEFENSE_RANGE &&
+ (dist =
+ (UWORD)delta_x * (UWORD)delta_x
+ + (UWORD)delta_y * (UWORD)delta_y) <=
+ DEFENSE_RANGE * DEFENSE_RANGE
+ && (ObjectPtr->hit_points < weakest
+ || (ObjectPtr->hit_points == weakest
+ && dist < best_dist)))
+ {
+ hBestObject = hObject;
+ best_dist = dist;
+ weakest = ObjectPtr->hit_points;
+ }
+ }
+ UnlockElement (hObject);
+ }
+
+ if (hBestObject)
+ {
+ LASER_BLOCK LaserBlock;
+ HELEMENT hPointDefense;
+
+ LockElement (hBestObject, &ObjectPtr);
+
+ LaserBlock.cx = SattPtr->next.location.x;
+ LaserBlock.cy = SattPtr->next.location.y;
+ LaserBlock.face = 0;
+ LaserBlock.ex = ObjectPtr->next.location.x
+ - SattPtr->next.location.x;
+ LaserBlock.ey = ObjectPtr->next.location.y
+ - SattPtr->next.location.y;
+ LaserBlock.sender = SattPtr->playerNr;
+ LaserBlock.flags = IGNORE_SIMILAR;
+ LaserBlock.pixoffs = 0;
+ LaserBlock.color = BUILD_COLOR (MAKE_RGB15 (0x00, 0x01, 0x1F), 0x4D);
+ hPointDefense = initialize_laser (&LaserBlock);
+ if (hPointDefense)
+ {
+ ELEMENT *PDPtr;
+
+ LockElement (hPointDefense, &PDPtr);
+ SetElementStarShip (PDPtr, StarShipPtr);
+ PDPtr->hTarget = 0;
+ UnlockElement (hPointDefense);
+
+ PutElement (hPointDefense);
+
+ SattPtr->thrust_wait = DEFENSE_WAIT;
+ }
+
+ UnlockElement (hBestObject);
+ }
+
+ UnlockElement (ElementPtr->hTarget);
+ UnlockElement (StarShipPtr->hShip);
+}
+
+static void
+satellite_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ if (ElementPtr->thrust_wait || ElementPtr->life_span == 0)
+ --ElementPtr->thrust_wait;
+ else
+ {
+ HELEMENT hDefense;
+
+ hDefense = AllocElement ();
+ if (hDefense)
+ {
+ ELEMENT *DefensePtr;
+
+ PutElement (hDefense);
+
+ LockElement (hDefense, &DefensePtr);
+ DefensePtr->playerNr = ElementPtr->playerNr;
+ DefensePtr->state_flags = APPEARING | NONSOLID | FINITE_LIFE;
+
+ {
+ ELEMENT *SuccPtr;
+
+ LockElement (GetSuccElement (ElementPtr), &SuccPtr);
+ DefensePtr->hTarget = GetPredElement (SuccPtr);
+ UnlockElement (GetSuccElement (ElementPtr));
+
+ DefensePtr->death_func = spawn_point_defense;
+ }
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ SetElementStarShip (DefensePtr, StarShipPtr);
+
+ UnlockElement (hDefense);
+ }
+ }
+}
+
+static void
+satellite_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ (void) ElementPtr0; /* Satisfying compiler (unused parameter) */
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) ElementPtr1; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static void
+satellite_death (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags &= ~POINT_DEFENSE;
+
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->state_flags |= NONSOLID | FINITE_LIFE | CHANGING;
+ ElementPtr->life_span = 4;
+ ElementPtr->turn_wait = 1;
+ ElementPtr->next_turn = 0;
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame, 8);
+
+ ElementPtr->preprocess_func = animate;
+ ElementPtr->death_func = NULL;
+ ElementPtr->postprocess_func = NULL;
+ ElementPtr->collision_func = NULL;
+}
+
+static void
+spawn_satellites (ELEMENT *ElementPtr)
+{
+ COUNT i;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->hShip)
+ {
+ LockElement (StarShipPtr->hShip, &ElementPtr);
+ for (i = 0; i < NUM_SATELLITES; ++i)
+ {
+ HELEMENT hSatellite;
+
+ hSatellite = AllocElement ();
+ if (hSatellite)
+ {
+ COUNT angle;
+ ELEMENT *SattPtr;
+
+ LockElement (hSatellite, &SattPtr);
+ SattPtr->playerNr = ElementPtr->playerNr;
+ SattPtr->state_flags = IGNORE_SIMILAR | APPEARING
+ | FINITE_LIFE;
+ SattPtr->life_span = NORMAL_LIFE + 1;
+ SattPtr->hit_points = SATELLITE_HITPOINTS;
+ SattPtr->mass_points = SATELLITE_MASS;
+
+ angle = (i * FULL_CIRCLE + (NUM_SATELLITES >> 1))
+ / NUM_SATELLITES;
+ SattPtr->turn_wait = (BYTE)angle;
+ SattPtr->current.location.x = ElementPtr->next.location.x
+ + COSINE (angle, SATELLITE_OFFSET);
+ SattPtr->current.location.y = ElementPtr->next.location.y
+ + SINE (angle, SATELLITE_OFFSET);
+ SattPtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ SattPtr->current.image.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.special[0],
+ (COUNT)TFB_Random () & 0x07
+ );
+
+ SattPtr->preprocess_func = satellite_preprocess;
+ SattPtr->postprocess_func = satellite_postprocess;
+ SattPtr->death_func = satellite_death;
+ SattPtr->collision_func = satellite_collision;
+
+ SetElementStarShip (SattPtr, StarShipPtr);
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ SattPtr->PrimIndex
+ ], STAMP_PRIM);
+
+ UnlockElement (hSatellite);
+ PutElement (hSatellite);
+ }
+ }
+ UnlockElement (StarShipPtr->hShip);
+ }
+}
+
+static void
+chmmr_preprocess (ELEMENT *ElementPtr)
+{
+ HELEMENT hSatellite;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ hSatellite = AllocElement ();
+ if (hSatellite)
+ {
+ ELEMENT *SattPtr;
+ STARSHIP *StarShipPtr;
+
+ LockElement (hSatellite, &SattPtr);
+ SattPtr->playerNr = ElementPtr->playerNr;
+ SattPtr->state_flags = FINITE_LIFE | NONSOLID | IGNORE_SIMILAR
+ | APPEARING;
+ SattPtr->life_span = HYPERJUMP_LIFE + 1;
+
+ SattPtr->death_func = spawn_satellites;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ SetElementStarShip (SattPtr, StarShipPtr);
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ SattPtr->PrimIndex
+ ], NO_PRIM);
+
+ UnlockElement (hSatellite);
+ PutElement (hSatellite);
+ }
+
+ StarShipPtr->RaceDescPtr->preprocess_func = 0;
+}
+
+RACE_DESC*
+init_chmmr (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ chmmr_desc.preprocess_func = chmmr_preprocess;
+ chmmr_desc.postprocess_func = chmmr_postprocess;
+ chmmr_desc.init_weapon_func = initialize_megawatt_laser;
+ chmmr_desc.cyborg_control.intelligence_func = chmmr_intelligence;
+
+ RaceDescPtr = &chmmr_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/chmmr/chmmr.h b/src/uqm/ships/chmmr/chmmr.h
new file mode 100644
index 0000000..88dd9b8
--- /dev/null
+++ b/src/uqm/ships/chmmr/chmmr.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CHMMR_H
+#define CHMMR_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_chmmr (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* CHMMR_H */
+
diff --git a/src/uqm/ships/chmmr/icode.h b/src/uqm/ships/chmmr/icode.h
new file mode 100644
index 0000000..0873a15
--- /dev/null
+++ b/src/uqm/ships/chmmr/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define CHMMR_CODE "ship.chmmr.code"
diff --git a/src/uqm/ships/chmmr/resinst.h b/src/uqm/ships/chmmr/resinst.h
new file mode 100644
index 0000000..a830273
--- /dev/null
+++ b/src/uqm/ships/chmmr/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define CHMMR_BIG_MASK_PMAP_ANIM "ship.chmmr.graphics.avatar.large"
+#define CHMMR_CAPTAIN_MASK_PMAP_ANIM "ship.chmmr.graphics.captain"
+#define CHMMR_ICON_MASK_PMAP_ANIM "ship.chmmr.icons"
+#define CHMMR_MED_MASK_PMAP_ANIM "ship.chmmr.graphics.avatar.medium"
+#define CHMMR_MICON_MASK_PMAP_ANIM "ship.chmmr.meleeicons"
+#define CHMMR_RACE_STRINGS "ship.chmmr.text"
+#define CHMMR_SHIP_SOUNDS "ship.chmmr.sounds"
+#define CHMMR_SML_MASK_PMAP_ANIM "ship.chmmr.graphics.avatar.small"
+#define CHMMR_VICTORY_SONG "ship.chmmr.ditty"
+#define MUZZLE_BIG_MASK_PMAP_ANIM "ship.chmmr.graphics.muzzle.large"
+#define MUZZLE_MED_MASK_PMAP_ANIM "ship.chmmr.graphics.muzzle.medium"
+#define MUZZLE_SML_MASK_PMAP_ANIM "ship.chmmr.graphics.muzzle.small"
+#define SATELLITE_BIG_MASK_PMAP_ANIM "ship.chmmr.graphics.satellite.large"
+#define SATELLITE_MED_MASK_PMAP_ANIM "ship.chmmr.graphics.satellite.medium"
+#define SATELLITE_SML_MASK_PMAP_ANIM "ship.chmmr.graphics.satellite.small"
diff --git a/src/uqm/ships/druuge/Makeinfo b/src/uqm/ships/druuge/Makeinfo
new file mode 100644
index 0000000..6687b56
--- /dev/null
+++ b/src/uqm/ships/druuge/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="druuge.c"
+uqm_HFILES="druuge.h icode.h resinst.h"
diff --git a/src/uqm/ships/druuge/druuge.c b/src/uqm/ships/druuge/druuge.c
new file mode 100644
index 0000000..6ba2591
--- /dev/null
+++ b/src/uqm/ships/druuge/druuge.c
@@ -0,0 +1,324 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "druuge.h"
+#include "resinst.h"
+
+// Core characteristics
+#define MAX_CREW 14
+#define MAX_ENERGY 32
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 50
+#define MAX_THRUST 20
+#define THRUST_INCREMENT 2
+#define THRUST_WAIT 1
+#define TURN_WAIT 4
+#define SHIP_MASS 5
+
+// Mass Driver
+#define WEAPON_ENERGY_COST 4
+#define WEAPON_WAIT 10
+#define DRUUGE_OFFSET 24
+#define MISSILE_OFFSET 6
+#define MISSILE_SPEED DISPLAY_TO_WORLD (30)
+#define MISSILE_LIFE 20
+#define MISSILE_RANGE (MISSILE_SPEED * MISSILE_LIFE)
+#define MISSILE_HITS 4
+#define MISSILE_DAMAGE 6
+#define RECOIL_VELOCITY WORLD_TO_VELOCITY (DISPLAY_TO_WORLD (6))
+#define MAX_RECOIL_VELOCITY (RECOIL_VELOCITY * 4)
+
+// Furnace
+#define SPECIAL_ENERGY_COST 16
+#define SPECIAL_WAIT 30
+
+static RACE_DESC druuge_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 17, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ DRUUGE_RACE_STRINGS,
+ DRUUGE_ICON_MASK_PMAP_ANIM,
+ DRUUGE_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 1400 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 9500, 2792,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ DRUUGE_BIG_MASK_PMAP_ANIM,
+ DRUUGE_MED_MASK_PMAP_ANIM,
+ DRUUGE_SML_MASK_PMAP_ANIM,
+ },
+ {
+ DRUUGE_CANNON_BIG_MASK_PMAP_ANIM,
+ DRUUGE_CANNON_MED_MASK_PMAP_ANIM,
+ DRUUGE_CANNON_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ DRUUGE_CAPT_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ DRUUGE_VICTORY_SONG,
+ DRUUGE_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ MISSILE_RANGE,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+cannon_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+
+ if ((ElementPtr1->state_flags & PLAYER_SHIP)
+ && ElementPtr1->crew_level
+ && !GRAVITY_MASS (ElementPtr1->mass_points + 1))
+ {
+ COUNT angle;
+ SIZE cur_delta_x, cur_delta_y;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+
+ angle = FACING_TO_ANGLE (
+ GetFrameIndex (ElementPtr0->next.image.frame)
+ );
+ DeltaVelocityComponents (&ElementPtr1->velocity,
+ COSINE (angle, RECOIL_VELOCITY),
+ SINE (angle, RECOIL_VELOCITY));
+ GetCurrentVelocityComponents (&ElementPtr1->velocity,
+ &cur_delta_x, &cur_delta_y);
+ if ((long)cur_delta_x * (long)cur_delta_x
+ + (long)cur_delta_y * (long)cur_delta_y
+ > (long)MAX_RECOIL_VELOCITY * (long)MAX_RECOIL_VELOCITY)
+ {
+ angle = ARCTAN (cur_delta_x, cur_delta_y);
+ SetVelocityComponents (&ElementPtr1->velocity,
+ COSINE (angle, MAX_RECOIL_VELOCITY),
+ SINE (angle, MAX_RECOIL_VELOCITY));
+ }
+ }
+}
+
+static COUNT
+initialize_cannon (ELEMENT *ShipPtr, HELEMENT CannonArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = MissileBlock.face;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = DRUUGE_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ CannonArray[0] = initialize_missile (&MissileBlock);
+
+ if (CannonArray[0])
+ {
+ ELEMENT *CannonPtr;
+
+ LockElement (CannonArray[0], &CannonPtr);
+ CannonPtr->collision_func = cannon_collision;
+ UnlockElement (CannonArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+druuge_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ UWORD ship_flags = 0;
+ STARSHIP *StarShipPtr;
+ STARSHIP *EnemyStarShipPtr = NULL;
+ EVALUATE_DESC *lpEvalDesc;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (StarShipPtr->cur_status_flags & SHIP_BEYOND_MAX_SPEED)
+ lpEvalDesc->MoveState = ENTICE;
+ else if (lpEvalDesc->ObjectPtr
+ && lpEvalDesc->which_turn <= WORLD_TO_TURN (MISSILE_RANGE * 3 / 4))
+ {
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ ship_flags = EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags;
+ EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags &=
+ ~(FIRES_FORE | FIRES_RIGHT | FIRES_AFT | FIRES_LEFT);
+
+ lpEvalDesc->MoveState = PURSUE;
+ if (ShipPtr->thrust_wait == 0)
+ ++ShipPtr->thrust_wait;
+ }
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+ if (EnemyStarShipPtr)
+ {
+ EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags = ship_flags;
+ }
+
+ if (!(StarShipPtr->cur_status_flags & SHIP_BEYOND_MAX_SPEED)
+ && (lpEvalDesc->which_turn <= 12
+ || (
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn <= 6
+ )))
+ {
+ StarShipPtr->ship_input_state |= WEAPON;
+ if (ShipPtr->thrust_wait < WEAPON_WAIT + 1)
+ ShipPtr->thrust_wait = WEAPON_WAIT + 1;
+ }
+
+
+ if ((StarShipPtr->ship_input_state & WEAPON)
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level < WEAPON_ENERGY_COST
+ && ShipPtr->crew_level > 1)
+ StarShipPtr->ship_input_state |= SPECIAL;
+ else
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+}
+
+static void
+druuge_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ /* if just fired cannon */
+ if ((StarShipPtr->cur_status_flags & WEAPON)
+ && StarShipPtr->weapon_counter ==
+ StarShipPtr->RaceDescPtr->characteristics.weapon_wait)
+ {
+ COUNT angle;
+ SIZE cur_delta_x, cur_delta_y;
+
+ StarShipPtr->cur_status_flags &= ~SHIP_AT_MAX_SPEED;
+
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing) + HALF_CIRCLE;
+ DeltaVelocityComponents (&ElementPtr->velocity,
+ COSINE (angle, RECOIL_VELOCITY),
+ SINE (angle, RECOIL_VELOCITY));
+ GetCurrentVelocityComponents (&ElementPtr->velocity,
+ &cur_delta_x, &cur_delta_y);
+ if ((long)cur_delta_x * (long)cur_delta_x
+ + (long)cur_delta_y * (long)cur_delta_y
+ > (long)MAX_RECOIL_VELOCITY * (long)MAX_RECOIL_VELOCITY)
+ {
+ angle = ARCTAN (cur_delta_x, cur_delta_y);
+ SetVelocityComponents (&ElementPtr->velocity,
+ COSINE (angle, MAX_RECOIL_VELOCITY),
+ SINE (angle, MAX_RECOIL_VELOCITY));
+ }
+ }
+}
+
+static void
+druuge_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->cur_status_flags & SPECIAL)
+ {
+ if (StarShipPtr->special_counter
+ || ElementPtr->crew_level == 1
+ || StarShipPtr->RaceDescPtr->ship_info.energy_level
+ == StarShipPtr->RaceDescPtr->ship_info.max_energy)
+ StarShipPtr->cur_status_flags &= ~SPECIAL;
+ else
+ {
+ ProcessSound (SetAbsSoundIndex (
+ /* BURN UP CREW */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+
+ DeltaCrew (ElementPtr, -1);
+ DeltaEnergy (ElementPtr, SPECIAL_ENERGY_COST);
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+ }
+}
+
+RACE_DESC*
+init_druuge (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ druuge_desc.preprocess_func = druuge_preprocess;
+ druuge_desc.postprocess_func = druuge_postprocess;
+ druuge_desc.init_weapon_func = initialize_cannon;
+ druuge_desc.cyborg_control.intelligence_func = druuge_intelligence;
+
+ RaceDescPtr = &druuge_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/druuge/druuge.h b/src/uqm/ships/druuge/druuge.h
new file mode 100644
index 0000000..f332bc3
--- /dev/null
+++ b/src/uqm/ships/druuge/druuge.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DRUUGE_H
+#define DRUUGE_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_druuge (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* DRUUGE_H */
+
diff --git a/src/uqm/ships/druuge/icode.h b/src/uqm/ships/druuge/icode.h
new file mode 100644
index 0000000..8599490
--- /dev/null
+++ b/src/uqm/ships/druuge/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define DRUUGE_CODE "ship.druuge.code"
diff --git a/src/uqm/ships/druuge/resinst.h b/src/uqm/ships/druuge/resinst.h
new file mode 100644
index 0000000..2213ad9
--- /dev/null
+++ b/src/uqm/ships/druuge/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define DRUUGE_BIG_MASK_PMAP_ANIM "ship.druuge.graphics.mauler.large"
+#define DRUUGE_CANNON_BIG_MASK_PMAP_ANIM "ship.druuge.graphics.cannon.large"
+#define DRUUGE_CANNON_MED_MASK_PMAP_ANIM "ship.druuge.graphics.cannon.medium"
+#define DRUUGE_CANNON_SML_MASK_PMAP_ANIM "ship.druuge.graphics.cannon.small"
+#define DRUUGE_CAPT_MASK_PMAP_ANIM "ship.druuge.graphics.captain"
+#define DRUUGE_ICON_MASK_PMAP_ANIM "ship.druuge.icons"
+#define DRUUGE_MED_MASK_PMAP_ANIM "ship.druuge.graphics.mauler.medium"
+#define DRUUGE_MICON_MASK_PMAP_ANIM "ship.druuge.meleeicons"
+#define DRUUGE_RACE_STRINGS "ship.druuge.text"
+#define DRUUGE_SHIP_SOUNDS "ship.druuge.sounds"
+#define DRUUGE_SML_MASK_PMAP_ANIM "ship.druuge.graphics.mauler.small"
+#define DRUUGE_VICTORY_SONG "ship.druuge.ditty"
diff --git a/src/uqm/ships/human/Makeinfo b/src/uqm/ships/human/Makeinfo
new file mode 100644
index 0000000..f80f814
--- /dev/null
+++ b/src/uqm/ships/human/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="human.c"
+uqm_HFILES="human.h icode.h resinst.h"
diff --git a/src/uqm/ships/human/human.c b/src/uqm/ships/human/human.c
new file mode 100644
index 0000000..354486d
--- /dev/null
+++ b/src/uqm/ships/human/human.c
@@ -0,0 +1,360 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "human.h"
+#include "resinst.h"
+
+#include "uqm/colors.h"
+#include "uqm/globdata.h"
+
+// Core characteristics
+#define MAX_CREW 18
+#define MAX_ENERGY 18
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 8
+#define MAX_THRUST /* DISPLAY_TO_WORLD (6) */ 24
+#define THRUST_INCREMENT /* DISPLAY_TO_WORLD (2) */ 3
+#define THRUST_WAIT 4
+#define TURN_WAIT 1
+#define SHIP_MASS 6
+
+// Nuke
+#define WEAPON_ENERGY_COST 9
+#define WEAPON_WAIT 10
+#define HUMAN_OFFSET 42
+#define NUKE_OFFSET 8
+#define MIN_MISSILE_SPEED DISPLAY_TO_WORLD (10)
+#define MAX_MISSILE_SPEED DISPLAY_TO_WORLD (20)
+#define MISSILE_SPEED (MAX_THRUST >= MIN_MISSILE_SPEED ? \
+ MAX_THRUST : MIN_MISSILE_SPEED)
+#define THRUST_SCALE DISPLAY_TO_WORLD (1)
+#define MISSILE_LIFE 60
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 4
+#define TRACK_WAIT 3
+
+// Point-Defense Laser
+#define SPECIAL_ENERGY_COST 4
+#define SPECIAL_WAIT 9
+#define LASER_RANGE (UWORD)100
+
+static RACE_DESC human_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | SEEKING_WEAPON | POINT_DEFENSE,
+ 11, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ HUMAN_RACE_STRINGS,
+ HUMAN_ICON_MASK_PMAP_ANIM,
+ HUMAN_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 1752, 1450,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ HUMAN_BIG_MASK_PMAP_ANIM,
+ HUMAN_MED_MASK_PMAP_ANIM,
+ HUMAN_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SATURN_BIG_MASK_PMAP_ANIM,
+ SATURN_MED_MASK_PMAP_ANIM,
+ SATURN_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ HUMAN_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ HUMAN_VICTORY_SONG,
+ HUMAN_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ LONG_RANGE_WEAPON,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+nuke_preprocess (ELEMENT *ElementPtr)
+{
+ COUNT facing;
+
+ facing = GetFrameIndex (ElementPtr->next.image.frame);
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ if (TrackShip (ElementPtr, &facing) > 0)
+ {
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->next.image.frame,
+ facing);
+ ElementPtr->state_flags |= CHANGING;
+ }
+
+ ElementPtr->turn_wait = TRACK_WAIT;
+ }
+
+ {
+ SIZE speed;
+
+ if ((speed = MISSILE_SPEED +
+ ((MISSILE_LIFE - ElementPtr->life_span) *
+ THRUST_SCALE)) > MAX_MISSILE_SPEED)
+ speed = MAX_MISSILE_SPEED;
+ SetVelocityVector (&ElementPtr->velocity,
+ speed, facing);
+ }
+}
+
+static void
+spawn_point_defense (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ {
+ HELEMENT hDefense;
+
+ hDefense = AllocElement ();
+ if (hDefense)
+ {
+ ELEMENT *DefensePtr;
+
+ LockElement (hDefense, &DefensePtr);
+ DefensePtr->playerNr = ElementPtr->playerNr;
+ DefensePtr->state_flags = APPEARING | NONSOLID | FINITE_LIFE;
+ DefensePtr->death_func = spawn_point_defense;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ SetElementStarShip (DefensePtr, StarShipPtr);
+ UnlockElement (hDefense);
+
+ PutElement (hDefense);
+ }
+ }
+ else
+ {
+ BOOLEAN PaidFor;
+ HELEMENT hObject, hNextObject;
+ ELEMENT *ShipPtr;
+
+ PaidFor = FALSE;
+
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ for (hObject = GetTailElement (); hObject; hObject = hNextObject)
+ {
+ ELEMENT *ObjectPtr;
+
+ LockElement (hObject, &ObjectPtr);
+ hNextObject = GetPredElement (ObjectPtr);
+ if (ObjectPtr != ShipPtr && CollidingElement (ObjectPtr) &&
+ !OBJECT_CLOAKED (ObjectPtr))
+ {
+ SIZE delta_x, delta_y;
+
+ delta_x = ObjectPtr->next.location.x -
+ ShipPtr->next.location.x;
+ delta_y = ObjectPtr->next.location.y -
+ ShipPtr->next.location.y;
+ if (delta_x < 0)
+ delta_x = -delta_x;
+ if (delta_y < 0)
+ delta_y = -delta_y;
+ delta_x = WORLD_TO_DISPLAY (delta_x);
+ delta_y = WORLD_TO_DISPLAY (delta_y);
+ if ((UWORD)delta_x <= LASER_RANGE &&
+ (UWORD)delta_y <= LASER_RANGE &&
+ (UWORD)delta_x * (UWORD)delta_x +
+ (UWORD)delta_y * (UWORD)delta_y <=
+ LASER_RANGE * LASER_RANGE)
+ {
+ HELEMENT hPointDefense;
+ LASER_BLOCK LaserBlock;
+
+ if (!PaidFor)
+ {
+ if (!DeltaEnergy (ShipPtr, -SPECIAL_ENERGY_COST))
+ break;
+
+ ProcessSound (SetAbsSoundIndex (
+ /* POINT_DEFENSE_LASER */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ PaidFor = TRUE;
+ }
+
+ LaserBlock.cx = ShipPtr->next.location.x;
+ LaserBlock.cy = ShipPtr->next.location.y;
+ LaserBlock.face = 0;
+ LaserBlock.ex = ObjectPtr->next.location.x
+ - ShipPtr->next.location.x;
+ LaserBlock.ey = ObjectPtr->next.location.y
+ - ShipPtr->next.location.y;
+ LaserBlock.sender = ShipPtr->playerNr;
+ LaserBlock.flags = IGNORE_SIMILAR;
+ LaserBlock.pixoffs = 0;
+ LaserBlock.color = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F);
+ hPointDefense = initialize_laser (&LaserBlock);
+ if (hPointDefense)
+ {
+ ELEMENT *PDPtr;
+
+ LockElement (hPointDefense, &PDPtr);
+ SetElementStarShip (PDPtr, StarShipPtr);
+ PDPtr->hTarget = 0;
+ UnlockElement (hPointDefense);
+
+ PutElement (hPointDefense);
+ }
+ }
+ }
+ UnlockElement (hObject);
+ }
+ UnlockElement (StarShipPtr->hShip);
+ }
+}
+
+static COUNT
+initialize_nuke (ELEMENT *ShipPtr, HELEMENT NukeArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = 0;
+ MissileBlock.pixoffs = HUMAN_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = nuke_preprocess;
+ MissileBlock.blast_offs = NUKE_OFFSET;
+ NukeArray[0] = initialize_missile (&MissileBlock);
+
+ if (NukeArray[0])
+ {
+ ELEMENT *NukePtr;
+
+ LockElement (NukeArray[0], &NukePtr);
+ NukePtr->turn_wait = TRACK_WAIT;
+ UnlockElement (NukeArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+human_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->special_counter == 0
+ && ((ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr != NULL
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn <= 2)
+ || (ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr != NULL
+ && ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn <= 4)))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ else
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr = NULL;
+
+ ship_intelligence (ShipPtr,
+ ObjectsOfConcern, ConcernCounter);
+
+ if (StarShipPtr->weapon_counter == 0)
+ {
+ if (ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr
+ && (!(StarShipPtr->ship_input_state & (LEFT | RIGHT /* | THRUST */))
+ || ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn <= 12))
+ StarShipPtr->ship_input_state |= WEAPON;
+ }
+}
+
+static void
+human_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0)
+ {
+ spawn_point_defense (ElementPtr);
+ }
+}
+
+RACE_DESC*
+init_human (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ human_desc.postprocess_func = human_postprocess;
+ human_desc.init_weapon_func = initialize_nuke;
+ human_desc.cyborg_control.intelligence_func = human_intelligence;
+
+ RaceDescPtr = &human_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/human/human.h b/src/uqm/ships/human/human.h
new file mode 100644
index 0000000..6f7314d
--- /dev/null
+++ b/src/uqm/ships/human/human.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef HUMAN_H
+#define HUMAN_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_human (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* HUMAN_H */
+
diff --git a/src/uqm/ships/human/icode.h b/src/uqm/ships/human/icode.h
new file mode 100644
index 0000000..acfd62e
--- /dev/null
+++ b/src/uqm/ships/human/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define HUMAN_CODE "ship.earthling.code"
diff --git a/src/uqm/ships/human/resinst.h b/src/uqm/ships/human/resinst.h
new file mode 100644
index 0000000..3d4022e
--- /dev/null
+++ b/src/uqm/ships/human/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define HUMAN_BIG_MASK_PMAP_ANIM "ship.earthling.graphics.human.large"
+#define HUMAN_CAPTAIN_MASK_PMAP_ANIM "ship.earthling.graphics.captain"
+#define HUMAN_ICON_MASK_PMAP_ANIM "ship.earthling.icons"
+#define HUMAN_MED_MASK_PMAP_ANIM "ship.earthling.graphics.human.medium"
+#define HUMAN_MICON_MASK_PMAP_ANIM "ship.earthling.meleeicons"
+#define HUMAN_RACE_STRINGS "ship.earthling.text"
+#define HUMAN_SHIP_SOUNDS "ship.earthling.sounds"
+#define HUMAN_SML_MASK_PMAP_ANIM "ship.earthling.graphics.human.small"
+#define HUMAN_VICTORY_SONG "ship.earthling.ditty"
+#define SATURN_BIG_MASK_PMAP_ANIM "ship.earthling.graphics.saturn.large"
+#define SATURN_MED_MASK_PMAP_ANIM "ship.earthling.graphics.saturn.medium"
+#define SATURN_SML_MASK_PMAP_ANIM "ship.earthling.graphics.saturn.small"
diff --git a/src/uqm/ships/ilwrath/Makeinfo b/src/uqm/ships/ilwrath/Makeinfo
new file mode 100644
index 0000000..cbc8f69
--- /dev/null
+++ b/src/uqm/ships/ilwrath/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="ilwrath.c"
+uqm_HFILES="icode.h ilwrath.h resinst.h"
diff --git a/src/uqm/ships/ilwrath/icode.h b/src/uqm/ships/ilwrath/icode.h
new file mode 100644
index 0000000..fa78adc
--- /dev/null
+++ b/src/uqm/ships/ilwrath/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ILWRATH_CODE "ship.ilwrath.code"
diff --git a/src/uqm/ships/ilwrath/ilwrath.c b/src/uqm/ships/ilwrath/ilwrath.c
new file mode 100644
index 0000000..3947081
--- /dev/null
+++ b/src/uqm/ships/ilwrath/ilwrath.c
@@ -0,0 +1,409 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "ilwrath.h"
+#include "resinst.h"
+
+#include "uqm/colors.h"
+#include "uqm/globdata.h"
+
+
+// Core characteristics
+#define MAX_CREW 22
+#define MAX_ENERGY 16
+#define ENERGY_REGENERATION 4
+#define ENERGY_WAIT 4
+#define MAX_THRUST 25
+#define THRUST_INCREMENT 5
+#define THRUST_WAIT 0
+#define TURN_WAIT 2
+#define SHIP_MASS 7
+#define LOOK_AHEAD 4
+ /* Controls how much the auto-turn will attempt to "lead"
+ * its target. */
+
+// Hellfire Spout
+#define WEAPON_ENERGY_COST 1
+#define WEAPON_WAIT 0
+#define MISSILE_LIFE 8
+#define ILWRATH_OFFSET 29
+#define MISSILE_SPEED MAX_THRUST
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 1
+#define MISSILE_OFFSET 0
+
+// Cloaking Device
+#define SPECIAL_ENERGY_COST 3
+#define SPECIAL_WAIT 13
+
+static RACE_DESC ilwrath_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 10, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ ILWRATH_RACE_STRINGS,
+ ILWRATH_ICON_MASK_PMAP_ANIM,
+ ILWRATH_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 1410 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 48, 1700,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ ILWRATH_BIG_MASK_PMAP_ANIM,
+ ILWRATH_MED_MASK_PMAP_ANIM,
+ ILWRATH_SML_MASK_PMAP_ANIM,
+ },
+ {
+ FIRE_BIG_MASK_PMAP_ANIM,
+ FIRE_MED_MASK_PMAP_ANIM,
+ FIRE_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ ILWRATH_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ ILWRATH_VICTORY_SONG,
+ ILWRATH_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ CLOSE_RANGE_WEAPON,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+flame_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = ElementPtr->next_turn;
+ }
+}
+
+static void
+flame_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ ElementPtr0->state_flags &= ~DISAPPEARING;
+ ElementPtr0->state_flags |= NONSOLID;
+}
+
+static void
+ilwrath_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ lpEvalDesc->MoveState = PURSUE;
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->which_turn <= 10)
+ /* don't want to dodge when you could be flaming */
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr = 0;
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (lpEvalDesc->ObjectPtr
+ && (lpEvalDesc->which_turn <= 6
+ || (lpEvalDesc->which_turn <= 10
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn <= 10)))
+ {
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (OBJECT_CLOAKED (ShipPtr))
+ {
+ StarShipPtr->ship_input_state &= ~LEFT | RIGHT;
+ StarShipPtr->ship_input_state |= THRUST;
+ }
+ StarShipPtr->ship_input_state |= WEAPON;
+ }
+ else if (StarShipPtr->special_counter == 0
+ && (LOBYTE (GLOBAL (CurrentActivity)) != IN_ENCOUNTER
+ || !GET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER)))
+ {
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (!OBJECT_CLOAKED (ShipPtr)
+ && !(StarShipPtr->ship_input_state & WEAPON))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+}
+
+static COUNT
+initialize_flame (ELEMENT *ShipPtr, HELEMENT FlameArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = ILWRATH_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = flame_preprocess;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ FlameArray[0] = initialize_missile (&MissileBlock);
+
+ if (FlameArray[0])
+ {
+ SIZE dx, dy;
+ ELEMENT *FlamePtr;
+
+ LockElement (FlameArray[0], &FlamePtr);
+ GetCurrentVelocityComponents (&ShipPtr->velocity, &dx, &dy);
+ DeltaVelocityComponents (&FlamePtr->velocity, dx, dy);
+ FlamePtr->current.location.x -= VELOCITY_TO_WORLD (dx);
+ FlamePtr->current.location.y -= VELOCITY_TO_WORLD (dy);
+
+ FlamePtr->collision_func = flame_collision;
+ FlamePtr->turn_wait = 0;
+ UnlockElement (FlameArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+ilwrath_preprocess (ELEMENT *ElementPtr)
+{
+ STATUS_FLAGS status_flags;
+ STARSHIP *StarShipPtr;
+ PRIMITIVE *lpPrim;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ status_flags = StarShipPtr->cur_status_flags;
+ lpPrim = &(GLOBAL (DisplayArray))[ElementPtr->PrimIndex];
+ if (GetPrimType (lpPrim) == STAMPFILL_PRIM)
+ {
+ Color color;
+ BOOLEAN weapon_discharge;
+
+ color = GetPrimColor (lpPrim);
+ weapon_discharge = ((status_flags & WEAPON)
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >= WEAPON_ENERGY_COST);
+ if (weapon_discharge
+ || (StarShipPtr->special_counter == 0
+ && ((status_flags & SPECIAL) ||
+ !sameColor (color, BLACK_COLOR))))
+ {
+ if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F)))
+ SetPrimType (lpPrim, STAMP_PRIM);
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F));
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B));
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03));
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09));
+ else
+ {
+ ProcessSound (SetAbsSoundIndex (
+ /* CLOAKING_OFF */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2), ElementPtr);
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01));
+ if (weapon_discharge)
+ {
+ COUNT facing;
+
+ facing = StarShipPtr->ShipFacing;
+ if (TrackShip (ElementPtr, &facing) >= 0)
+ {
+ ELEMENT *eptr;
+ SIZE dx0, dy0, dx1, dy1;
+ VELOCITY_DESC v;
+
+ LockElement (ElementPtr->hTarget, &eptr);
+ v = eptr->velocity;
+ GetNextVelocityComponents (&v, &dx0, &dy0, LOOK_AHEAD);
+ v = ElementPtr->velocity;
+ GetNextVelocityComponents (&v, &dx1, &dy1, LOOK_AHEAD);
+ dx0 = (eptr->current.location.x + dx0)
+ - (ElementPtr->current.location.x + dx1);
+ dy0 = (eptr->current.location.y + dy0)
+ - (ElementPtr->current.location.y + dy1);
+ UnlockElement (ElementPtr->hTarget);
+
+ StarShipPtr->ShipFacing =
+ NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (dx0, dy0))
+ );
+#ifdef NOTYET
+ if (ElementPtr->thrust_wait == 0
+ && (StarShipPtr->cur_status_flags & THRUST))
+ {
+ COUNT last_facing;
+
+ do
+ {
+ VELOCITY_DESC temp_v;
+
+ last_facing = StarShipPtr->ShipFacing;
+ inertial_thrust (ElementPtr);
+ temp_v = ElementPtr->velocity;
+ ElementPtr->velocity = v;
+
+ dx0 += dx1;
+ dy0 += dy1;
+ GetNextVelocityComponents (&temp_v,
+ &dx1, &dy1, LOOK_AHEAD);
+ dx0 -= dx1;
+ dy0 -= dy1;
+ StarShipPtr->ShipFacing =
+ NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (dx0, dy0))
+ );
+ } while (StarShipPtr->ShipFacing != last_facing);
+ }
+#endif /* NOTYET */
+ if (ElementPtr->turn_wait == 0)
+ ++ElementPtr->turn_wait;
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->next.image.frame,
+ StarShipPtr->ShipFacing);
+ }
+ ElementPtr->hTarget = 0;
+ }
+ }
+
+ ElementPtr->state_flags |= CHANGING;
+ status_flags &= ~SPECIAL;
+ StarShipPtr->special_counter = 0;
+ }
+ else if (!sameColor (color, BLACK_COLOR))
+ {
+ if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)))
+ {
+ SetPrimColor (lpPrim, BLACK_COLOR);
+ Untarget (ElementPtr);
+ }
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01));
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09));
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03));
+ else
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B));
+
+ ElementPtr->state_flags |= CHANGING;
+ }
+ }
+
+ if ((status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F));
+ SetPrimType (lpPrim, STAMPFILL_PRIM);
+
+ ProcessSound (SetAbsSoundIndex (
+ /* CLOAKING_ON */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1),
+ ElementPtr);
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+RACE_DESC*
+init_ilwrath (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ ilwrath_desc.preprocess_func = ilwrath_preprocess;
+ ilwrath_desc.init_weapon_func = initialize_flame;
+ ilwrath_desc.cyborg_control.intelligence_func = ilwrath_intelligence;
+
+ RaceDescPtr = &ilwrath_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/ilwrath/ilwrath.h b/src/uqm/ships/ilwrath/ilwrath.h
new file mode 100644
index 0000000..a442b9d
--- /dev/null
+++ b/src/uqm/ships/ilwrath/ilwrath.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ILWRATH_H
+#define ILWRATH_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_ilwrath (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* ILWRATH_H */
+
diff --git a/src/uqm/ships/ilwrath/resinst.h b/src/uqm/ships/ilwrath/resinst.h
new file mode 100644
index 0000000..46cb53a
--- /dev/null
+++ b/src/uqm/ships/ilwrath/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define FIRE_BIG_MASK_PMAP_ANIM "ship.ilwrath.graphics.fire.large"
+#define FIRE_MED_MASK_PMAP_ANIM "ship.ilwrath.graphics.fire.medium"
+#define FIRE_SML_MASK_PMAP_ANIM "ship.ilwrath.graphics.fire.small"
+#define ILWRATH_BIG_MASK_PMAP_ANIM "ship.ilwrath.graphics.avenger.large"
+#define ILWRATH_CAPTAIN_MASK_PMAP_ANIM "ship.ilwrath.graphics.captain"
+#define ILWRATH_ICON_MASK_PMAP_ANIM "ship.ilwrath.icons"
+#define ILWRATH_MED_MASK_PMAP_ANIM "ship.ilwrath.graphics.avenger.medium"
+#define ILWRATH_MICON_MASK_PMAP_ANIM "ship.ilwrath.meleeicons"
+#define ILWRATH_RACE_STRINGS "ship.ilwrath.text"
+#define ILWRATH_SHIP_SOUNDS "ship.ilwrath.sounds"
+#define ILWRATH_SML_MASK_PMAP_ANIM "ship.ilwrath.graphics.avenger.small"
+#define ILWRATH_VICTORY_SONG "ship.ilwrath.ditty"
diff --git a/src/uqm/ships/lastbat/Makeinfo b/src/uqm/ships/lastbat/Makeinfo
new file mode 100644
index 0000000..589d8d0
--- /dev/null
+++ b/src/uqm/ships/lastbat/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="lastbat.c"
+uqm_HFILES="icode.h lastbat.h resinst.h"
diff --git a/src/uqm/ships/lastbat/icode.h b/src/uqm/ships/lastbat/icode.h
new file mode 100644
index 0000000..087c891
--- /dev/null
+++ b/src/uqm/ships/lastbat/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SAMATRA_CODE "ship.samatra.code"
diff --git a/src/uqm/ships/lastbat/lastbat.c b/src/uqm/ships/lastbat/lastbat.c
new file mode 100644
index 0000000..9d44742
--- /dev/null
+++ b/src/uqm/ships/lastbat/lastbat.c
@@ -0,0 +1,926 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "lastbat.h"
+#include "resinst.h"
+
+#include "uqm/colors.h"
+#include "uqm/globdata.h"
+#include "uqm/battle.h"
+ // For BATTLE_FRAME_RATE
+#include "libs/mathlib.h"
+#include "libs/timelib.h"
+
+#define num_generators characteristics.max_thrust
+
+// Core characteristics
+#define MAX_CREW 1
+#define MAX_ENERGY MAX_ENERGY_SIZE
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 6
+#define MAX_THRUST 0
+#define THRUST_INCREMENT 0
+#define TURN_WAIT 0
+#define THRUST_WAIT 0
+#define SHIP_MASS (MAX_SHIP_MASS * 10)
+#define TURRET_WAIT 0 /* Controls animation of the Sa-Matra's central
+ * 'furnace', a new frame is displayed once every
+ * TURRET_WAIT frames. */
+
+// Yellow comet
+#define WEAPON_WAIT ((ONE_SECOND / BATTLE_FRAME_RATE) * 10)
+#define COMET_DAMAGE 2
+#define COMET_OFFSET 0
+#define COMET_HITS 12
+#define COMET_SPEED DISPLAY_TO_WORLD (12)
+#define COMET_LIFE 2
+#define COMET_TURN_WAIT 3
+#define MAX_COMETS 3
+#define WEAPON_ENERGY_COST 2
+ /* Used for samatra_desc.weapon_energy_cost, but the value isn't
+ * actually used. */
+
+// Green sentinel
+#define SPECIAL_WAIT ((ONE_SECOND / BATTLE_FRAME_RATE) * 3)
+#define SENTINEL_SPEED DISPLAY_TO_WORLD (8)
+#define SENTINEL_LIFE 2
+#define SENTINEL_OFFSET 0
+#define SENTINEL_HITS 10
+#define SENTINEL_DAMAGE 1
+#define TRACK_WAIT 1
+#define ANIMATION_WAIT 1
+#define RECOIL_VELOCITY WORLD_TO_VELOCITY (DISPLAY_TO_WORLD (10))
+#define MAX_RECOIL_VELOCITY (RECOIL_VELOCITY * 4)
+#define MAX_SENTINELS 4
+#define SPECIAL_ENERGY_COST 3
+ /* Used for samatra_desc.special_energy_cost, but the value isn't
+ * actually used. */
+
+// Blue force field
+#define GATE_DAMAGE 1
+#define GATE_HITS 100
+
+// Red generators
+#define GENERATOR_HITS 15
+#define MAX_GENERATORS 8
+
+static RACE_DESC samatra_desc =
+{
+ { /* SHIP_INFO */
+ /* FIRES_FORE | */ IMMEDIATE_WEAPON | CREW_IMMUNE,
+ 16, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 0, 0,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ SAMATRA_BIG_MASK_ANIM,
+ SAMATRA_MED_MASK_PMAP_ANIM,
+ SAMATRA_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SENTINEL_BIG_MASK_ANIM,
+ SENTINEL_MED_MASK_PMAP_ANIM,
+ SENTINEL_SML_MASK_PMAP_ANIM,
+ },
+ {
+ GENERATOR_BIG_MASK_ANIM,
+ GENERATOR_MED_MASK_PMAP_ANIM,
+ GENERATOR_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SAMATRA_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ NULL_RESOURCE,
+ SAMATRA_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ 0,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static HELEMENT spawn_comet (ELEMENT *ElementPtr);
+
+static void
+comet_preprocess (ELEMENT *ElementPtr)
+{
+ COUNT frame_index;
+
+ frame_index = GetFrameIndex (ElementPtr->current.image.frame) + 1;
+ if (frame_index < 29)
+ {
+ if (frame_index == 25)
+ {
+ SIZE cur_delta_x, cur_delta_y;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ ++StarShipPtr->RaceDescPtr->characteristics.weapon_wait;
+ spawn_comet (ElementPtr);
+ ElementPtr->state_flags |= NONSOLID;
+
+ GetCurrentVelocityComponents (&ElementPtr->velocity,
+ &cur_delta_x, &cur_delta_y);
+ SetVelocityComponents (&ElementPtr->velocity,
+ cur_delta_x / 2, cur_delta_y / 2);
+ }
+ ++ElementPtr->life_span;
+ }
+
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (
+ ElementPtr->current.image.frame, frame_index
+ );
+ ElementPtr->state_flags |= CHANGING;
+}
+
+static void
+comet_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (ElementPtr1->playerNr == RPG_PLAYER_NUM)
+ {
+ BYTE old_hits;
+ COUNT old_life;
+ HELEMENT hBlastElement;
+
+ if (ElementPtr1->state_flags & PLAYER_SHIP)
+ ElementPtr0->mass_points = COMET_DAMAGE;
+ else
+ ElementPtr0->mass_points = 50;
+
+ old_hits = ElementPtr0->hit_points;
+ old_life = ElementPtr0->life_span;
+ hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ if (ElementPtr1->state_flags & PLAYER_SHIP)
+ {
+ ElementPtr0->hit_points = old_hits;
+ ElementPtr0->life_span = old_life;
+ ElementPtr0->state_flags &= ~(DISAPPEARING | NONSOLID | COLLISION);
+
+ if (hBlastElement)
+ {
+ RemoveElement (hBlastElement);
+ FreeElement (hBlastElement);
+ }
+ }
+
+ if (ElementPtr0->state_flags & DISAPPEARING)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ --StarShipPtr->RaceDescPtr->characteristics.weapon_wait;
+ }
+ }
+}
+
+static HELEMENT
+spawn_comet (ELEMENT *ElementPtr)
+{
+ MISSILE_BLOCK MissileBlock;
+ HELEMENT hComet;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ MissileBlock.cx = ElementPtr->next.location.x;
+ MissileBlock.cy = ElementPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = 0;
+ MissileBlock.index = 24;
+ MissileBlock.sender = ElementPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = 0;
+ MissileBlock.speed = 0;
+ MissileBlock.hit_points = COMET_HITS;
+ MissileBlock.damage = COMET_DAMAGE;
+ MissileBlock.life = COMET_LIFE;
+ MissileBlock.preprocess_func = comet_preprocess;
+ MissileBlock.blast_offs = COMET_OFFSET;
+ hComet = initialize_missile (&MissileBlock);
+
+ if (hComet)
+ {
+ ELEMENT *CometPtr;
+
+ PutElement (hComet);
+
+ LockElement (hComet, &CometPtr);
+ CometPtr->collision_func = comet_collision;
+ SetElementStarShip (CometPtr, StarShipPtr);
+ {
+ COUNT facing;
+
+ CometPtr->turn_wait = ElementPtr->turn_wait;
+ CometPtr->hTarget = ElementPtr->hTarget;
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ {
+ CometPtr->turn_wait = 0;
+ facing = (COUNT)TFB_Random ();
+ SetVelocityVector (&CometPtr->velocity,
+ COMET_SPEED, facing);
+ }
+ else
+ {
+ CometPtr->velocity = ElementPtr->velocity;
+ CometPtr->hit_points = ElementPtr->hit_points;
+ facing = ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&CometPtr->velocity)
+ );
+ }
+
+ if (CometPtr->turn_wait)
+ --CometPtr->turn_wait;
+ else
+ {
+ facing = NORMALIZE_FACING (facing);
+ if (TrackShip (CometPtr, &facing) > 0)
+ SetVelocityVector (&CometPtr->velocity,
+ COMET_SPEED, facing);
+ CometPtr->turn_wait = COMET_TURN_WAIT;
+ }
+ }
+ UnlockElement (hComet);
+ }
+
+ return (hComet);
+}
+
+static void
+turret_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame,
+ (GetFrameIndex (ElementPtr->current.image.frame) % 10) + 1);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = TURRET_WAIT;
+ }
+}
+
+static void
+gate_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (ElementPtr1->playerNr == RPG_PLAYER_NUM)
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ if (StarShipPtr->RaceDescPtr->num_generators == 0)
+ {
+ if (!(ElementPtr1->state_flags & FINITE_LIFE))
+ ElementPtr0->state_flags |= COLLISION;
+
+ if ((ElementPtr1->state_flags & PLAYER_SHIP)
+ && GetPrimType (
+ &GLOBAL (DisplayArray[ElementPtr0->PrimIndex])
+ ) == STAMPFILL_PRIM
+ && GET_GAME_STATE (BOMB_CARRIER))
+ {
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ }
+ }
+ else
+ {
+ HELEMENT hBlastElement;
+
+ if (ElementPtr1->state_flags & PLAYER_SHIP)
+ ElementPtr0->mass_points = GATE_DAMAGE;
+ else
+ ElementPtr0->mass_points = 50;
+
+ ElementPtr0->hit_points = GATE_HITS;
+ hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ ElementPtr0->state_flags &= ~(DISAPPEARING | NONSOLID | COLLISION);
+ ElementPtr0->life_span = NORMAL_LIFE;
+
+ if (hBlastElement)
+ {
+ RemoveElement (hBlastElement);
+ FreeElement (hBlastElement);
+ }
+ }
+ }
+}
+
+static void
+gate_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->RaceDescPtr->num_generators == 0)
+ {
+ ElementPtr->mass_points = SHIP_MASS;
+ ElementPtr->state_flags &= ~FINITE_LIFE;
+ ElementPtr->life_span = NORMAL_LIFE + 1;
+ ElementPtr->preprocess_func = 0;
+ SetPrimColor (
+ &GLOBAL (DisplayArray[ElementPtr->PrimIndex]),
+ BLACK_COLOR
+ );
+ SetPrimType (
+ &GLOBAL (DisplayArray[ElementPtr->PrimIndex]),
+ STAMPFILL_PRIM
+ );
+ }
+ else
+ {
+ ++ElementPtr->life_span;
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ if (GetFrameIndex (ElementPtr->next.image.frame) == 0)
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (
+ ElementPtr->next.image.frame, 11
+ );
+
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+static void
+generator_death (ELEMENT *ElementPtr)
+{
+ if (!(ElementPtr->state_flags & FINITE_LIFE))
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ --StarShipPtr->RaceDescPtr->num_generators;
+ ElementPtr->state_flags |= FINITE_LIFE | NONSOLID;
+ ElementPtr->preprocess_func = 0;
+ ElementPtr->turn_wait = 12;
+ ElementPtr->thrust_wait = 0;
+
+ ElementPtr->current.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame, 10 - 1);
+ }
+
+ if (ElementPtr->thrust_wait)
+ {
+ --ElementPtr->thrust_wait;
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->state_flags |= CHANGING;
+ ++ElementPtr->life_span;
+ }
+ else if (ElementPtr->turn_wait--)
+ {
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->state_flags |= CHANGING;
+ ++ElementPtr->life_span;
+
+ ElementPtr->next.image.frame = IncFrameIndex (
+ ElementPtr->current.image.frame
+ );
+
+ ElementPtr->thrust_wait = 1;
+ }
+}
+
+static void
+generator_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else if ((ElementPtr->turn_wait =
+ (BYTE)((GENERATOR_HITS
+ - ElementPtr->hit_points) / 5)) < 3)
+ {
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame,
+ (GetFrameIndex (ElementPtr->current.image.frame) + 1) % 10);
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+static void
+generator_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (!(ElementPtr1->state_flags & FINITE_LIFE))
+ {
+ ElementPtr0->state_flags |= COLLISION;
+ }
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static void
+sentinel_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ ++StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ ++ElementPtr->life_span;
+
+ if (ElementPtr->thrust_wait)
+ --ElementPtr->thrust_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame,
+ (GetFrameIndex (ElementPtr->current.image.frame) + 1) % 6);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->thrust_wait = ANIMATION_WAIT;
+ }
+
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ COUNT facing;
+ HELEMENT hTarget;
+
+ if (!(ElementPtr->state_flags & NONSOLID))
+ facing = ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&ElementPtr->velocity)
+ );
+ else
+ {
+ ElementPtr->state_flags &= ~NONSOLID;
+ facing = (COUNT)TFB_Random ();
+ SetVelocityVector (&ElementPtr->velocity,
+ SENTINEL_SPEED, facing);
+ }
+ facing = NORMALIZE_FACING (facing);
+ if (ElementPtr->hTarget == 0)
+ {
+ COUNT f;
+
+ f = facing;
+ TrackShip (ElementPtr, &f);
+ }
+
+ if (ElementPtr->hTarget == 0)
+ hTarget = StarShipPtr->hShip;
+ else if (StarShipPtr->hShip == 0)
+ hTarget = ElementPtr->hTarget;
+ else
+ {
+ SIZE delta_x0, delta_y0, delta_x1, delta_y1;
+ ELEMENT *ShipPtr;
+ ELEMENT *EnemyShipPtr;
+
+ LockElement (ElementPtr->hTarget, &EnemyShipPtr);
+
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ delta_x0 = ShipPtr->current.location.x
+ - ElementPtr->current.location.x;
+ delta_y0 = ShipPtr->current.location.y
+ - ElementPtr->current.location.y;
+
+ delta_x1 = ShipPtr->current.location.x
+ - EnemyShipPtr->current.location.x;
+ delta_y1 = ShipPtr->current.location.y
+ - EnemyShipPtr->current.location.y;
+ UnlockElement (StarShipPtr->hShip);
+
+ if ((long)delta_x0 * delta_x0
+ + (long)delta_y0 * delta_y0 >
+ (long)delta_x1 * delta_x1
+ + (long)delta_y1 * delta_y1)
+ hTarget = StarShipPtr->hShip;
+ else
+ hTarget = ElementPtr->hTarget;
+
+ UnlockElement (ElementPtr->hTarget);
+ }
+
+ if (hTarget)
+ {
+ COUNT num_frames;
+ SIZE delta_x, delta_y;
+ ELEMENT *TargetPtr;
+ VELOCITY_DESC TargetVelocity;
+
+ LockElement (hTarget, &TargetPtr);
+
+ delta_x = TargetPtr->current.location.x
+ - ElementPtr->current.location.x;
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = TargetPtr->current.location.y
+ - ElementPtr->current.location.y;
+ delta_y = WRAP_DELTA_Y (delta_y);
+
+ if ((num_frames = WORLD_TO_TURN (
+ square_root ((long)delta_x * delta_x
+ + (long)delta_y * delta_y)
+ )) == 0)
+ num_frames = 1;
+
+ TargetVelocity = TargetPtr->velocity;
+ GetNextVelocityComponents (&TargetVelocity,
+ &delta_x, &delta_y, num_frames);
+
+ delta_x = (TargetPtr->current.location.x + delta_x)
+ - ElementPtr->current.location.x;
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = (TargetPtr->current.location.y + delta_y)
+ - ElementPtr->current.location.y;
+ delta_y = WRAP_DELTA_Y (delta_y);
+
+ UnlockElement (hTarget);
+
+ delta_x = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y)) - facing
+ );
+
+ if (delta_x > 0)
+ {
+ if (delta_x <= ANGLE_TO_FACING (HALF_CIRCLE))
+ ++facing;
+ else
+ --facing;
+ }
+
+ SetVelocityVector (&ElementPtr->velocity,
+ SENTINEL_SPEED, facing);
+ }
+
+ ElementPtr->turn_wait = TRACK_WAIT;
+ }
+}
+
+static void
+sentinel_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ COUNT angle;
+ STARSHIP *StarShipPtr;
+
+ if (ElementPtr1->playerNr == NPC_PLAYER_NUM)
+ {
+ if (ElementPtr0->preprocess_func == ElementPtr1->preprocess_func
+ && !(ElementPtr0->state_flags & DEFY_PHYSICS)
+ && (pPt0->x != ElementPtr0->IntersectControl.IntersectStamp.origin.x
+ || pPt0->y != ElementPtr0->IntersectControl.IntersectStamp.origin.y))
+ {
+ angle = ARCTAN (pPt0->x - pPt1->x, pPt0->y - pPt1->y);
+
+ SetVelocityComponents (&ElementPtr0->velocity,
+ COSINE (angle, WORLD_TO_VELOCITY (SENTINEL_SPEED)),
+ SINE (angle, WORLD_TO_VELOCITY (SENTINEL_SPEED)));
+ ElementPtr0->turn_wait = TRACK_WAIT;
+ ElementPtr0->state_flags |= COLLISION | DEFY_PHYSICS;
+ }
+ }
+ else
+ {
+ BYTE old_hits;
+ COUNT old_life;
+ HELEMENT hBlastElement;
+
+ old_hits = ElementPtr0->hit_points;
+ old_life = ElementPtr0->life_span;
+ ElementPtr0->blast_offset = 0;
+ hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ ElementPtr0->thrust_wait = 0;
+
+ if ((ElementPtr1->state_flags & PLAYER_SHIP)
+ && ElementPtr1->crew_level
+ && !GRAVITY_MASS (ElementPtr1->mass_points + 1))
+ {
+ SIZE cur_delta_x, cur_delta_y;
+
+ ElementPtr0->life_span = old_life;
+ ElementPtr0->hit_points = old_hits;
+ ElementPtr0->state_flags &= ~DISAPPEARING;
+ ElementPtr0->state_flags |= DEFY_PHYSICS;
+ ElementPtr0->turn_wait = (ONE_SECOND / BATTLE_FRAME_RATE) >> 1;
+
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+ if (ElementPtr1->turn_wait < COLLISION_TURN_WAIT)
+ ElementPtr1->turn_wait += COLLISION_TURN_WAIT;
+ if (ElementPtr1->thrust_wait < COLLISION_THRUST_WAIT)
+ ElementPtr1->thrust_wait += COLLISION_THRUST_WAIT;
+
+ angle = GetVelocityTravelAngle (&ElementPtr0->velocity);
+ DeltaVelocityComponents (&ElementPtr1->velocity,
+ COSINE (angle, RECOIL_VELOCITY),
+ SINE (angle, RECOIL_VELOCITY));
+ GetCurrentVelocityComponents (&ElementPtr1->velocity,
+ &cur_delta_x, &cur_delta_y);
+ if ((long)cur_delta_x * (long)cur_delta_x
+ + (long)cur_delta_y * (long)cur_delta_y
+ > (long)MAX_RECOIL_VELOCITY * (long)MAX_RECOIL_VELOCITY)
+ {
+ angle = ARCTAN (cur_delta_x, cur_delta_y);
+ SetVelocityComponents (&ElementPtr1->velocity,
+ COSINE (angle, MAX_RECOIL_VELOCITY),
+ SINE (angle, MAX_RECOIL_VELOCITY));
+ }
+
+ ZeroVelocityComponents (&ElementPtr0->velocity);
+ }
+
+ if (ElementPtr0->state_flags & DISAPPEARING)
+ {
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ --StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ if (hBlastElement)
+ {
+ ELEMENT *BlastElementPtr;
+
+ LockElement (hBlastElement, &BlastElementPtr);
+ BlastElementPtr->life_span = 6;
+ BlastElementPtr->current.image.frame =
+ SetAbsFrameIndex (
+ BlastElementPtr->current.image.farray[0], 6
+ );
+ UnlockElement (hBlastElement);
+ }
+ }
+ }
+}
+
+static void
+samatra_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+}
+
+static void
+samatra_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->RaceDescPtr->num_generators)
+ {
+ if (StarShipPtr->weapon_counter == 0
+ && StarShipPtr->RaceDescPtr->characteristics.weapon_wait < MAX_COMETS
+ && spawn_comet (ElementPtr))
+ {
+ StarShipPtr->weapon_counter = WEAPON_WAIT;
+ }
+
+ if (StarShipPtr->special_counter == 0
+ && StarShipPtr->RaceDescPtr->characteristics.special_wait < MAX_SENTINELS)
+ {
+ MISSILE_BLOCK MissileBlock;
+ HELEMENT hSentinel;
+
+ MissileBlock.cx = ElementPtr->next.location.x;
+ MissileBlock.cy = ElementPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = 0;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ElementPtr->playerNr;
+ MissileBlock.flags = 0;
+ MissileBlock.pixoffs = 0;
+ MissileBlock.speed = SENTINEL_SPEED;
+ MissileBlock.hit_points = SENTINEL_HITS;
+ MissileBlock.damage = SENTINEL_DAMAGE;
+ MissileBlock.life = SENTINEL_LIFE;
+ MissileBlock.preprocess_func = sentinel_preprocess;
+ MissileBlock.blast_offs = SENTINEL_OFFSET;
+ hSentinel = initialize_missile (&MissileBlock);
+
+ if (hSentinel)
+ {
+ ELEMENT *SentinelPtr;
+
+ LockElement (hSentinel, &SentinelPtr);
+ SentinelPtr->collision_func = sentinel_collision;
+ SentinelPtr->turn_wait = TRACK_WAIT + 2;
+ SetElementStarShip (SentinelPtr, StarShipPtr);
+ UnlockElement (hSentinel);
+
+ StarShipPtr->special_counter = SPECIAL_WAIT;
+
+ PutElement (hSentinel);
+ }
+ }
+ }
+}
+
+static void
+samatra_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ StarShipPtr->RaceDescPtr->characteristics.weapon_wait = 0;
+ StarShipPtr->RaceDescPtr->characteristics.special_wait = 0;
+ if (!(ElementPtr->state_flags & APPEARING))
+ {
+ ++ElementPtr->turn_wait;
+ ++ElementPtr->thrust_wait;
+ }
+ else
+ {
+ POINT offs[] =
+ {
+ {-127-9, -53+18},
+ { -38-9, -88+18},
+ { 44-9, -85+18},
+ { 127-9, -60+18},
+ { 124-9, 28+18},
+ { 73-9, 61+18},
+ { -87-9, 58+18},
+ {-136-9, 29+18},
+ };
+
+ for (StarShipPtr->RaceDescPtr->num_generators = 0;
+ StarShipPtr->RaceDescPtr->num_generators < MAX_GENERATORS;
+ ++StarShipPtr->RaceDescPtr->num_generators)
+ {
+ HELEMENT hGenerator;
+
+ hGenerator = AllocElement ();
+ if (hGenerator)
+ {
+ ELEMENT *GeneratorPtr;
+
+ LockElement (hGenerator, &GeneratorPtr);
+ GeneratorPtr->hit_points = GENERATOR_HITS;
+ GeneratorPtr->mass_points = MAX_SHIP_MASS * 10;
+ GeneratorPtr->life_span = NORMAL_LIFE;
+ GeneratorPtr->playerNr = ElementPtr->playerNr;
+ GeneratorPtr->state_flags = APPEARING | IGNORE_SIMILAR;
+ SetPrimType (
+ &GLOBAL (DisplayArray[GeneratorPtr->PrimIndex]),
+ STAMP_PRIM
+ );
+ GeneratorPtr->current.location.x =
+ ((LOG_SPACE_WIDTH >> 1)
+ + DISPLAY_TO_WORLD (offs[StarShipPtr->RaceDescPtr->num_generators].x))
+ & ~((SCALED_ONE << MAX_VIS_REDUCTION) - 1);
+ GeneratorPtr->current.location.y =
+ ((LOG_SPACE_HEIGHT >> 1)
+ + DISPLAY_TO_WORLD (offs[StarShipPtr->RaceDescPtr->num_generators].y))
+ & ~((SCALED_ONE << MAX_VIS_REDUCTION) - 1);
+ GeneratorPtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ GeneratorPtr->current.image.frame =
+ SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.special[0],
+ (BYTE)TFB_Random () % 10
+ );
+
+ GeneratorPtr->preprocess_func = generator_preprocess;
+ GeneratorPtr->collision_func = generator_collision;
+ GeneratorPtr->death_func = generator_death;
+
+ SetElementStarShip (GeneratorPtr, StarShipPtr);
+ UnlockElement (hGenerator);
+
+ InsertElement (hGenerator, GetHeadElement ());
+ }
+ }
+
+ {
+ HELEMENT hTurret;
+
+ hTurret = AllocElement ();
+ if (hTurret)
+ {
+ ELEMENT *TurretPtr;
+
+ LockElement (hTurret, &TurretPtr);
+ TurretPtr->hit_points = 1;
+ TurretPtr->life_span = NORMAL_LIFE;
+ TurretPtr->playerNr = ElementPtr->playerNr;
+ TurretPtr->state_flags = APPEARING | IGNORE_SIMILAR | NONSOLID;
+ SetPrimType (
+ &GLOBAL (DisplayArray[TurretPtr->PrimIndex]),
+ STAMP_PRIM
+ );
+ TurretPtr->current.location.x = LOG_SPACE_WIDTH >> 1;
+ TurretPtr->current.location.y = LOG_SPACE_HEIGHT >> 1;
+ TurretPtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.ship;
+ TurretPtr->current.image.frame =
+ SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship[0], 1
+ );
+
+ TurretPtr->preprocess_func = turret_preprocess;
+
+ SetElementStarShip (TurretPtr, StarShipPtr);
+ UnlockElement (hTurret);
+
+ InsertElement (hTurret, GetSuccElement (ElementPtr));
+ }
+ }
+
+ {
+ HELEMENT hGate;
+
+ hGate = AllocElement ();
+ if (hGate)
+ {
+ ELEMENT *GatePtr;
+
+ LockElement (hGate, &GatePtr);
+ GatePtr->hit_points = GATE_HITS;
+ GatePtr->mass_points = GATE_DAMAGE;
+ GatePtr->life_span = 2;
+ GatePtr->playerNr = ElementPtr->playerNr;
+ GatePtr->state_flags = APPEARING | FINITE_LIFE
+ | IGNORE_SIMILAR;
+ SetPrimType (
+ &GLOBAL (DisplayArray[GatePtr->PrimIndex]),
+ STAMP_PRIM
+ );
+ GatePtr->current.location.x = LOG_SPACE_WIDTH >> 1;
+ GatePtr->current.location.y = LOG_SPACE_HEIGHT >> 1;
+ GatePtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.ship;
+ GatePtr->current.image.frame =
+ SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship[0], 11
+ );
+
+ GatePtr->preprocess_func = gate_preprocess;
+ GatePtr->collision_func = gate_collision;
+
+ SetElementStarShip (GatePtr, StarShipPtr);
+ UnlockElement (hGate);
+
+ InsertElement (hGate, GetSuccElement (ElementPtr));
+ }
+ }
+
+ StarShipPtr->weapon_counter = WEAPON_WAIT >> 1;
+ StarShipPtr->special_counter = SPECIAL_WAIT >> 1;
+ }
+}
+
+RACE_DESC*
+init_samatra (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ samatra_desc.preprocess_func = samatra_preprocess;
+ samatra_desc.postprocess_func = samatra_postprocess;
+ samatra_desc.cyborg_control.intelligence_func = samatra_intelligence;
+
+ RaceDescPtr = &samatra_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/lastbat/lastbat.h b/src/uqm/ships/lastbat/lastbat.h
new file mode 100644
index 0000000..ccda7f1
--- /dev/null
+++ b/src/uqm/ships/lastbat/lastbat.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef LASTBAT_H
+#define LASTBAT_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_samatra (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* LASTBAT_H */
+
diff --git a/src/uqm/ships/lastbat/resinst.h b/src/uqm/ships/lastbat/resinst.h
new file mode 100644
index 0000000..779c00a
--- /dev/null
+++ b/src/uqm/ships/lastbat/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define GENERATOR_BIG_MASK_ANIM "ship.samatra.graphics.generator.large"
+#define GENERATOR_MED_MASK_PMAP_ANIM "ship.samatra.graphics.generator.medium"
+#define GENERATOR_SML_MASK_PMAP_ANIM "ship.samatra.graphics.generator.small"
+#define SAMATRA_BIG_MASK_ANIM "ship.samatra.graphics.samatra.large"
+#define SAMATRA_CAPTAIN_MASK_PMAP_ANIM "ship.samatra.graphics.captain"
+#define SAMATRA_MED_MASK_PMAP_ANIM "ship.samatra.graphics.samatra.medium"
+#define SAMATRA_SHIP_SOUNDS "ship.samatra.sounds"
+#define SAMATRA_SML_MASK_PMAP_ANIM "ship.samatra.graphics.samatra.small"
+#define SAMATRA_VICTORY_SONG "ship.samatra.ditty"
+#define SENTINEL_BIG_MASK_ANIM "ship.samatra.graphics.sentinel.large"
+#define SENTINEL_MED_MASK_PMAP_ANIM "ship.samatra.graphics.sentinel.medium"
+#define SENTINEL_SML_MASK_PMAP_ANIM "ship.samatra.graphics.sentinel.small"
diff --git a/src/uqm/ships/melnorme/Makeinfo b/src/uqm/ships/melnorme/Makeinfo
new file mode 100644
index 0000000..f5bb991
--- /dev/null
+++ b/src/uqm/ships/melnorme/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="melnorme.c"
+uqm_HFILES="icode.h melnorme.h resinst.h"
diff --git a/src/uqm/ships/melnorme/icode.h b/src/uqm/ships/melnorme/icode.h
new file mode 100644
index 0000000..d9dd355
--- /dev/null
+++ b/src/uqm/ships/melnorme/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define MELNORME_CODE "ship.melnorme.code"
diff --git a/src/uqm/ships/melnorme/melnorme.c b/src/uqm/ships/melnorme/melnorme.c
new file mode 100644
index 0000000..8e5ab2b
--- /dev/null
+++ b/src/uqm/ships/melnorme/melnorme.c
@@ -0,0 +1,658 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "melnorme.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 20
+#define MAX_ENERGY MAX_ENERGY_SIZE
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 4
+#define MAX_THRUST 36
+#define THRUST_INCREMENT 6
+#define THRUST_WAIT 4
+#define TURN_WAIT 4
+#define SHIP_MASS 7
+
+// Blaster Pulse
+#define WEAPON_ENERGY_COST 5
+#define WEAPON_WAIT 1
+#define MELNORME_OFFSET 24
+#define LEVEL_COUNTER 72
+#define MAX_PUMP 4
+#define PUMPUP_SPEED DISPLAY_TO_WORLD (45)
+#define PUMPUP_LIFE 10
+#define PUMPUP_DAMAGE 2
+#define MIN_PUMPITUDE_ANIMS 3
+#define NUM_PUMP_ANIMS 5
+#define REVERSE_DIR (BYTE)(1 << 7)
+
+// Confusion Pulse
+#define SPECIAL_ENERGY_COST 20
+#define SPECIAL_WAIT 20
+#define CMISSILE_SPEED DISPLAY_TO_WORLD (30)
+#define CMISSILE_LIFE 20
+#define CMISSILE_HITS 200
+#define CMISSILE_DAMAGE 0
+#define CMISSILE_OFFSET 4
+
+static RACE_DESC melnorme_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 18, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ MELNORME_RACE_STRINGS,
+ MELNORME_ICON_MASK_PMAP_ANIM,
+ MELNORME_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ INFINITE_RADIUS, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ MAX_X_UNIVERSE >> 1, MAX_Y_UNIVERSE >> 1,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ MELNORME_BIG_MASK_PMAP_ANIM,
+ MELNORME_MED_MASK_PMAP_ANIM,
+ MELNORME_SML_MASK_PMAP_ANIM,
+ },
+ {
+ PUMPUP_BIG_MASK_PMAP_ANIM,
+ PUMPUP_MED_MASK_PMAP_ANIM,
+ PUMPUP_SML_MASK_PMAP_ANIM,
+ },
+ {
+ CONFUSE_BIG_MASK_PMAP_ANIM,
+ CONFUSE_MED_MASK_PMAP_ANIM,
+ CONFUSE_SML_MASK_PMAP_ANIM,
+ },
+ {
+ MELNORME_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ MELNORME_VICTORY_SONG,
+ MELNORME_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ PUMPUP_SPEED * PUMPUP_LIFE,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+pump_up_preprocess (ELEMENT *ElementPtr)
+{
+ if (--ElementPtr->thrust_wait & 1)
+ {
+ COUNT frame_index;
+
+ frame_index = GetFrameIndex (ElementPtr->current.image.frame);
+ if (((ElementPtr->turn_wait & REVERSE_DIR)
+ && (frame_index % NUM_PUMP_ANIMS) != 0)
+ || (!(ElementPtr->turn_wait & REVERSE_DIR)
+ && ((frame_index + 1) % NUM_PUMP_ANIMS) == 0))
+ {
+ --frame_index;
+ ElementPtr->turn_wait |= REVERSE_DIR;
+ }
+ else
+ {
+ ++frame_index;
+ ElementPtr->turn_wait &= ~REVERSE_DIR;
+ }
+
+ ElementPtr->next.image.frame = SetAbsFrameIndex (
+ ElementPtr->current.image.frame, frame_index);
+
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+static COUNT initialize_pump_up (ELEMENT *ShipPtr, HELEMENT PumpUpArray[]);
+
+static void
+pump_up_postprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ }
+ else
+ {
+ HELEMENT hPumpUp;
+ ELEMENT *EPtr;
+ ELEMENT *ShipPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ initialize_pump_up (ShipPtr, &hPumpUp);
+ DeltaEnergy (ShipPtr, 0);
+ UnlockElement (StarShipPtr->hShip);
+
+ LockElement (hPumpUp, &EPtr);
+
+ EPtr->current.image.frame = ElementPtr->current.image.frame;
+ EPtr->turn_wait = ElementPtr->turn_wait;
+ EPtr->thrust_wait = ElementPtr->thrust_wait;
+ if (--EPtr->thrust_wait == 0)
+ {
+ if ((EPtr->turn_wait & ~REVERSE_DIR) < MAX_PUMP - 1)
+ {
+ ++EPtr->turn_wait;
+ EPtr->current.image.frame = SetRelFrameIndex (
+ EPtr->current.image.frame, NUM_PUMP_ANIMS);
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2),
+ EPtr);
+ }
+ EPtr->thrust_wait = LEVEL_COUNTER;
+ }
+
+ EPtr->mass_points = EPtr->hit_points =
+ (PUMPUP_DAMAGE << (ElementPtr->turn_wait & ~REVERSE_DIR));
+ SetElementStarShip (EPtr, StarShipPtr);
+
+ if (EPtr->thrust_wait & 1)
+ {
+ COUNT frame_index;
+
+ frame_index = GetFrameIndex (EPtr->current.image.frame);
+ if (((EPtr->turn_wait & REVERSE_DIR)
+ && (frame_index % NUM_PUMP_ANIMS) != 0)
+ || (!(EPtr->turn_wait & REVERSE_DIR)
+ && ((frame_index + 1) % NUM_PUMP_ANIMS) == 0))
+ {
+ --frame_index;
+ EPtr->turn_wait |= REVERSE_DIR;
+ }
+ else
+ {
+ ++frame_index;
+ EPtr->turn_wait &= ~REVERSE_DIR;
+ }
+
+ EPtr->current.image.frame = SetAbsFrameIndex (
+ EPtr->current.image.frame, frame_index);
+ }
+
+ if (StarShipPtr->cur_status_flags & StarShipPtr->old_status_flags
+ & WEAPON)
+ {
+ StarShipPtr->weapon_counter = WEAPON_WAIT;
+ }
+ else
+ {
+ COUNT angle;
+
+ EPtr->life_span = PUMPUP_LIFE;
+ EPtr->preprocess_func = pump_up_preprocess;
+ EPtr->postprocess_func = 0;
+
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+ SetVelocityComponents (&EPtr->velocity,
+ COSINE (angle, WORLD_TO_VELOCITY (PUMPUP_SPEED)),
+ SINE (angle, WORLD_TO_VELOCITY (PUMPUP_SPEED)));
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 3), EPtr);
+ }
+
+ UnlockElement (hPumpUp);
+ PutElement (hPumpUp);
+
+ SetPrimType (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ NO_PRIM);
+ ElementPtr->state_flags |= NONSOLID;
+ }
+}
+
+static void
+animate (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = ElementPtr->next_turn;
+ }
+}
+
+static void
+pump_up_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ RECT r;
+ BYTE old_thrust_wait;
+ HELEMENT hBlastElement;
+
+ GetFrameRect (ElementPtr0->next.image.frame, &r);
+
+ old_thrust_wait = ElementPtr0->thrust_wait;
+ ElementPtr0->blast_offset = r.extent.width >> 1;
+ hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ ElementPtr0->thrust_wait = old_thrust_wait;
+
+ if (hBlastElement)
+ {
+ ELEMENT *BlastElementPtr;
+
+ LockElement (hBlastElement, &BlastElementPtr);
+
+ BlastElementPtr->life_span =
+ MIN_PUMPITUDE_ANIMS
+ + (ElementPtr0->turn_wait & ~REVERSE_DIR);
+ BlastElementPtr->turn_wait = BlastElementPtr->next_turn = 0;
+ {
+ BlastElementPtr->preprocess_func = animate;
+ }
+
+ BlastElementPtr->current.image.farray = ElementPtr0->next.image.farray;
+ BlastElementPtr->current.image.frame =
+ SetAbsFrameIndex (BlastElementPtr->current.image.farray[0],
+ MAX_PUMP * NUM_PUMP_ANIMS);
+
+ UnlockElement (hBlastElement);
+ }
+}
+
+static COUNT
+initialize_pump_up (ELEMENT *ShipPtr, HELEMENT PumpUpArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = MELNORME_OFFSET;
+ MissileBlock.speed = DISPLAY_TO_WORLD (MELNORME_OFFSET);
+ MissileBlock.hit_points = PUMPUP_DAMAGE;
+ MissileBlock.damage = PUMPUP_DAMAGE;
+ MissileBlock.life = 2;
+ MissileBlock.preprocess_func = 0;
+ MissileBlock.blast_offs = 0;
+ PumpUpArray[0] = initialize_missile (&MissileBlock);
+
+ if (PumpUpArray[0])
+ {
+ ELEMENT *PumpUpPtr;
+
+ LockElement (PumpUpArray[0], &PumpUpPtr);
+ PumpUpPtr->postprocess_func = pump_up_postprocess;
+ PumpUpPtr->collision_func = pump_up_collision;
+ PumpUpPtr->thrust_wait = LEVEL_COUNTER;
+ UnlockElement (PumpUpArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+confuse_preprocess (ELEMENT *ElementPtr)
+{
+ if (!(ElementPtr->state_flags & NONSOLID))
+ {
+ ElementPtr->next.image.frame = SetAbsFrameIndex (
+ ElementPtr->current.image.frame,
+ (GetFrameIndex (ElementPtr->current.image.frame) + 1) & 7);
+ ElementPtr->state_flags |= CHANGING;
+ }
+ else if (ElementPtr->hTarget == 0)
+ {
+ ElementPtr->life_span = 0;
+ ElementPtr->state_flags |= DISAPPEARING;
+ }
+ else
+ {
+ ELEMENT *eptr;
+
+ LockElement (ElementPtr->hTarget, &eptr);
+
+ ElementPtr->next.location = eptr->next.location;
+
+ if (ElementPtr->turn_wait)
+ {
+ HELEMENT hEffect;
+ STARSHIP *StarShipPtr;
+
+ if (GetFrameIndex (ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame)) == 0)
+ ElementPtr->next.image.frame =
+ SetRelFrameIndex (ElementPtr->next.image.frame, -8);
+
+ GetElementStarShip (eptr, &StarShipPtr);
+ StarShipPtr->ship_input_state =
+ (StarShipPtr->ship_input_state
+ & ~(LEFT | RIGHT | SPECIAL))
+ | ElementPtr->turn_wait;
+
+ hEffect = AllocElement ();
+ if (hEffect)
+ {
+ LockElement (hEffect, &eptr);
+ eptr->playerNr = ElementPtr->playerNr;
+ eptr->state_flags = FINITE_LIFE | NONSOLID | CHANGING;
+ eptr->life_span = 1;
+ eptr->current = eptr->next = ElementPtr->next;
+ eptr->preprocess_func = confuse_preprocess;
+ SetPrimType (&(GLOBAL (DisplayArray))[eptr->PrimIndex],
+ STAMP_PRIM);
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ SetElementStarShip (eptr, StarShipPtr);
+ eptr->hTarget = ElementPtr->hTarget;
+
+ UnlockElement (hEffect);
+ PutElement (hEffect);
+ }
+ }
+
+ UnlockElement (ElementPtr->hTarget);
+ }
+}
+
+static void
+confusion_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (ElementPtr1->state_flags & PLAYER_SHIP)
+ {
+ HELEMENT hConfusionElement, hNextElement;
+ ELEMENT *ConfusionPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ for (hConfusionElement = GetHeadElement ();
+ hConfusionElement; hConfusionElement = hNextElement)
+ {
+ LockElement (hConfusionElement, &ConfusionPtr);
+ if (elementsOfSamePlayer (ConfusionPtr, ElementPtr0)
+ && ConfusionPtr->current.image.farray ==
+ StarShipPtr->RaceDescPtr->ship_data.special
+ && (ConfusionPtr->state_flags & NONSOLID))
+ {
+ UnlockElement (hConfusionElement);
+ break;
+ }
+ hNextElement = GetSuccElement (ConfusionPtr);
+ UnlockElement (hConfusionElement);
+ }
+
+ if (hConfusionElement || (hConfusionElement = AllocElement ()))
+ {
+ LockElement (hConfusionElement, &ConfusionPtr);
+
+ if (ConfusionPtr->state_flags == 0) /* not allocated before */
+ {
+ InsertElement (hConfusionElement, GetHeadElement ());
+
+ ConfusionPtr->current = ElementPtr0->next;
+ ConfusionPtr->current.image.frame = SetAbsFrameIndex (
+ ConfusionPtr->current.image.frame, 8
+ );
+ ConfusionPtr->next = ConfusionPtr->current;
+ ConfusionPtr->playerNr = ElementPtr0->playerNr;
+ ConfusionPtr->state_flags = FINITE_LIFE | NONSOLID | CHANGING;
+ ConfusionPtr->preprocess_func = confuse_preprocess;
+ SetPrimType (
+ &(GLOBAL (DisplayArray))[ConfusionPtr->PrimIndex],
+ NO_PRIM
+ );
+
+ SetElementStarShip (ConfusionPtr, StarShipPtr);
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ ConfusionPtr->hTarget = StarShipPtr->hShip;
+ }
+
+ ConfusionPtr->life_span = 400;
+ ConfusionPtr->turn_wait =
+ (BYTE)(1 << ((BYTE)TFB_Random () & 1)); /* LEFT or RIGHT */
+
+ UnlockElement (hConfusionElement);
+ }
+
+ ElementPtr0->hit_points = 0;
+ ElementPtr0->life_span = 0;
+ ElementPtr0->state_flags |= DISAPPEARING | COLLISION | NONSOLID;
+ }
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static COUNT
+initialize_confusion (ELEMENT *ShipPtr, HELEMENT ConfusionArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK ConfusionBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ ConfusionBlock.cx = ShipPtr->next.location.x;
+ ConfusionBlock.cy = ShipPtr->next.location.y;
+ ConfusionBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ ConfusionBlock.index = 0;
+ ConfusionBlock.face = StarShipPtr->ShipFacing;
+ ConfusionBlock.sender = ShipPtr->playerNr;
+ ConfusionBlock.flags = IGNORE_SIMILAR;
+ ConfusionBlock.pixoffs = MELNORME_OFFSET;
+ ConfusionBlock.speed = CMISSILE_SPEED;
+ ConfusionBlock.hit_points = CMISSILE_HITS;
+ ConfusionBlock.damage = CMISSILE_DAMAGE;
+ ConfusionBlock.life = CMISSILE_LIFE;
+ ConfusionBlock.preprocess_func = confuse_preprocess;
+ ConfusionBlock.blast_offs = CMISSILE_OFFSET;
+ ConfusionArray[0] = initialize_missile (&ConfusionBlock);
+
+ if (ConfusionArray[0])
+ {
+ ELEMENT *CMissilePtr;
+
+ LockElement (ConfusionArray[0], &CMissilePtr);
+ CMissilePtr->collision_func = confusion_collision;
+ SetElementStarShip (CMissilePtr, StarShipPtr);
+ UnlockElement (ConfusionArray[0]);
+ }
+ return (1);
+}
+
+static COUNT
+initialize_test_pump_up (ELEMENT *ShipPtr, HELEMENT PumpUpArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+ //ELEMENT *PumpUpPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = MELNORME_OFFSET;
+ MissileBlock.speed = PUMPUP_SPEED;
+ MissileBlock.hit_points = PUMPUP_DAMAGE;
+ MissileBlock.damage = PUMPUP_DAMAGE;
+ MissileBlock.life = PUMPUP_LIFE;
+ MissileBlock.preprocess_func = 0;
+ MissileBlock.blast_offs = 0;
+ PumpUpArray[0] = initialize_missile (&MissileBlock);
+
+ return (1);
+}
+
+static void
+melnorme_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ BYTE old_count;
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ StarShipPtr->RaceDescPtr->init_weapon_func = initialize_test_pump_up;
+ old_count = StarShipPtr->weapon_counter;
+
+ if (StarShipPtr->weapon_counter == WEAPON_WAIT)
+ StarShipPtr->weapon_counter = 0;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEvalDesc->ObjectPtr)
+ {
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level < SPECIAL_ENERGY_COST
+ + WEAPON_ENERGY_COST
+ && !(StarShipPtr->old_status_flags & WEAPON))
+ lpEvalDesc->MoveState = ENTICE;
+ else
+ {
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if (!(EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags
+ & IMMEDIATE_WEAPON))
+ lpEvalDesc->MoveState = PURSUE;
+ }
+ }
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ if (StarShipPtr->weapon_counter == 0
+ && (old_count != 0
+ || ((StarShipPtr->special_counter
+ || StarShipPtr->RaceDescPtr->ship_info.energy_level >= SPECIAL_ENERGY_COST
+ + WEAPON_ENERGY_COST)
+ && !(StarShipPtr->ship_input_state & WEAPON))))
+ StarShipPtr->ship_input_state ^= WEAPON;
+
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (StarShipPtr->special_counter == 0
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >= SPECIAL_ENERGY_COST)
+ {
+ BYTE old_input_state;
+
+ old_input_state = StarShipPtr->ship_input_state;
+
+ StarShipPtr->RaceDescPtr->init_weapon_func = initialize_confusion;
+
+ ++ShipPtr->turn_wait;
+ ++ShipPtr->thrust_wait;
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ENEMY_SHIP_INDEX + 1);
+ --ShipPtr->thrust_wait;
+ --ShipPtr->turn_wait;
+
+ if (StarShipPtr->ship_input_state & WEAPON)
+ {
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+
+ StarShipPtr->ship_input_state = (unsigned char)(old_input_state
+ | (StarShipPtr->ship_input_state & SPECIAL));
+ }
+
+ StarShipPtr->weapon_counter = old_count;
+
+ StarShipPtr->RaceDescPtr->init_weapon_func = initialize_pump_up;
+}
+
+static void
+melnorme_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ HELEMENT Confusion;
+
+ initialize_confusion (ElementPtr, &Confusion);
+ if (Confusion)
+ {
+ ELEMENT *CMissilePtr;
+ LockElement (Confusion, &CMissilePtr);
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), CMissilePtr);
+
+ UnlockElement (Confusion);
+ PutElement (Confusion);
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+ }
+}
+
+RACE_DESC*
+init_melnorme (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ melnorme_desc.postprocess_func = melnorme_postprocess;
+ melnorme_desc.init_weapon_func = initialize_pump_up;
+ melnorme_desc.cyborg_control.intelligence_func = melnorme_intelligence;
+
+ RaceDescPtr = &melnorme_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/melnorme/melnorme.h b/src/uqm/ships/melnorme/melnorme.h
new file mode 100644
index 0000000..287ec76
--- /dev/null
+++ b/src/uqm/ships/melnorme/melnorme.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MELNORME_H
+#define MELNORME_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_melnorme (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* MELNORME_H */
+
diff --git a/src/uqm/ships/melnorme/resinst.h b/src/uqm/ships/melnorme/resinst.h
new file mode 100644
index 0000000..01b93df
--- /dev/null
+++ b/src/uqm/ships/melnorme/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define CONFUSE_BIG_MASK_PMAP_ANIM "ship.melnorme.graphics.confuse.large"
+#define CONFUSE_MED_MASK_PMAP_ANIM "ship.melnorme.graphics.confuse.medium"
+#define CONFUSE_SML_MASK_PMAP_ANIM "ship.melnorme.graphics.confuse.small"
+#define MELNORME_BIG_MASK_PMAP_ANIM "ship.melnorme.graphics.trader.large"
+#define MELNORME_CAPTAIN_MASK_PMAP_ANIM "ship.melnorme.graphics.captain"
+#define MELNORME_ICON_MASK_PMAP_ANIM "ship.melnorme.icons"
+#define MELNORME_MED_MASK_PMAP_ANIM "ship.melnorme.graphics.trader.medium"
+#define MELNORME_MICON_MASK_PMAP_ANIM "ship.melnorme.meleeicons"
+#define MELNORME_RACE_STRINGS "ship.melnorme.text"
+#define MELNORME_SHIP_SOUNDS "ship.melnorme.sounds"
+#define MELNORME_SML_MASK_PMAP_ANIM "ship.melnorme.graphics.trader.small"
+#define MELNORME_VICTORY_SONG "ship.melnorme.ditty"
+#define PUMPUP_BIG_MASK_PMAP_ANIM "ship.melnorme.graphics.pumpup.large"
+#define PUMPUP_MED_MASK_PMAP_ANIM "ship.melnorme.graphics.pumpup.medium"
+#define PUMPUP_SML_MASK_PMAP_ANIM "ship.melnorme.graphics.pumpup.small"
diff --git a/src/uqm/ships/mmrnmhrm/Makeinfo b/src/uqm/ships/mmrnmhrm/Makeinfo
new file mode 100644
index 0000000..0c86637
--- /dev/null
+++ b/src/uqm/ships/mmrnmhrm/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="mmrnmhrm.c"
+uqm_HFILES="icode.h mmrnmhrm.h resinst.h"
diff --git a/src/uqm/ships/mmrnmhrm/icode.h b/src/uqm/ships/mmrnmhrm/icode.h
new file mode 100644
index 0000000..ba3f593
--- /dev/null
+++ b/src/uqm/ships/mmrnmhrm/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define MMRNMHRM_CODE "ship.mmrnmhrm.code"
diff --git a/src/uqm/ships/mmrnmhrm/mmrnmhrm.c b/src/uqm/ships/mmrnmhrm/mmrnmhrm.c
new file mode 100644
index 0000000..e8f8348
--- /dev/null
+++ b/src/uqm/ships/mmrnmhrm/mmrnmhrm.c
@@ -0,0 +1,527 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "mmrnmhrm.h"
+#include "resinst.h"
+
+// Core characteristics
+#define MAX_CREW 20
+#define MAX_ENERGY 10
+#define SHIP_MASS 3
+
+// X-Wing characteristics
+#define ENERGY_REGENERATION 2
+#define ENERGY_WAIT 6
+#define MAX_THRUST 20
+#define THRUST_INCREMENT 5
+#define THRUST_WAIT 1
+#define TURN_WAIT 2
+
+// Y-Wing characteristics
+#define YWING_ENERGY_REGENERATION 1
+#define YWING_SPECIAL_ENERGY_COST MAX_ENERGY
+#define YWING_ENERGY_WAIT 6
+#define YWING_MAX_THRUST 50
+#define YWING_THRUST_INCREMENT 10
+#define YWING_THRUST_WAIT 0
+#define YWING_TURN_WAIT 14
+
+// X-Wing Lasers
+#define MMRNMHRM_OFFSET 16
+#define WEAPON_ENERGY_COST 1
+#define WEAPON_WAIT 0
+#define CENTER_OFFS DISPLAY_TO_WORLD (4)
+#define WING_OFFS DISPLAY_TO_WORLD (10)
+#define LASER_RANGE DISPLAY_TO_WORLD (125 + MMRNMHRM_OFFSET)
+
+// Y-Wing Missiles
+#define YWING_WEAPON_ENERGY_COST 1
+#define YWING_WEAPON_WAIT 20
+#define LAUNCH_OFFS DISPLAY_TO_WORLD (4)
+#define MISSILE_OFFSET 0
+#define MISSILE_SPEED DISPLAY_TO_WORLD (20)
+#define MISSILE_LIFE 40
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 1
+#define TRACK_WAIT 5
+
+// Transform
+#define SPECIAL_ENERGY_COST MAX_ENERGY
+#define SPECIAL_WAIT 0
+#define YWING_SPECIAL_WAIT 0
+
+static RACE_DESC mmrnmhrm_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | IMMEDIATE_WEAPON,
+ 19, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ MMRNMHRM_RACE_STRINGS,
+ MMRNMHRM_ICON_MASK_PMAP_ANIM,
+ MMRNMHRM_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 0, 0,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ MMRNMHRM_BIG_MASK_PMAP_ANIM,
+ MMRNMHRM_MED_MASK_PMAP_ANIM,
+ MMRNMHRM_SML_MASK_PMAP_ANIM,
+ },
+ {
+ TORP_BIG_MASK_PMAP_ANIM,
+ TORP_MED_MASK_PMAP_ANIM,
+ TORP_SML_MASK_PMAP_ANIM,
+ },
+ {
+ YWING_BIG_MASK_PMAP_ANIM,
+ YWING_MED_MASK_PMAP_ANIM,
+ YWING_SML_MASK_PMAP_ANIM,
+ },
+ {
+ MMRNMHRM_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ MMRNMHRM_VICTORY_SONG,
+ MMRNMHRM_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ CLOSE_RANGE_WEAPON,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+// Private per-instance ship data
+typedef CHARACTERISTIC_STUFF MMRNMHRM_DATA;
+
+// Local typedef
+typedef MMRNMHRM_DATA CustomShipData_t;
+
+// Retrieve race-specific ship data from a race desc
+static CustomShipData_t *
+GetCustomShipData (RACE_DESC *pRaceDesc)
+{
+ return pRaceDesc->data;
+}
+
+// Set the race-specific data in a race desc
+// (Re)Allocates its own storage for the data.
+static void
+SetCustomShipData (RACE_DESC *pRaceDesc, const CustomShipData_t *data)
+{
+ if (pRaceDesc->data == data)
+ return; // no-op
+
+ if (pRaceDesc->data) // Out with the old
+ {
+ HFree (pRaceDesc->data);
+ pRaceDesc->data = NULL;
+ }
+
+ if (data) // In with the new
+ {
+ CustomShipData_t* newData = HMalloc (sizeof (*data));
+ *newData = *data;
+ pRaceDesc->data = newData;
+ }
+}
+
+static void
+missile_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ COUNT facing;
+
+ facing = GetFrameIndex (ElementPtr->next.image.frame);
+ if (TrackShip (ElementPtr, &facing) > 0)
+ {
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->next.image.frame,
+ facing);
+ ElementPtr->state_flags |= CHANGING;
+
+ SetVelocityVector (&ElementPtr->velocity,
+ MISSILE_SPEED, facing);
+ }
+
+ ElementPtr->turn_wait = TRACK_WAIT;
+ }
+}
+
+static void
+mmrnmhrm_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ BOOLEAN CanTransform;
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+ STARSHIP *EnemyStarShipPtr = NULL;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ CanTransform = (BOOLEAN)(StarShipPtr->special_counter == 0
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >=
+ StarShipPtr->RaceDescPtr->characteristics.special_energy_cost);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEvalDesc->ObjectPtr)
+ {
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ }
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (CanTransform
+ && lpEvalDesc->ObjectPtr
+ && !(StarShipPtr->ship_input_state & WEAPON))
+ {
+ SIZE delta_x, delta_y;
+ COUNT travel_angle, direction_angle;
+
+ GetCurrentVelocityComponents (&lpEvalDesc->ObjectPtr->velocity,
+ &delta_x, &delta_y);
+ if (delta_x == 0 && delta_y == 0)
+ direction_angle = travel_angle = 0;
+ else
+ {
+ delta_x = lpEvalDesc->ObjectPtr->current.location.x
+ - ShipPtr->current.location.x;
+ delta_y = lpEvalDesc->ObjectPtr->current.location.y
+ - ShipPtr->current.location.y;
+ direction_angle = ARCTAN (-delta_x, -delta_y);
+ travel_angle = GetVelocityTravelAngle (
+ &lpEvalDesc->ObjectPtr->velocity
+ );
+ }
+
+ if (ShipPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.ship)
+ {
+ if (lpEvalDesc->which_turn > 8)
+ {
+ if (MANEUVERABILITY (&EnemyStarShipPtr->RaceDescPtr->cyborg_control) <= SLOW_SHIP
+ || NORMALIZE_ANGLE (
+ direction_angle - travel_angle + QUADRANT
+ ) > HALF_CIRCLE)
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ }
+ else
+ {
+ SIZE ship_delta_x, ship_delta_y;
+
+ GetCurrentVelocityComponents (&ShipPtr->velocity,
+ &ship_delta_x, &ship_delta_y);
+ delta_x -= ship_delta_x;
+ delta_y -= ship_delta_y;
+ travel_angle = ARCTAN (delta_x, delta_y);
+ if (lpEvalDesc->which_turn < 16)
+ {
+ if (lpEvalDesc->which_turn <= 8
+ || NORMALIZE_ANGLE (
+ direction_angle - travel_angle + OCTANT
+ ) <= QUADRANT)
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ else if (lpEvalDesc->which_turn > 32
+ && NORMALIZE_ANGLE (
+ direction_angle - travel_angle + QUADRANT
+ ) > HALF_CIRCLE)
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ }
+
+ if (ShipPtr->current.image.farray == StarShipPtr->RaceDescPtr->ship_data.special)
+ {
+ if (!(StarShipPtr->ship_input_state & SPECIAL)
+ && lpEvalDesc->ObjectPtr)
+ StarShipPtr->ship_input_state |= WEAPON;
+ else
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ }
+}
+
+static void
+twin_laser_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (!(ElementPtr1->state_flags & PLAYER_SHIP)
+ || !elementsOfSamePlayer (ElementPtr0, ElementPtr1))
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+}
+
+static COUNT
+initialize_dual_weapons (ELEMENT *ShipPtr, HELEMENT WeaponArray[])
+{
+ COORD cx, cy;
+ COUNT facing, angle;
+ SIZE offs_x, offs_y;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ facing = StarShipPtr->ShipFacing;
+ angle = FACING_TO_ANGLE (facing);
+ cx = ShipPtr->next.location.x + COSINE (angle, CENTER_OFFS);
+ cy = ShipPtr->next.location.y + SINE (angle, CENTER_OFFS);
+
+ if (ShipPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.ship)
+ {
+ COORD ex, ey;
+ LASER_BLOCK LaserBlock;
+ ELEMENT *LaserPtr;
+
+ LaserBlock.sender = ShipPtr->playerNr;
+ LaserBlock.flags = 0;
+ LaserBlock.pixoffs = 0;
+ LaserBlock.color = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C);
+ LaserBlock.face = facing;
+
+ ex = cx + COSINE (angle, LASER_RANGE);
+ ey = cy + SINE (angle, LASER_RANGE);
+ offs_x = -SINE (angle, WING_OFFS);
+ offs_y = COSINE (angle, WING_OFFS);
+
+ LaserBlock.cx = cx + offs_x;
+ LaserBlock.cy = cy + offs_y;
+ LaserBlock.ex = ex - LaserBlock.cx;
+ LaserBlock.ey = ey - LaserBlock.cy;
+ if ((WeaponArray[0] = initialize_laser (&LaserBlock)))
+ {
+ LockElement (WeaponArray[0], &LaserPtr);
+ LaserPtr->collision_func = twin_laser_collision;
+ UnlockElement (WeaponArray[0]);
+ }
+
+ LaserBlock.cx = cx - offs_x;
+ LaserBlock.cy = cy - offs_y;
+ LaserBlock.ex = ex - LaserBlock.cx;
+ LaserBlock.ey = ey - LaserBlock.cy;
+ if ((WeaponArray[1] = initialize_laser (&LaserBlock)))
+ {
+ LockElement (WeaponArray[1], &LaserPtr);
+ LaserPtr->collision_func = twin_laser_collision;
+ UnlockElement (WeaponArray[1]);
+ }
+ }
+ else
+ {
+ MISSILE_BLOCK TorpBlock;
+ ELEMENT *TorpPtr;
+
+ TorpBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ TorpBlock.sender = ShipPtr->playerNr;
+ TorpBlock.flags = IGNORE_SIMILAR;
+ TorpBlock.pixoffs = 0;
+ TorpBlock.speed = MISSILE_SPEED;
+ TorpBlock.hit_points = MISSILE_HITS;
+ TorpBlock.damage = MISSILE_DAMAGE;
+ TorpBlock.life = MISSILE_LIFE;
+ TorpBlock.preprocess_func = missile_preprocess;
+ TorpBlock.blast_offs = MISSILE_OFFSET;
+
+ TorpBlock.face = TorpBlock.index = NORMALIZE_FACING (facing - 1);
+ offs_x = -SINE (FACING_TO_ANGLE (TorpBlock.face), LAUNCH_OFFS);
+ offs_y = COSINE (FACING_TO_ANGLE (TorpBlock.face), LAUNCH_OFFS);
+
+ TorpBlock.cx = cx + offs_x;
+ TorpBlock.cy = cy + offs_y;
+ if ((WeaponArray[0] = initialize_missile (&TorpBlock)))
+ {
+ LockElement (WeaponArray[0], &TorpPtr);
+ TorpPtr->turn_wait = TRACK_WAIT;
+ UnlockElement (WeaponArray[0]);
+ }
+
+ TorpBlock.face = TorpBlock.index = NORMALIZE_FACING (facing + 1);
+
+ TorpBlock.cx = cx - offs_x;
+ TorpBlock.cy = cy - offs_y;
+ if ((WeaponArray[1] = initialize_missile (&TorpBlock)))
+ {
+ LockElement (WeaponArray[1], &TorpPtr);
+ TorpPtr->turn_wait = TRACK_WAIT;
+ UnlockElement (WeaponArray[1]);
+ }
+ }
+
+ return (2);
+}
+
+static void
+mmrnmhrm_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ /* take care of transform effect */
+ if (ElementPtr->next.image.farray != ElementPtr->current.image.farray)
+ {
+ MMRNMHRM_DATA tempShipData;
+ MMRNMHRM_DATA *otherwing_desc;
+
+ ProcessSound (SetAbsSoundIndex (
+ /* TRANSFORM */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+
+ StarShipPtr->weapon_counter = 0;
+
+ /* Swap characteristics descriptors around */
+ otherwing_desc = GetCustomShipData (StarShipPtr->RaceDescPtr);
+ if (!otherwing_desc)
+ return; // No ship data (?!)
+
+ tempShipData = *otherwing_desc;
+ SetCustomShipData (StarShipPtr->RaceDescPtr, &StarShipPtr->RaceDescPtr->characteristics);
+ StarShipPtr->RaceDescPtr->characteristics = tempShipData;
+ StarShipPtr->RaceDescPtr->cyborg_control.ManeuverabilityIndex = 0;
+
+ if (ElementPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.special)
+ {
+ StarShipPtr->RaceDescPtr->cyborg_control.WeaponRange = LONG_RANGE_WEAPON - 1;
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags &= ~IMMEDIATE_WEAPON;
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags |= SEEKING_WEAPON;
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds =
+ SetAbsSoundIndex (StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2);
+
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+ }
+ else
+ {
+ StarShipPtr->RaceDescPtr->cyborg_control.WeaponRange = CLOSE_RANGE_WEAPON;
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags &= ~SEEKING_WEAPON;
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags |= IMMEDIATE_WEAPON;
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds =
+ SetAbsSoundIndex (StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 0);
+
+ if (StarShipPtr->cur_status_flags
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED))
+ StarShipPtr->cur_status_flags |=
+ SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED;
+ }
+ }
+}
+
+static void
+mmrnmhrm_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ if (!(ElementPtr->state_flags & APPEARING))
+ {
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0)
+ {
+ /* Either we transform or text will flash */
+ if (DeltaEnergy (ElementPtr,
+ -StarShipPtr->RaceDescPtr->characteristics.special_energy_cost))
+ {
+ if (ElementPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.ship)
+ ElementPtr->next.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ else
+ ElementPtr->next.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.ship;
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (ElementPtr->next.image.farray[0],
+ ElementPtr->next.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+ }
+ }
+}
+
+static void
+uninit_mmrnmhrm (RACE_DESC *pRaceDesc)
+{
+ SetCustomShipData (pRaceDesc, NULL);
+}
+
+RACE_DESC*
+init_mmrnmhrm (void)
+{
+ RACE_DESC *RaceDescPtr;
+ // The caller of this func will copy the struct
+ static RACE_DESC new_mmrnmhrm_desc;
+ MMRNMHRM_DATA otherwing_desc;
+
+ mmrnmhrm_desc.uninit_func = uninit_mmrnmhrm;
+ mmrnmhrm_desc.preprocess_func = mmrnmhrm_preprocess;
+ mmrnmhrm_desc.postprocess_func = mmrnmhrm_postprocess;
+ mmrnmhrm_desc.init_weapon_func = initialize_dual_weapons;
+ mmrnmhrm_desc.cyborg_control.intelligence_func = mmrnmhrm_intelligence;
+
+ new_mmrnmhrm_desc = mmrnmhrm_desc;
+
+ otherwing_desc.max_thrust = YWING_MAX_THRUST;
+ otherwing_desc.thrust_increment = YWING_THRUST_INCREMENT;
+ otherwing_desc.energy_regeneration = YWING_ENERGY_REGENERATION;
+ otherwing_desc.weapon_energy_cost = YWING_WEAPON_ENERGY_COST;
+ otherwing_desc.special_energy_cost = YWING_SPECIAL_ENERGY_COST;
+ otherwing_desc.energy_wait = YWING_ENERGY_WAIT;
+ otherwing_desc.turn_wait = YWING_TURN_WAIT;
+ otherwing_desc.thrust_wait = YWING_THRUST_WAIT;
+ otherwing_desc.weapon_wait = YWING_WEAPON_WAIT;
+ otherwing_desc.special_wait = YWING_SPECIAL_WAIT;
+ otherwing_desc.ship_mass = SHIP_MASS;
+
+ SetCustomShipData (&new_mmrnmhrm_desc, &otherwing_desc);
+
+ RaceDescPtr = &new_mmrnmhrm_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/mmrnmhrm/mmrnmhrm.h b/src/uqm/ships/mmrnmhrm/mmrnmhrm.h
new file mode 100644
index 0000000..c2c8512
--- /dev/null
+++ b/src/uqm/ships/mmrnmhrm/mmrnmhrm.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MMRNMHRM_H
+#define MMRNMHRM_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_mmrnmhrm (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* MMRNMHRM_H */
+
diff --git a/src/uqm/ships/mmrnmhrm/resinst.h b/src/uqm/ships/mmrnmhrm/resinst.h
new file mode 100644
index 0000000..f44285f
--- /dev/null
+++ b/src/uqm/ships/mmrnmhrm/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define MMRNMHRM_BIG_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.xform.large"
+#define MMRNMHRM_CAPTAIN_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.captain"
+#define MMRNMHRM_ICON_MASK_PMAP_ANIM "ship.mmrnmhrm.icons"
+#define MMRNMHRM_MED_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.xform.medium"
+#define MMRNMHRM_MICON_MASK_PMAP_ANIM "ship.mmrnmhrm.meleeicons"
+#define MMRNMHRM_RACE_STRINGS "ship.mmrnmhrm.text"
+#define MMRNMHRM_SHIP_SOUNDS "ship.mmrnmhrm.sounds"
+#define MMRNMHRM_SML_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.xform.small"
+#define MMRNMHRM_VICTORY_SONG "ship.mmrnmhrm.ditty"
+#define TORP_BIG_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.torpedo.large"
+#define TORP_MED_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.torpedo.medium"
+#define TORP_SML_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.torpedo.small"
+#define YWING_BIG_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.ywing.large"
+#define YWING_MED_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.ywing.medium"
+#define YWING_SML_MASK_PMAP_ANIM "ship.mmrnmhrm.graphics.ywing.small"
diff --git a/src/uqm/ships/mycon/Makeinfo b/src/uqm/ships/mycon/Makeinfo
new file mode 100644
index 0000000..0ba8988
--- /dev/null
+++ b/src/uqm/ships/mycon/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="mycon.c"
+uqm_HFILES="icode.h mycon.h resinst.h"
diff --git a/src/uqm/ships/mycon/icode.h b/src/uqm/ships/mycon/icode.h
new file mode 100644
index 0000000..b3caa58
--- /dev/null
+++ b/src/uqm/ships/mycon/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define MYCON_CODE "ship.mycon.code"
diff --git a/src/uqm/ships/mycon/mycon.c b/src/uqm/ships/mycon/mycon.c
new file mode 100644
index 0000000..8c99fbe
--- /dev/null
+++ b/src/uqm/ships/mycon/mycon.c
@@ -0,0 +1,376 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "mycon.h"
+#include "resinst.h"
+
+// Core characteristics
+#define MAX_CREW 20
+#define MAX_ENERGY 40
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 4
+#define MAX_THRUST /* DISPLAY_TO_WORLD (7) */ 27
+#define THRUST_INCREMENT /* DISPLAY_TO_WORLD (2) */ 9
+#define THRUST_WAIT 6
+#define TURN_WAIT 6
+#define SHIP_MASS 7
+
+// Plasmoid
+#define WEAPON_ENERGY_COST 20
+#define WEAPON_WAIT 5
+#define MYCON_OFFSET 24
+#define MISSILE_OFFSET 0
+#define NUM_PLASMAS 11
+#define NUM_GLOBALLS 8
+#define PLASMA_DURATION 13
+#define MISSILE_LIFE (NUM_PLASMAS * PLASMA_DURATION)
+#define MISSILE_SPEED DISPLAY_TO_WORLD (8)
+#define MISSILE_DAMAGE 10
+#define TRACK_WAIT 1
+
+// Regenerate
+#define SPECIAL_ENERGY_COST MAX_ENERGY
+#define SPECIAL_WAIT 0
+#define REGENERATION_AMOUNT 4
+
+static RACE_DESC mycon_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | SEEKING_WEAPON,
+ 21, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ MYCON_RACE_STRINGS,
+ MYCON_ICON_MASK_PMAP_ANIM,
+ MYCON_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 1070 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 6392, 2200,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ MYCON_BIG_MASK_PMAP_ANIM,
+ MYCON_MED_MASK_PMAP_ANIM,
+ MYCON_SML_MASK_PMAP_ANIM,
+ },
+ {
+ PLASMA_BIG_MASK_PMAP_ANIM,
+ PLASMA_MED_MASK_PMAP_ANIM,
+ PLASMA_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ MYCON_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ MYCON_VICTORY_SONG,
+ MYCON_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ DISPLAY_TO_WORLD (800),
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+plasma_preprocess (ELEMENT *ElementPtr)
+{
+ COUNT plasma_index;
+
+ if (ElementPtr->mass_points > ElementPtr->hit_points)
+ ElementPtr->life_span = ElementPtr->hit_points * PLASMA_DURATION;
+ else
+ ElementPtr->hit_points = (BYTE)((ElementPtr->life_span *
+ MISSILE_DAMAGE + (MISSILE_LIFE - 1)) / MISSILE_LIFE);
+ ElementPtr->mass_points = ElementPtr->hit_points;
+ plasma_index = NUM_PLASMAS - ((ElementPtr->life_span +
+ (PLASMA_DURATION - 1)) / PLASMA_DURATION);
+ if (plasma_index != GetFrameIndex (ElementPtr->next.image.frame))
+ {
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->next.image.frame,
+ plasma_index);
+ ElementPtr->state_flags |= CHANGING;
+ }
+
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ COUNT facing;
+
+ facing = NORMALIZE_FACING (ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&ElementPtr->velocity)
+ ));
+ if (TrackShip (ElementPtr, &facing) > 0)
+ SetVelocityVector (&ElementPtr->velocity,
+ MISSILE_SPEED, facing);
+
+ ElementPtr->turn_wait = TRACK_WAIT;
+ }
+}
+
+static void
+plasma_blast_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->life_span >= ElementPtr->thrust_wait)
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->next.image.frame);
+ else
+ ElementPtr->next.image.frame =
+ DecFrameIndex (ElementPtr->next.image.frame);
+ if (ElementPtr->hTarget)
+ {
+ ELEMENT *ShipPtr;
+
+ LockElement (ElementPtr->hTarget, &ShipPtr);
+ ElementPtr->next.location = ShipPtr->next.location;
+ UnlockElement (ElementPtr->hTarget);
+ }
+
+ ElementPtr->state_flags |= CHANGING;
+}
+
+static void
+plasma_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ SIZE old_mass;
+ HELEMENT hBlastElement;
+
+ old_mass = (SIZE)ElementPtr0->mass_points;
+ if ((ElementPtr0->pParent != ElementPtr1->pParent
+ || (ElementPtr1->state_flags & PLAYER_SHIP))
+ && (hBlastElement =
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1)))
+ {
+ SIZE num_animations;
+ ELEMENT *BlastElementPtr;
+
+ LockElement (hBlastElement, &BlastElementPtr);
+ BlastElementPtr->pParent = ElementPtr0->pParent;
+ if (!(ElementPtr1->state_flags & PLAYER_SHIP))
+ BlastElementPtr->hTarget = 0;
+ else
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ BlastElementPtr->hTarget = StarShipPtr->hShip;
+ }
+
+ BlastElementPtr->current.location = ElementPtr1->current.location;
+
+ if ((num_animations =
+ (old_mass * NUM_GLOBALLS +
+ (MISSILE_DAMAGE - 1)) / MISSILE_DAMAGE) == 0)
+ num_animations = 1;
+
+ BlastElementPtr->thrust_wait = (BYTE)num_animations;
+ BlastElementPtr->life_span = (num_animations << 1) - 1;
+ {
+ BlastElementPtr->preprocess_func = plasma_blast_preprocess;
+ }
+ BlastElementPtr->current.image.farray = ElementPtr0->next.image.farray;
+ BlastElementPtr->current.image.frame =
+ SetAbsFrameIndex (BlastElementPtr->current.image.farray[0],
+ NUM_PLASMAS);
+
+ UnlockElement (hBlastElement);
+ }
+}
+
+static void
+mycon_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->MoveState == ENTICE)
+ {
+ if ((lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE)
+ && !(lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT))
+ lpEvalDesc->MoveState = AVOID;
+ else
+ lpEvalDesc->MoveState = PURSUE;
+ }
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (ObjectsOfConcern[ENEMY_WEAPON_INDEX].MoveState == PURSUE)
+ StarShipPtr->ship_input_state &= ~THRUST; /* don't pursue seekers */
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (StarShipPtr->weapon_counter == 0
+ && lpEvalDesc->ObjectPtr
+ && (lpEvalDesc->which_turn <= 16
+ || ShipPtr->crew_level == StarShipPtr->RaceDescPtr->ship_info.max_crew))
+ {
+ COUNT travel_facing, direction_facing;
+ SIZE delta_x, delta_y;
+
+ travel_facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (GetVelocityTravelAngle (&ShipPtr->velocity)
+ + HALF_CIRCLE)
+ );
+ delta_x = lpEvalDesc->ObjectPtr->current.location.x
+ - ShipPtr->current.location.x;
+ delta_y = lpEvalDesc->ObjectPtr->current.location.y
+ - ShipPtr->current.location.y;
+ direction_facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
+ );
+
+ if (NORMALIZE_FACING (direction_facing
+ - StarShipPtr->ShipFacing
+ + ANGLE_TO_FACING (QUADRANT))
+ <= ANGLE_TO_FACING (HALF_CIRCLE)
+ && (!(StarShipPtr->cur_status_flags &
+ (SHIP_BEYOND_MAX_SPEED | SHIP_IN_GRAVITY_WELL))
+ || NORMALIZE_FACING (direction_facing
+ - travel_facing + ANGLE_TO_FACING (OCTANT))
+ <= ANGLE_TO_FACING (QUADRANT)))
+ StarShipPtr->ship_input_state |= WEAPON;
+ }
+
+ if (StarShipPtr->special_counter == 0)
+ {
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ StarShipPtr->RaceDescPtr->cyborg_control.WeaponRange = DISPLAY_TO_WORLD (800);
+ if (ShipPtr->crew_level < StarShipPtr->RaceDescPtr->ship_info.max_crew)
+ {
+ StarShipPtr->RaceDescPtr->cyborg_control.WeaponRange = MISSILE_SPEED * MISSILE_LIFE;
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level >= SPECIAL_ENERGY_COST
+ && !(StarShipPtr->ship_input_state & WEAPON))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ }
+}
+
+static COUNT
+initialize_plasma (ELEMENT *ShipPtr, HELEMENT PlasmaArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = 0;
+ MissileBlock.pixoffs = MYCON_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_DAMAGE;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = plasma_preprocess;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ PlasmaArray[0] = initialize_missile (&MissileBlock);
+
+ if (PlasmaArray[0])
+ {
+ ELEMENT *PlasmaPtr;
+
+ LockElement (PlasmaArray[0], &PlasmaPtr);
+ PlasmaPtr->collision_func = plasma_collision;
+ PlasmaPtr->turn_wait = TRACK_WAIT + 2;
+ UnlockElement (PlasmaArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+mycon_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && ElementPtr->crew_level != StarShipPtr->RaceDescPtr->ship_info.max_crew
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ SIZE add_crew;
+
+ ProcessSound (SetAbsSoundIndex (
+ /* GROW_NEW_CREW */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ if ((add_crew = REGENERATION_AMOUNT) >
+ StarShipPtr->RaceDescPtr->ship_info.max_crew - ElementPtr->crew_level)
+ add_crew = StarShipPtr->RaceDescPtr->ship_info.max_crew - ElementPtr->crew_level;
+ DeltaCrew (ElementPtr, add_crew);
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+}
+
+RACE_DESC*
+init_mycon (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ mycon_desc.postprocess_func = mycon_postprocess;
+ mycon_desc.init_weapon_func = initialize_plasma;
+ mycon_desc.cyborg_control.intelligence_func = mycon_intelligence;
+
+ RaceDescPtr = &mycon_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/mycon/mycon.h b/src/uqm/ships/mycon/mycon.h
new file mode 100644
index 0000000..8051b7c
--- /dev/null
+++ b/src/uqm/ships/mycon/mycon.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MYCON_H
+#define MYCON_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_mycon (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* MYCON_H */
+
diff --git a/src/uqm/ships/mycon/resinst.h b/src/uqm/ships/mycon/resinst.h
new file mode 100644
index 0000000..38908a2
--- /dev/null
+++ b/src/uqm/ships/mycon/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define MYCON_BIG_MASK_PMAP_ANIM "ship.mycon.graphics.podship.large"
+#define MYCON_CAPTAIN_MASK_PMAP_ANIM "ship.mycon.graphics.captain"
+#define MYCON_ICON_MASK_PMAP_ANIM "ship.mycon.icons"
+#define MYCON_MED_MASK_PMAP_ANIM "ship.mycon.graphics.podship.medium"
+#define MYCON_MICON_MASK_PMAP_ANIM "ship.mycon.meleeicons"
+#define MYCON_RACE_STRINGS "ship.mycon.text"
+#define MYCON_SHIP_SOUNDS "ship.mycon.sounds"
+#define MYCON_SML_MASK_PMAP_ANIM "ship.mycon.graphics.podship.small"
+#define MYCON_VICTORY_SONG "ship.mycon.ditty"
+#define PLASMA_BIG_MASK_PMAP_ANIM "ship.mycon.graphics.plasma.large"
+#define PLASMA_MED_MASK_PMAP_ANIM "ship.mycon.graphics.plasma.medium"
+#define PLASMA_SML_MASK_PMAP_ANIM "ship.mycon.graphics.plasma.small"
diff --git a/src/uqm/ships/orz/Makeinfo b/src/uqm/ships/orz/Makeinfo
new file mode 100644
index 0000000..0c4961d
--- /dev/null
+++ b/src/uqm/ships/orz/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="orz.c"
+uqm_HFILES="icode.h orz.h resinst.h"
diff --git a/src/uqm/ships/orz/icode.h b/src/uqm/ships/orz/icode.h
new file mode 100644
index 0000000..bb45c4e
--- /dev/null
+++ b/src/uqm/ships/orz/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ORZ_CODE "ship.orz.code"
diff --git a/src/uqm/ships/orz/orz.c b/src/uqm/ships/orz/orz.c
new file mode 100644
index 0000000..1a95fec
--- /dev/null
+++ b/src/uqm/ships/orz/orz.c
@@ -0,0 +1,1083 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "orz.h"
+#include "resinst.h"
+
+#include "uqm/colors.h"
+#include "uqm/globdata.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 16
+#define MAX_ENERGY 20
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 6
+#define MAX_THRUST 35
+#define THRUST_INCREMENT 5
+#define THRUST_WAIT 0
+#define TURN_WAIT 1
+#define SHIP_MASS 4
+
+// Howitzer
+#define WEAPON_ENERGY_COST (MAX_ENERGY / 3)
+#define WEAPON_WAIT 4
+#define ORZ_OFFSET 9
+#define MISSILE_SPEED DISPLAY_TO_WORLD (30)
+#define MISSILE_LIFE 12
+#define MISSILE_HITS 2
+#define MISSILE_DAMAGE 3
+#define MISSILE_OFFSET 1
+
+// Marine
+#define SPECIAL_ENERGY_COST 0
+#define SPECIAL_WAIT 12
+#define MARINE_MAX_THRUST 32
+#define MARINE_THRUST_INCREMENT 8
+#define MARINE_HIT_POINTS 3
+#define MARINE_MASS_POINTS 1
+#define MAX_MARINES 8
+#define MARINE_WAIT 12
+#define ION_LIFE 1
+#define START_ION_COLOR BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7A)
+
+// Rotating Turret
+#define TURRET_OFFSET 14
+#define TURRET_WAIT 3
+
+static RACE_DESC orz_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | SEEKING_SPECIAL,
+ 23, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ ORZ_RACE_STRINGS,
+ ORZ_ICON_MASK_PMAP_ANIM,
+ ORZ_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 333 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 3608, 2637,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ ORZ_BIG_MASK_PMAP_ANIM,
+ ORZ_MED_MASK_PMAP_ANIM,
+ ORZ_SML_MASK_PMAP_ANIM,
+ },
+ {
+ HOWITZER_BIG_MASK_PMAP_ANIM,
+ HOWITZER_MED_MASK_PMAP_ANIM,
+ HOWITZER_SML_MASK_PMAP_ANIM,
+ },
+ {
+ TURRET_BIG_MASK_PMAP_ANIM,
+ TURRET_MED_MASK_PMAP_ANIM,
+ TURRET_SML_MASK_PMAP_ANIM,
+ },
+ {
+ ORZ_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ ORZ_VICTORY_SONG,
+ ORZ_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ MISSILE_SPEED * MISSILE_LIFE,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+howitzer_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (!elementsOfSamePlayer (ElementPtr0, ElementPtr1))
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+}
+
+static COUNT
+initialize_turret_missile (ELEMENT *ShipPtr, HELEMENT MissileArray[])
+{
+ ELEMENT *TurretPtr;
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+
+ LockElement (GetSuccElement (ShipPtr), &TurretPtr);
+ if (TurretPtr->turn_wait == 0
+ && (StarShipPtr->cur_status_flags & SPECIAL)
+ && (StarShipPtr->cur_status_flags & (LEFT | RIGHT)))
+ {
+ if (StarShipPtr->cur_status_flags & RIGHT)
+ ++TurretPtr->thrust_wait;
+ else
+ --TurretPtr->thrust_wait;
+
+ TurretPtr->turn_wait = TURRET_WAIT + 1;
+ }
+ MissileBlock.face = MissileBlock.index =
+ NORMALIZE_FACING (StarShipPtr->ShipFacing
+ + TurretPtr->thrust_wait);
+ UnlockElement (GetSuccElement (ShipPtr));
+
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = TURRET_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ MissileArray[0] = initialize_missile (&MissileBlock);
+
+ if (MissileArray[0])
+ {
+ ELEMENT *HowitzerPtr;
+
+ LockElement (MissileArray[0], &HowitzerPtr);
+ HowitzerPtr->collision_func = howitzer_collision;
+ UnlockElement (MissileArray[0]);
+ }
+
+ return (1);
+}
+
+static BYTE
+count_marines (STARSHIP *StarShipPtr, BOOLEAN FindSpot)
+{
+ BYTE num_marines, id_use[MAX_MARINES];
+ HELEMENT hElement, hNextElement;
+
+ num_marines = MAX_MARINES;
+ while (num_marines--)
+ id_use[num_marines] = 0;
+
+ num_marines = 0;
+ for (hElement = GetTailElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = GetPredElement (ElementPtr);
+ if (ElementPtr->current.image.farray ==
+ StarShipPtr->RaceDescPtr->ship_data.special
+ && ElementPtr->life_span
+ && !(ElementPtr->state_flags & (FINITE_LIFE | DISAPPEARING)))
+ {
+ if (ElementPtr->state_flags & NONSOLID)
+ {
+ id_use[ElementPtr->turn_wait] = 1;
+ }
+
+ if (++num_marines == MAX_MARINES)
+ {
+ UnlockElement (hElement);
+ hNextElement = 0;
+ }
+ }
+ UnlockElement (hElement);
+ }
+
+ if (FindSpot)
+ {
+ num_marines = 0;
+ while (id_use[num_marines])
+ ++num_marines;
+ }
+
+ return (num_marines);
+}
+
+static void
+orz_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ ELEMENT *TurretPtr;
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ LockElement (GetSuccElement (ShipPtr), &TurretPtr);
+
+ ++TurretPtr->turn_wait;
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+ --TurretPtr->turn_wait;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEvalDesc->ObjectPtr == 0)
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ else if (StarShipPtr->special_counter != 1)
+ {
+ STARSHIP *EnemyStarShipPtr;
+
+ if (ShipPtr->turn_wait == 0
+ && lpEvalDesc->MoveState == ENTICE
+ && lpEvalDesc->which_turn < 24
+ && (StarShipPtr->cur_status_flags
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED))
+ && !(StarShipPtr->ship_input_state & THRUST)
+ && NORMALIZE_ANGLE (
+ GetVelocityTravelAngle (&ShipPtr->velocity)
+ - ARCTAN (
+ lpEvalDesc->ObjectPtr->next.location.x
+ - ShipPtr->next.location.x,
+ lpEvalDesc->ObjectPtr->next.location.y
+ - ShipPtr->next.location.y
+ ) + (QUADRANT - (OCTANT >> 1))) >=
+ ((QUADRANT - (OCTANT >> 1)) << 1))
+ StarShipPtr->ship_input_state &= ~(LEFT | RIGHT);
+
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (ShipPtr->turn_wait == 0
+ && !(StarShipPtr->ship_input_state & (LEFT | RIGHT | WEAPON))
+ && TurretPtr->turn_wait == 0)
+ {
+ SIZE delta_facing;
+ COUNT facing;//, orig_facing;
+
+ facing = NORMALIZE_FACING (StarShipPtr->ShipFacing
+ + TurretPtr->thrust_wait);
+ if ((delta_facing = TrackShip (TurretPtr, &facing)) > 0)
+ {
+ StarShipPtr->ship_input_state |= SPECIAL;
+ if (delta_facing == ANGLE_TO_FACING (HALF_CIRCLE))
+ delta_facing += (((BYTE)TFB_Random () & 1) << 1) - 1;
+
+ if (delta_facing < ANGLE_TO_FACING (HALF_CIRCLE))
+ StarShipPtr->ship_input_state |= RIGHT;
+ else
+ StarShipPtr->ship_input_state |= LEFT;
+ }
+ }
+
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if (StarShipPtr->special_counter == 0
+ && !(StarShipPtr->ship_input_state & WEAPON)
+ && StarShipPtr->RaceDescPtr->ship_info.crew_level >
+ (BYTE)(StarShipPtr->RaceDescPtr->ship_info.max_crew >> 2)
+ && !(EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags
+ & POINT_DEFENSE)
+ && (MANEUVERABILITY (
+ &EnemyStarShipPtr->RaceDescPtr->cyborg_control
+ ) < SLOW_SHIP
+ || lpEvalDesc->which_turn <= 12
+ || count_marines (StarShipPtr, FALSE) < 2))
+ {
+ StarShipPtr->ship_input_state |= WEAPON | SPECIAL;
+ }
+ }
+
+ UnlockElement (GetSuccElement (ShipPtr));
+}
+
+static void
+ion_preprocess (ELEMENT *ElementPtr)
+{
+ /* Originally, this table also contained the now commented out
+ * entries. It then used some if statements to skip over these.
+ * The current behaviour is the same as the old behavior.
+ */
+ static const Color colorTable[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7a),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7c),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7d),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7e),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7f),
+
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x2a),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2b),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2c),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2d),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2e),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x0B, 0x00, 0x00), 0x2f),
+ };
+ const size_t colorTabCount = sizeof colorTable / sizeof colorTable[0];
+
+ ElementPtr->colorCycleIndex++;
+ if (ElementPtr->colorCycleIndex != colorTabCount)
+ {
+ ElementPtr->life_span = ElementPtr->thrust_wait;
+
+ SetPrimColor (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ colorTable[ElementPtr->colorCycleIndex]);
+
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+static void marine_preprocess (ELEMENT *ElementPtr);
+
+void
+intruder_preprocess (ELEMENT *ElementPtr)
+{
+ HELEMENT hElement, hNextElement;
+ ELEMENT *ShipPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ if (ShipPtr->crew_level == 0
+ && ShipPtr->life_span == 1
+ && (ShipPtr->state_flags & (FINITE_LIFE | NONSOLID)) ==
+ (FINITE_LIFE | NONSOLID))
+ {
+ ElementPtr->life_span = 0;
+ ElementPtr->state_flags |= DISAPPEARING;
+ }
+ UnlockElement (StarShipPtr->hShip);
+
+ if (ElementPtr->thrust_wait)
+ --ElementPtr->thrust_wait;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ LockElement (hElement, &ShipPtr);
+ if ((ShipPtr->state_flags & PLAYER_SHIP)
+ && !elementsOfSamePlayer (ShipPtr, ElementPtr))
+ {
+ STAMP s;
+
+ if (ElementPtr->thrust_wait == MARINE_WAIT)
+ {
+ --ElementPtr->thrust_wait;
+
+ s.origin.x = 16 + (ElementPtr->turn_wait & 3) * 9;
+ s.origin.y = 14 + (ElementPtr->turn_wait >> 2) * 11;
+ s.frame = SetAbsFrameIndex (ElementPtr->next.image.farray[0],
+ GetFrameCount (ElementPtr->next.image.farray[0]) - 2);
+ ModifySilhouette (ShipPtr, &s, 0);
+ }
+
+ ElementPtr->next.location = ShipPtr->next.location;
+
+ if (ShipPtr->crew_level == 0
+ || ElementPtr->life_span == 0)
+ {
+ UnlockElement (hElement);
+ hElement = 0;
+LeftShip:
+ s.origin.x = 16 + (ElementPtr->turn_wait & 3) * 9;
+ s.origin.y = 14 + (ElementPtr->turn_wait >> 2) * 11;
+ s.frame = ElementPtr->next.image.frame;
+ ModifySilhouette (ShipPtr, &s, MODIFY_SWAP);
+ }
+ else if (ElementPtr->thrust_wait == 0)
+ {
+ BYTE randval;
+
+ ElementPtr->thrust_wait = MARINE_WAIT;
+
+ randval = (BYTE)TFB_Random ();
+ if (randval < (0x0100 / 16))
+ {
+ ElementPtr->life_span = 0;
+ ElementPtr->state_flags |= DISAPPEARING;
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 4), ElementPtr);
+ goto LeftShip;
+ }
+ else if (randval < (0x0100 / 2 + 0x0100 / 16))
+ {
+ if (!DeltaCrew (ShipPtr, -1))
+ ShipPtr->life_span = 0;
+
+ ++ElementPtr->thrust_wait;
+ s.origin.x = 16 + (ElementPtr->turn_wait & 3) * 9;
+ s.origin.y = 14 + (ElementPtr->turn_wait >> 2) * 11;
+ s.frame = SetAbsFrameIndex (ElementPtr->next.image.farray[0],
+ GetFrameCount (ElementPtr->next.image.farray[0]) - 1);
+ ModifySilhouette (ShipPtr, &s, 0);
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 3), ElementPtr);
+ }
+ }
+
+ UnlockElement (hElement);
+ break;
+ }
+ hNextElement = GetSuccElement (ShipPtr);
+ UnlockElement (hElement);
+ }
+
+ if (hElement == 0 && ElementPtr->life_span)
+ {
+ ElementPtr->state_flags &= ~NONSOLID;
+ ElementPtr->state_flags |= CHANGING | CREW_OBJECT;
+ SetPrimType (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ STAMP_PRIM);
+
+ ElementPtr->current.image.frame =
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.special[0], 21);
+ ElementPtr->thrust_wait = 0;
+ ElementPtr->turn_wait =
+ MAKE_BYTE (0, NORMALIZE_FACING ((BYTE)TFB_Random ()));
+ ElementPtr->preprocess_func = marine_preprocess;
+ }
+}
+
+// XXX: merge this with spawn_ion_trail from tactrans.c?
+static void
+spawn_marine_ion_trail (ELEMENT *ElementPtr, STARSHIP *StarShipPtr,
+ COUNT facing)
+{
+ HELEMENT hIonElement;
+
+ hIonElement = AllocElement ();
+ if (hIonElement)
+ {
+ COUNT angle;
+ ELEMENT *IonElementPtr;
+
+ angle = FACING_TO_ANGLE (facing) + HALF_CIRCLE;
+
+ InsertElement (hIonElement, GetHeadElement ());
+ LockElement (hIonElement, &IonElementPtr);
+ IonElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ IonElementPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ IonElementPtr->thrust_wait = ION_LIFE;
+ IonElementPtr->life_span = IonElementPtr->thrust_wait;
+ // When the element "dies", in the death_func
+ // 'cycle_ion_trail', it is given new life a number of
+ // times, by setting life_span to thrust_wait.
+ SetPrimType (&(GLOBAL (DisplayArray))[IonElementPtr->PrimIndex],
+ POINT_PRIM);
+ SetPrimColor (&(GLOBAL (DisplayArray))[IonElementPtr->PrimIndex],
+ START_ION_COLOR);
+ IonElementPtr->colorCycleIndex = 0;
+ IonElementPtr->current.location = ElementPtr->current.location;
+ IonElementPtr->current.location.x +=
+ (COORD)COSINE (angle, DISPLAY_TO_WORLD (2));
+ IonElementPtr->current.location.y +=
+ (COORD)SINE (angle, DISPLAY_TO_WORLD (2));
+ IonElementPtr->death_func = ion_preprocess;
+
+ SetElementStarShip (IonElementPtr, StarShipPtr);
+
+ {
+ /* normally done during preprocess, but because
+ * object is being inserted at head rather than
+ * appended after tail it may never get preprocessed.
+ */
+ IonElementPtr->next = IonElementPtr->current;
+ --IonElementPtr->life_span;
+ IonElementPtr->state_flags |= PRE_PROCESS;
+ }
+
+ UnlockElement (hIonElement);
+ }
+}
+
+static void
+marine_preprocess (ELEMENT *ElementPtr)
+{
+ ELEMENT *ShipPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ if (ShipPtr->crew_level == 0
+ && ShipPtr->life_span == 1
+ && (ShipPtr->state_flags & (FINITE_LIFE | NONSOLID)) ==
+ (FINITE_LIFE | NONSOLID))
+ {
+ ElementPtr->life_span = 0;
+ ElementPtr->state_flags |= DISAPPEARING | NONSOLID;
+ ElementPtr->turn_wait = 1;
+ }
+ UnlockElement (StarShipPtr->hShip);
+
+ if (LONIBBLE (ElementPtr->turn_wait))
+ --ElementPtr->turn_wait;
+ else
+ {
+ COUNT facing, pfacing = 0;
+ SIZE delta_x, delta_y, delta_facing;
+ HELEMENT hObject, hNextObject, hTarget;
+ ELEMENT *ObjectPtr;
+
+ // XXX: thrust_wait is abused to store marine speed and
+ // gravity well flags
+ ElementPtr->thrust_wait &= ~(SHIP_IN_GRAVITY_WELL >> 6);
+
+ hTarget = 0;
+ for (hObject = GetHeadElement ();
+ hObject; hObject = hNextObject)
+ {
+ LockElement (hObject, &ObjectPtr);
+ hNextObject = GetSuccElement (ObjectPtr);
+ if (GRAVITY_MASS (ObjectPtr->mass_points))
+ {
+ delta_x = ObjectPtr->current.location.x
+ - ElementPtr->current.location.x;
+ delta_x = WRAP_DELTA_X (delta_x);
+
+ delta_y = ObjectPtr->current.location.y
+ - ElementPtr->current.location.y;
+ delta_y = WRAP_DELTA_Y (delta_y);
+ if ((long)delta_x * delta_x + (long)delta_y * delta_y <=
+ (long)(DISPLAY_TO_WORLD (GRAVITY_THRESHOLD)
+ * DISPLAY_TO_WORLD (GRAVITY_THRESHOLD)))
+ {
+ pfacing = ANGLE_TO_FACING (ARCTAN (delta_x, delta_y));
+ delta_facing = NORMALIZE_FACING (
+ pfacing - ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&ElementPtr->velocity))
+ + ANGLE_TO_FACING (OCTANT));
+ if (delta_facing <= ANGLE_TO_FACING (QUADRANT))
+ {
+ hTarget = hObject;
+ hNextObject = 0;
+ }
+
+ ElementPtr->thrust_wait |= (SHIP_IN_GRAVITY_WELL >> 6);
+ }
+ }
+ else if ((ObjectPtr->state_flags & PLAYER_SHIP)
+ && ObjectPtr->crew_level
+ && !OBJECT_CLOAKED (ObjectPtr))
+ {
+ if (!elementsOfSamePlayer (ObjectPtr, ElementPtr))
+ {
+ if (ElementPtr->state_flags & IGNORE_SIMILAR)
+ hTarget = hObject;
+ }
+ else if (hTarget == 0)
+ hTarget = hObject;
+ }
+ UnlockElement (hObject);
+ }
+
+ facing = HINIBBLE (ElementPtr->turn_wait);
+ if (hTarget == 0)
+ delta_facing = -1;
+ else
+ {
+ LockElement (hTarget, &ObjectPtr);
+ delta_x = ObjectPtr->current.location.x
+ - ElementPtr->current.location.x;
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = ObjectPtr->current.location.y
+ - ElementPtr->current.location.y;
+ delta_y = WRAP_DELTA_Y (delta_y);
+ if (GRAVITY_MASS (ObjectPtr->mass_points))
+ {
+ delta_facing = NORMALIZE_FACING (pfacing - facing
+ + ANGLE_TO_FACING (OCTANT));
+
+ if (delta_facing > ANGLE_TO_FACING (QUADRANT))
+ delta_facing = 0;
+ else
+ {
+ if (delta_facing == ANGLE_TO_FACING (OCTANT))
+ facing += (((SIZE)TFB_Random () & 1) << 1) - 1;
+ else if (delta_facing < ANGLE_TO_FACING (OCTANT))
+ ++facing;
+ else
+ --facing;
+ }
+ }
+ else
+ {
+ COUNT num_frames;
+ VELOCITY_DESC ShipVelocity;
+
+ if (elementsOfSamePlayer (ObjectPtr, ElementPtr)
+ && (ElementPtr->state_flags & IGNORE_SIMILAR))
+ {
+ ElementPtr->next.image.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.special[0],
+ 21);
+ ElementPtr->state_flags &= ~IGNORE_SIMILAR;
+ ElementPtr->state_flags |= CHANGING;
+ }
+
+ num_frames = WORLD_TO_TURN (
+ square_root ((long)delta_x * delta_x
+ + (long)delta_y * delta_y));
+ if (num_frames == 0)
+ num_frames = 1;
+
+ ShipVelocity = ObjectPtr->velocity;
+ GetNextVelocityComponents (&ShipVelocity,
+ &delta_x, &delta_y, num_frames);
+
+ delta_x = (ObjectPtr->current.location.x + delta_x)
+ - ElementPtr->current.location.x;
+ delta_y = (ObjectPtr->current.location.y + delta_y)
+ - ElementPtr->current.location.y;
+
+ delta_facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y)) - facing);
+
+ if (delta_facing > 0)
+ {
+ if (delta_facing == ANGLE_TO_FACING (HALF_CIRCLE))
+ facing += (((BYTE)TFB_Random () & 1) << 1) - 1;
+ else if (delta_facing < ANGLE_TO_FACING (HALF_CIRCLE))
+ ++facing;
+ else
+ --facing;
+ }
+ }
+ UnlockElement (hTarget);
+ }
+
+ ElementPtr->turn_wait = MAKE_BYTE (0, NORMALIZE_FACING (facing));
+ if (delta_facing == 0
+ || ((ElementPtr->thrust_wait & (SHIP_BEYOND_MAX_SPEED >> 6))
+ && !(ElementPtr->thrust_wait & (SHIP_IN_GRAVITY_WELL >> 6))))
+ {
+ STATUS_FLAGS thrust_status;
+ COUNT OldFacing;
+ STATUS_FLAGS OldStatus;
+ COUNT OldIncrement, OldThrust;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ // XXX: Hack: abusing the primary STARSHIP struct in order
+ // to call inertial_thrust() for a marine
+ OldFacing = StarShipPtr->ShipFacing;
+ OldStatus = StarShipPtr->cur_status_flags;
+ OldIncrement = StarShipPtr->RaceDescPtr->characteristics.
+ thrust_increment;
+ OldThrust = StarShipPtr->RaceDescPtr->characteristics.max_thrust;
+
+ StarShipPtr->ShipFacing = facing;
+ // XXX: thrust_wait is abused to store marine speed and
+ // gravity well flags
+ StarShipPtr->cur_status_flags = ElementPtr->thrust_wait << 6;
+ StarShipPtr->RaceDescPtr->characteristics.thrust_increment =
+ MARINE_THRUST_INCREMENT;
+ StarShipPtr->RaceDescPtr->characteristics.max_thrust =
+ MARINE_MAX_THRUST;
+
+ thrust_status = inertial_thrust (ElementPtr);
+
+ StarShipPtr->RaceDescPtr->characteristics.max_thrust = OldThrust;
+ StarShipPtr->RaceDescPtr->characteristics.thrust_increment =
+ OldIncrement;
+ StarShipPtr->cur_status_flags = OldStatus;
+ StarShipPtr->ShipFacing = OldFacing;
+
+ if ((ElementPtr->thrust_wait & (SHIP_IN_GRAVITY_WELL >> 6))
+ || delta_facing
+ || !(thrust_status
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED)))
+ {
+ spawn_marine_ion_trail (ElementPtr, StarShipPtr, facing);
+ }
+
+ // XXX: thrust_wait is abused to store marine speed and
+ // gravity well flags
+ ElementPtr->thrust_wait = (BYTE)(thrust_status >> 6);
+ }
+ }
+}
+
+void
+marine_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (ElementPtr0->life_span
+ && !(ElementPtr0->state_flags & (NONSOLID | COLLISION))
+ && !(ElementPtr1->state_flags & FINITE_LIFE))
+ {
+ if (!elementsOfSamePlayer (ElementPtr0, ElementPtr1))
+ {
+ ElementPtr0->turn_wait =
+ MAKE_BYTE (5, HINIBBLE (ElementPtr0->turn_wait));
+ ElementPtr0->thrust_wait &=
+ ~((SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED) >> 6);
+ ElementPtr0->state_flags |= COLLISION;
+ }
+
+ if (GRAVITY_MASS (ElementPtr1->mass_points))
+ {
+ ElementPtr0->state_flags |= NONSOLID | FINITE_LIFE;
+ ElementPtr0->hit_points = 0;
+ ElementPtr0->life_span = 0;
+ }
+ else if ((ElementPtr1->state_flags & PLAYER_SHIP)
+ && ((ElementPtr1->state_flags & FINITE_LIFE)
+ || ElementPtr1->life_span == NORMAL_LIFE))
+ {
+ ElementPtr1->state_flags &= ~COLLISION;
+
+ if (!(ElementPtr0->state_flags & COLLISION))
+ {
+ DeltaCrew (ElementPtr1, 1);
+
+ ElementPtr0->state_flags |=
+ DISAPPEARING | NONSOLID | FINITE_LIFE;
+ ElementPtr0->hit_points = 0;
+ ElementPtr0->life_span = 0;
+ }
+ else if ((ElementPtr0->state_flags & IGNORE_SIMILAR)
+ && ElementPtr1->crew_level
+#ifdef NEVER
+ && (BYTE)TFB_Random () <= (0x0100 / 3)
+#endif /* NEVER */
+ )
+ {
+ STAMP s;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ if (!DeltaCrew (ElementPtr1, -1))
+ ElementPtr1->life_span = 0;
+ else
+ {
+ ElementPtr0->turn_wait = count_marines (StarShipPtr, TRUE);
+ ElementPtr0->thrust_wait = MARINE_WAIT;
+ ElementPtr0->next.image.frame = SetAbsFrameIndex (
+ ElementPtr0->next.image.farray[0],
+ 22 + ElementPtr0->turn_wait
+ );
+ ElementPtr0->state_flags |= NONSOLID;
+ ElementPtr0->state_flags &= ~CREW_OBJECT;
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ ElementPtr0->PrimIndex
+ ], NO_PRIM);
+ ElementPtr0->preprocess_func = intruder_preprocess;
+
+ s.origin.x = 16 + (ElementPtr0->turn_wait & 3) * 9;
+ s.origin.y = 14 + (ElementPtr0->turn_wait >> 2) * 11;
+ s.frame = ElementPtr0->next.image.frame;
+ ModifySilhouette (ElementPtr1, &s, 0);
+ }
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2),
+ ElementPtr1);
+ }
+
+ ElementPtr0->state_flags &= ~COLLISION;
+ }
+ }
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static void
+animate (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = ElementPtr->next_turn;
+ }
+}
+
+static void
+turret_postprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->life_span == 0)
+ {
+ STARSHIP *StarShipPtr;
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ ElementPtr->PrimIndex], NO_PRIM);
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->hShip)
+ {
+ COUNT facing;
+ HELEMENT hTurret, hSpaceMarine;
+ ELEMENT *ShipPtr;
+
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ hTurret = AllocElement ();
+ if (hTurret)
+ {
+ ELEMENT *TurretPtr;
+
+ LockElement (hTurret, &TurretPtr);
+ TurretPtr->playerNr = ElementPtr->playerNr;
+ TurretPtr->state_flags = FINITE_LIFE | NONSOLID
+ | IGNORE_SIMILAR | CHANGING | PRE_PROCESS
+ | POST_PROCESS;
+ TurretPtr->life_span = 1;
+ TurretPtr->current.image = ElementPtr->current.image;
+ TurretPtr->current.location = ShipPtr->next.location;
+ TurretPtr->turn_wait = ElementPtr->turn_wait;
+ TurretPtr->thrust_wait = ElementPtr->thrust_wait;
+
+ if (TurretPtr->turn_wait)
+ --TurretPtr->turn_wait;
+ else if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && (StarShipPtr->cur_status_flags & (LEFT | RIGHT)))
+ {
+ if (StarShipPtr->cur_status_flags & RIGHT)
+ ++TurretPtr->thrust_wait;
+ else
+ --TurretPtr->thrust_wait;
+
+ TurretPtr->turn_wait = TURRET_WAIT;
+ }
+ facing = NORMALIZE_FACING (StarShipPtr->ShipFacing
+ + TurretPtr->thrust_wait);
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags &=
+ ~(FIRES_FORE | FIRES_RIGHT | FIRES_AFT | FIRES_LEFT);
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags |= FIRES_FORE
+ << (NORMALIZE_FACING (facing + ANGLE_TO_FACING (OCTANT))
+ / ANGLE_TO_FACING (QUADRANT));
+ TurretPtr->current.image.frame = SetAbsFrameIndex (
+ TurretPtr->current.image.frame, facing);
+ facing = FACING_TO_ANGLE (facing);
+ if (StarShipPtr->cur_status_flags & WEAPON)
+ {
+ HELEMENT hTurretEffect;
+ ELEMENT *TurretEffectPtr;
+
+ LockElement (GetTailElement (), &TurretEffectPtr);
+ if (TurretEffectPtr != ElementPtr
+ && elementsOfSamePlayer (TurretEffectPtr, ElementPtr)
+ && (TurretEffectPtr->state_flags & APPEARING)
+ && GetPrimType (&(GLOBAL (DisplayArray))[
+ TurretEffectPtr->PrimIndex
+ ]) == STAMP_PRIM
+ && (hTurretEffect = AllocElement ()))
+ {
+ TurretPtr->current.location.x -=
+ COSINE (facing, DISPLAY_TO_WORLD (2));
+ TurretPtr->current.location.y -=
+ SINE (facing, DISPLAY_TO_WORLD (2));
+
+ LockElement (hTurretEffect, &TurretEffectPtr);
+ TurretEffectPtr->playerNr = ElementPtr->playerNr;
+ TurretEffectPtr->state_flags = FINITE_LIFE
+ | NONSOLID | IGNORE_SIMILAR | APPEARING;
+ TurretEffectPtr->life_span = 4;
+
+ TurretEffectPtr->current.location.x =
+ TurretPtr->current.location.x
+ + COSINE (facing,
+ DISPLAY_TO_WORLD (TURRET_OFFSET));
+ TurretEffectPtr->current.location.y =
+ TurretPtr->current.location.y
+ + SINE (facing,
+ DISPLAY_TO_WORLD (TURRET_OFFSET));
+ TurretEffectPtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ TurretEffectPtr->current.image.frame =
+ SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.special[0],
+ ANGLE_TO_FACING (FULL_CIRCLE));
+
+ TurretEffectPtr->preprocess_func = animate;
+
+ SetElementStarShip (TurretEffectPtr, StarShipPtr);
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ TurretEffectPtr->PrimIndex], STAMP_PRIM);
+
+ UnlockElement (hTurretEffect);
+ PutElement (hTurretEffect);
+ }
+ UnlockElement (GetTailElement ());
+ }
+ TurretPtr->next = TurretPtr->current;
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ TurretPtr->PrimIndex],
+ GetPrimType (&(GLOBAL (DisplayArray))[
+ ShipPtr->PrimIndex]));
+ SetPrimColor (&(GLOBAL (DisplayArray))[
+ TurretPtr->PrimIndex],
+ GetPrimColor (&(GLOBAL (DisplayArray))[
+ ShipPtr->PrimIndex]));
+
+ TurretPtr->postprocess_func = ElementPtr->postprocess_func;
+
+ SetElementStarShip (TurretPtr, StarShipPtr);
+
+ UnlockElement (hTurret);
+ InsertElement (hTurret, GetSuccElement (ElementPtr));
+ }
+
+ if (StarShipPtr->special_counter == 0
+ && (StarShipPtr->cur_status_flags & SPECIAL)
+ && (StarShipPtr->cur_status_flags & WEAPON)
+ && ShipPtr->crew_level > 1
+ && count_marines (StarShipPtr, FALSE) < MAX_MARINES
+ && TrackShip (ShipPtr, &facing) >= 0
+ && (hSpaceMarine = AllocElement ()))
+ {
+ ELEMENT *SpaceMarinePtr;
+
+ LockElement (hSpaceMarine, &SpaceMarinePtr);
+ SpaceMarinePtr->playerNr = ElementPtr->playerNr;
+ SpaceMarinePtr->state_flags = IGNORE_SIMILAR | APPEARING
+ | CREW_OBJECT;
+ SpaceMarinePtr->life_span = NORMAL_LIFE;
+ SpaceMarinePtr->hit_points = MARINE_HIT_POINTS;
+ SpaceMarinePtr->mass_points = MARINE_MASS_POINTS;
+
+ facing = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+ SpaceMarinePtr->current.location.x =
+ ShipPtr->current.location.x
+ - COSINE (facing, DISPLAY_TO_WORLD (TURRET_OFFSET));
+ SpaceMarinePtr->current.location.y =
+ ShipPtr->current.location.y
+ - SINE (facing, DISPLAY_TO_WORLD (TURRET_OFFSET));
+ SpaceMarinePtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ SpaceMarinePtr->current.image.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.special[0], 20);
+
+ SpaceMarinePtr->turn_wait =
+ MAKE_BYTE (0, NORMALIZE_FACING (
+ ANGLE_TO_FACING (facing + HALF_CIRCLE)));
+ SpaceMarinePtr->preprocess_func = marine_preprocess;
+ SpaceMarinePtr->collision_func = marine_collision;
+
+ SetElementStarShip (SpaceMarinePtr, StarShipPtr);
+
+ SetPrimType (&(GLOBAL (DisplayArray))[
+ SpaceMarinePtr->PrimIndex], STAMP_PRIM);
+
+ UnlockElement (hSpaceMarine);
+ PutElement (hSpaceMarine);
+
+ DeltaCrew (ShipPtr, -1);
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1),
+ SpaceMarinePtr);
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+
+ UnlockElement (StarShipPtr->hShip);
+ }
+ }
+}
+
+static void
+orz_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (!(ElementPtr->state_flags & APPEARING))
+ {
+ if (((StarShipPtr->cur_status_flags
+ | StarShipPtr->old_status_flags) & SPECIAL)
+ && (StarShipPtr->cur_status_flags & (LEFT | RIGHT))
+ && ElementPtr->turn_wait == 0)
+ {
+ ++ElementPtr->turn_wait;
+ }
+
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && (StarShipPtr->cur_status_flags & WEAPON)
+ && StarShipPtr->weapon_counter == 0)
+ {
+ ++StarShipPtr->weapon_counter;
+ }
+ }
+ else
+ {
+ HELEMENT hTurret;
+
+ hTurret = AllocElement ();
+ if (hTurret)
+ {
+ ELEMENT *TurretPtr;
+
+ LockElement (hTurret, &TurretPtr);
+ TurretPtr->playerNr = ElementPtr->playerNr;
+ TurretPtr->state_flags = FINITE_LIFE | NONSOLID | IGNORE_SIMILAR;
+ TurretPtr->life_span = 1;
+ TurretPtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ TurretPtr->current.image.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.special[0],
+ StarShipPtr->ShipFacing);
+
+ TurretPtr->postprocess_func = turret_postprocess;
+
+ SetElementStarShip (TurretPtr, StarShipPtr);
+
+ UnlockElement (hTurret);
+ InsertElement (hTurret, GetSuccElement (ElementPtr));
+ }
+ }
+}
+
+RACE_DESC*
+init_orz (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ orz_desc.preprocess_func = orz_preprocess;
+ orz_desc.init_weapon_func = initialize_turret_missile;
+ orz_desc.cyborg_control.intelligence_func = orz_intelligence;
+
+ RaceDescPtr = &orz_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/orz/orz.h b/src/uqm/ships/orz/orz.h
new file mode 100644
index 0000000..a62caac
--- /dev/null
+++ b/src/uqm/ships/orz/orz.h
@@ -0,0 +1,35 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ORZ_H
+#define ORZ_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_orz (void);
+
+void intruder_preprocess (ELEMENT *ElementPtr);
+void marine_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* ORZ_H */
+
diff --git a/src/uqm/ships/orz/resinst.h b/src/uqm/ships/orz/resinst.h
new file mode 100644
index 0000000..4af5264
--- /dev/null
+++ b/src/uqm/ships/orz/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define HOWITZER_BIG_MASK_PMAP_ANIM "ship.orz.graphics.howitzer.large"
+#define HOWITZER_MED_MASK_PMAP_ANIM "ship.orz.graphics.howitzer.medium"
+#define HOWITZER_SML_MASK_PMAP_ANIM "ship.orz.graphics.howitzer.small"
+#define ORZ_BIG_MASK_PMAP_ANIM "ship.orz.graphics.nemesis.large"
+#define ORZ_CAPTAIN_MASK_PMAP_ANIM "ship.orz.graphics.captain"
+#define ORZ_ICON_MASK_PMAP_ANIM "ship.orz.icons"
+#define ORZ_MED_MASK_PMAP_ANIM "ship.orz.graphics.nemesis.medium"
+#define ORZ_MICON_MASK_PMAP_ANIM "ship.orz.meleeicons"
+#define ORZ_RACE_STRINGS "ship.orz.text"
+#define ORZ_SHIP_SOUNDS "ship.orz.sounds"
+#define ORZ_SML_MASK_PMAP_ANIM "ship.orz.graphics.nemesis.small"
+#define ORZ_VICTORY_SONG "ship.orz.ditty"
+#define TURRET_BIG_MASK_PMAP_ANIM "ship.orz.graphics.turret.large"
+#define TURRET_MED_MASK_PMAP_ANIM "ship.orz.graphics.turret.medium"
+#define TURRET_SML_MASK_PMAP_ANIM "ship.orz.graphics.turret.small"
diff --git a/src/uqm/ships/pkunk/Makeinfo b/src/uqm/ships/pkunk/Makeinfo
new file mode 100644
index 0000000..54d92b0
--- /dev/null
+++ b/src/uqm/ships/pkunk/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="pkunk.c"
+uqm_HFILES="icode.h pkunk.h resinst.h"
diff --git a/src/uqm/ships/pkunk/icode.h b/src/uqm/ships/pkunk/icode.h
new file mode 100644
index 0000000..4d0d4e5
--- /dev/null
+++ b/src/uqm/ships/pkunk/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define PKUNK_CODE "ship.pkunk.code"
diff --git a/src/uqm/ships/pkunk/pkunk.c b/src/uqm/ships/pkunk/pkunk.c
new file mode 100644
index 0000000..76f8413
--- /dev/null
+++ b/src/uqm/ships/pkunk/pkunk.c
@@ -0,0 +1,640 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "pkunk.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+#include "uqm/tactrans.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 8
+#define MAX_ENERGY 12
+#define ENERGY_REGENERATION 0
+#define ENERGY_WAIT 0
+#define MAX_THRUST 64
+#define THRUST_INCREMENT 16
+#define THRUST_WAIT 0
+#define TURN_WAIT 0
+#define SHIP_MASS 1
+
+// Triple Miniguns
+#define WEAPON_ENERGY_COST 1
+#define WEAPON_WAIT 0
+#define PKUNK_OFFSET 15
+#define MISSILE_OFFSET 1
+#define MISSILE_SPEED DISPLAY_TO_WORLD (24)
+#define MISSILE_LIFE 5
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 1
+
+// Taunt
+#define SPECIAL_ENERGY_COST 2
+#define SPECIAL_WAIT 16
+
+// Respawn
+#define PHOENIX_LIFE 12
+#define START_PHOENIX_COLOR BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7A)
+#define TRANSITION_LIFE 1
+#define TRANSITION_SPEED DISPLAY_TO_WORLD (20)
+
+static RACE_DESC pkunk_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | FIRES_LEFT | FIRES_RIGHT,
+ 20, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ PKUNK_RACE_STRINGS,
+ PKUNK_ICON_MASK_PMAP_ANIM,
+ PKUNK_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 666 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 502, 401,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ 0, /* SPECIAL_WAIT */
+ SHIP_MASS,
+ },
+ {
+ {
+ PKUNK_BIG_MASK_PMAP_ANIM,
+ PKUNK_MED_MASK_PMAP_ANIM,
+ PKUNK_SML_MASK_PMAP_ANIM,
+ },
+ {
+ BUG_BIG_MASK_PMAP_ANIM,
+ BUG_MED_MASK_PMAP_ANIM,
+ BUG_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ PKUNK_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ PKUNK_VICTORY_SONG,
+ PKUNK_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ CLOSE_RANGE_WEAPON + 1,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+// Private per-instance ship data
+typedef struct
+{
+ HELEMENT hPhoenix;
+ ElementProcessFunc *saved_preprocess_func;
+ ElementProcessFunc *saved_postprocess_func;
+ ElementProcessFunc *saved_death_func;
+
+} PKUNK_DATA;
+
+// Local typedef
+typedef PKUNK_DATA CustomShipData_t;
+
+// Retrieve race-specific ship data from a race desc
+static CustomShipData_t *
+GetCustomShipData (RACE_DESC *pRaceDesc)
+{
+ return pRaceDesc->data;
+}
+
+// Set the race-specific data in a race desc
+// (Re)Allocates its own storage for the data.
+static void
+SetCustomShipData (RACE_DESC *pRaceDesc, const CustomShipData_t *data)
+{
+ if (pRaceDesc->data == data)
+ return; // no-op
+
+ if (pRaceDesc->data) // Out with the old
+ {
+ HFree (pRaceDesc->data);
+ pRaceDesc->data = NULL;
+ }
+
+ if (data) // In with the new
+ {
+ CustomShipData_t* newData = HMalloc (sizeof (*data));
+ *newData = *data;
+ pRaceDesc->data = newData;
+ }
+}
+
+static void
+animate (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->turn_wait = ElementPtr->next_turn;
+ }
+}
+
+static COUNT
+initialize_bug_missile (ELEMENT *ShipPtr, HELEMENT MissileArray[])
+{
+ COUNT i;
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = PKUNK_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+
+ for (i = 0; i < 3; ++i)
+ {
+ MissileBlock.face =
+ StarShipPtr->ShipFacing
+ + (ANGLE_TO_FACING (QUADRANT) * i);
+ if (i == 2)
+ MissileBlock.face += ANGLE_TO_FACING (QUADRANT);
+ MissileBlock.face = NORMALIZE_FACING (MissileBlock.face);
+
+ if ((MissileArray[i] = initialize_missile (&MissileBlock)))
+ {
+ SIZE dx, dy;
+ ELEMENT *MissilePtr;
+
+ LockElement (MissileArray[i], &MissilePtr);
+ GetCurrentVelocityComponents (&ShipPtr->velocity, &dx, &dy);
+ DeltaVelocityComponents (&MissilePtr->velocity, dx, dy);
+ MissilePtr->current.location.x -= VELOCITY_TO_WORLD (dx);
+ MissilePtr->current.location.y -= VELOCITY_TO_WORLD (dy);
+
+ MissilePtr->preprocess_func = animate;
+ UnlockElement (MissileArray[i]);
+ }
+ }
+
+ return (3);
+}
+
+static void
+pkunk_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ STARSHIP *StarShipPtr;
+ PKUNK_DATA *PkunkData;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ PkunkData = GetCustomShipData (StarShipPtr->RaceDescPtr);
+ if (PkunkData->hPhoenix && (StarShipPtr->control & STANDARD_RATING))
+ {
+ RemoveElement (PkunkData->hPhoenix);
+ FreeElement (PkunkData->hPhoenix);
+ PkunkData->hPhoenix = 0;
+ }
+
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level <
+ StarShipPtr->RaceDescPtr->ship_info.max_energy
+ && (StarShipPtr->special_counter == 0
+ || (BYTE)TFB_Random () < 20))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ else
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+}
+
+static void pkunk_preprocess (ELEMENT *ElementPtr);
+
+static void
+new_pkunk (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ PKUNK_DATA *PkunkData;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ PkunkData = GetCustomShipData (StarShipPtr->RaceDescPtr);
+
+ ElementPtr->state_flags = APPEARING | PLAYER_SHIP | IGNORE_SIMILAR;
+ ElementPtr->mass_points = SHIP_MASS;
+ // Restore the element processing callbacks after the explosion.
+ // The callbacks were changed for the explosion sequence
+ ElementPtr->preprocess_func = PkunkData->saved_preprocess_func;
+ ElementPtr->postprocess_func = PkunkData->saved_postprocess_func;
+ ElementPtr->death_func = PkunkData->saved_death_func;
+ // preprocess_func() is called during the phoenix transition and
+ // then cleared, so we need to restore it
+ StarShipPtr->RaceDescPtr->preprocess_func = pkunk_preprocess;
+ StarShipPtr->RaceDescPtr->ship_info.crew_level = MAX_CREW;
+ StarShipPtr->RaceDescPtr->ship_info.energy_level = MAX_ENERGY;
+ /* fix vux impairment */
+ StarShipPtr->RaceDescPtr->characteristics.max_thrust = MAX_THRUST;
+ StarShipPtr->RaceDescPtr->characteristics.thrust_increment = THRUST_INCREMENT;
+ StarShipPtr->RaceDescPtr->characteristics.turn_wait = TURN_WAIT;
+ StarShipPtr->RaceDescPtr->characteristics.thrust_wait = THRUST_WAIT;
+ StarShipPtr->RaceDescPtr->characteristics.special_wait = 0;
+
+ StarShipPtr->ship_input_state = 0;
+ // Pkunk wins in a simultaneous destruction if it reincarnates
+ StarShipPtr->cur_status_flags &= PLAY_VICTORY_DITTY;
+ StarShipPtr->old_status_flags = 0;
+ StarShipPtr->energy_counter = 0;
+ StarShipPtr->weapon_counter = 0;
+ StarShipPtr->special_counter = 0;
+ ElementPtr->crew_level = 0;
+ ElementPtr->turn_wait = 0;
+ ElementPtr->thrust_wait = 0;
+ ElementPtr->life_span = NORMAL_LIFE;
+
+ StarShipPtr->ShipFacing = NORMALIZE_FACING (TFB_Random ());
+ ElementPtr->current.image.farray = StarShipPtr->RaceDescPtr->ship_data.ship;
+ ElementPtr->current.image.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship[0],
+ StarShipPtr->ShipFacing);
+ SetPrimType (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex], STAMP_PRIM);
+
+ do
+ {
+ ElementPtr->current.location.x =
+ WRAP_X (DISPLAY_ALIGN_X (TFB_Random ()));
+ ElementPtr->current.location.y =
+ WRAP_Y (DISPLAY_ALIGN_Y (TFB_Random ()));
+ } while (CalculateGravity (ElementPtr)
+ || TimeSpaceMatterConflict (ElementPtr));
+
+ // XXX: Hack: Set hTarget!=0 so that ship_preprocess() does not
+ // call ship_transition() for us.
+ ElementPtr->hTarget = StarShipPtr->hShip;
+}
+
+// This function is called when the ship dies but reincarnates.
+// The generic ship_death() function is not called for the ship in this case.
+static void
+pkunk_reincarnation_death (ELEMENT *ShipPtr)
+{
+ // Simulate ship death
+ StopAllBattleMusic ();
+ StartShipExplosion (ShipPtr, true);
+ // Once the explosion ends, we will get a brand new ship
+ ShipPtr->death_func = new_pkunk;
+}
+
+static void
+intercept_pkunk_death (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ PKUNK_DATA *PkunkData;
+ ELEMENT *ShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ PkunkData = GetCustomShipData (StarShipPtr->RaceDescPtr);
+
+ if (StarShipPtr->RaceDescPtr->ship_info.crew_level != 0)
+ { // Ship not dead yet.
+ // Keep the Phoenix element alive.
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->life_span = 1;
+ return;
+ }
+
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ // GRAVITY_MASS() indicates a warp-out here. If Pkunk dies while warping
+ // out, there is no reincarnation.
+ if (!GRAVITY_MASS (ShipPtr->mass_points + 1))
+ {
+ // XXX: Hack: Set mass_points to indicate a reincarnation to
+ // FindAliveStarShip()
+ ShipPtr->mass_points = MAX_SHIP_MASS + 1;
+ // Save the various element processing callbacks before the
+ // explosion happens, because we were not the ones who set
+ // these callbacks and they are about to be changed.
+ PkunkData->saved_preprocess_func = ShipPtr->preprocess_func;
+ PkunkData->saved_postprocess_func = ShipPtr->postprocess_func;
+ PkunkData->saved_death_func = ShipPtr->death_func;
+
+ ShipPtr->death_func = pkunk_reincarnation_death;
+ }
+ UnlockElement (StarShipPtr->hShip);
+}
+
+static void
+spawn_phoenix_trail (ELEMENT *ElementPtr)
+{
+ static const Color colorTable[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7a),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7c),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7d),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7e),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7f),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x2a),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2c),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2d),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2e),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0B, 0x00, 0x00), 0x2f),
+ };
+ const size_t colorTableCount = sizeof colorTable / sizeof colorTable[0];
+
+ ElementPtr->colorCycleIndex++;
+ if (ElementPtr->colorCycleIndex != colorTableCount)
+ {
+ ElementPtr->life_span = TRANSITION_LIFE;
+
+ SetPrimColor (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ colorTable[ElementPtr->colorCycleIndex]);
+
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->state_flags |= CHANGING;
+ } // else, the element disappears.
+}
+
+static void
+phoenix_transition (ELEMENT *ElementPtr)
+{
+ HELEMENT hShipImage;
+ ELEMENT *ShipImagePtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LockElement (StarShipPtr->hShip, &ShipImagePtr);
+
+ if (!(ShipImagePtr->state_flags & NONSOLID))
+ {
+ ElementPtr->preprocess_func = NULL;
+ }
+ else if ((hShipImage = AllocElement ()))
+ {
+ COUNT angle;
+
+ PutElement (hShipImage);
+
+ LockElement (hShipImage, &ShipImagePtr);
+ ShipImagePtr->playerNr = NEUTRAL_PLAYER_NUM;
+ ShipImagePtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ ShipImagePtr->life_span = TRANSITION_LIFE;
+ SetPrimType (&(GLOBAL (DisplayArray))[ShipImagePtr->PrimIndex],
+ STAMPFILL_PRIM);
+ SetPrimColor (
+ &(GLOBAL (DisplayArray))[ShipImagePtr->PrimIndex],
+ START_PHOENIX_COLOR);
+ ShipImagePtr->colorCycleIndex = 0;
+ ShipImagePtr->current.image = ElementPtr->current.image;
+ ShipImagePtr->current.location = ElementPtr->current.location;
+ if (!(ElementPtr->state_flags & PLAYER_SHIP))
+ {
+ angle = ElementPtr->mass_points;
+
+ ShipImagePtr->current.location.x +=
+ COSINE (angle, TRANSITION_SPEED);
+ ShipImagePtr->current.location.y +=
+ SINE (angle, TRANSITION_SPEED);
+ ElementPtr->preprocess_func = NULL;
+ }
+ else
+ {
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+
+ ShipImagePtr->current.location.x -=
+ COSINE (angle, TRANSITION_SPEED)
+ * (ElementPtr->life_span - 1);
+ ShipImagePtr->current.location.y -=
+ SINE (angle, TRANSITION_SPEED)
+ * (ElementPtr->life_span - 1);
+
+ ShipImagePtr->current.location.x =
+ WRAP_X (ShipImagePtr->current.location.x);
+ ShipImagePtr->current.location.y =
+ WRAP_Y (ShipImagePtr->current.location.y);
+ }
+
+ ShipImagePtr->mass_points = (BYTE)angle;
+ ShipImagePtr->preprocess_func = phoenix_transition;
+ ShipImagePtr->death_func = spawn_phoenix_trail;
+ SetElementStarShip (ShipImagePtr, StarShipPtr);
+
+ UnlockElement (hShipImage);
+ }
+
+ UnlockElement (StarShipPtr->hShip);
+}
+
+static void
+pkunk_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ PKUNK_DATA *PkunkData;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ PkunkData = GetCustomShipData (StarShipPtr->RaceDescPtr);
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ HELEMENT hPhoenix = 0;
+
+ if (TFB_Random () & 1)
+ hPhoenix = AllocElement ();
+
+ // The hPhoenix element is created and placed at the head of the
+ // queue so that it is preprocessed before any of the ships' elements
+ // are, and so before death_func() is called for the dead Pkunk.
+ // hPhoenix detects when the Pkunk ship dies and tweaks the ship,
+ // starting the death + reincarnation sequence.
+ if (hPhoenix)
+ {
+ ELEMENT *PhoenixPtr;
+
+ LockElement (hPhoenix, &PhoenixPtr);
+ PhoenixPtr->playerNr = ElementPtr->playerNr;
+ PhoenixPtr->state_flags = FINITE_LIFE | NONSOLID | IGNORE_SIMILAR;
+ PhoenixPtr->life_span = 1;
+
+ PhoenixPtr->death_func = intercept_pkunk_death;
+
+ SetElementStarShip (PhoenixPtr, StarShipPtr);
+
+ UnlockElement (hPhoenix);
+ InsertElement (hPhoenix, GetHeadElement ());
+ }
+ PkunkData->hPhoenix = hPhoenix;
+
+ // XXX: Hack: new_pkunk() sets hTarget!=0 which indicates a
+ // reincarnation to us.
+ if (ElementPtr->hTarget == 0)
+ {
+ // A brand new ship is preprocessed only once
+ StarShipPtr->RaceDescPtr->preprocess_func = 0;
+ }
+ else
+ { // Start the reincarnation sequence
+ COUNT angle, facing;
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1
+ ), ElementPtr);
+
+ ElementPtr->life_span = PHOENIX_LIFE;
+ SetPrimType (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ NO_PRIM);
+ ElementPtr->state_flags |= NONSOLID | FINITE_LIFE | CHANGING;
+
+ facing = StarShipPtr->ShipFacing;
+ for (angle = OCTANT; angle < FULL_CIRCLE; angle += QUADRANT)
+ {
+ StarShipPtr->ShipFacing = NORMALIZE_FACING (
+ facing + ANGLE_TO_FACING (angle)
+ );
+ phoenix_transition (ElementPtr);
+ }
+ StarShipPtr->ShipFacing = facing;
+ }
+ }
+
+ if (StarShipPtr->RaceDescPtr->preprocess_func)
+ {
+ StarShipPtr->cur_status_flags &=
+ ~(LEFT | RIGHT | THRUST | WEAPON | SPECIAL);
+
+ if (ElementPtr->life_span == NORMAL_LIFE)
+ {
+ ElementPtr->current.image.frame =
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (
+ ElementPtr->current.image.farray[0],
+ ElementPtr->current.image.frame);
+ SetPrimType (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ STAMP_PRIM);
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectFrame (ElementPtr);
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ ElementPtr->state_flags &= ~(NONSOLID | FINITE_LIFE);
+ ElementPtr->state_flags |= CHANGING;
+
+ StarShipPtr->RaceDescPtr->preprocess_func = 0;
+ }
+ }
+}
+
+static COUNT LastSound = 0;
+
+static void
+pkunk_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->RaceDescPtr->characteristics.special_wait)
+ --StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ else if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level <
+ StarShipPtr->RaceDescPtr->ship_info.max_energy)
+ {
+ COUNT CurSound;
+
+ do
+ {
+ CurSound =
+ 2 + ((COUNT)TFB_Random ()
+ % (GetSoundCount (StarShipPtr->RaceDescPtr->ship_data.ship_sounds) - 2));
+ } while (CurSound == LastSound);
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, CurSound
+ ), ElementPtr);
+ LastSound = CurSound;
+
+ DeltaEnergy (ElementPtr, SPECIAL_ENERGY_COST);
+
+ StarShipPtr->RaceDescPtr->characteristics.special_wait = SPECIAL_WAIT;
+ }
+}
+
+static void
+uninit_pkunk (RACE_DESC *pRaceDesc)
+{
+ SetCustomShipData (pRaceDesc, NULL);
+}
+
+RACE_DESC*
+init_pkunk (void)
+{
+ RACE_DESC *RaceDescPtr;
+ // The caller of this func will copy the struct
+ static RACE_DESC new_pkunk_desc;
+ PKUNK_DATA empty_data;
+ memset (&empty_data, 0, sizeof (empty_data));
+
+ pkunk_desc.uninit_func = uninit_pkunk;
+ pkunk_desc.preprocess_func = pkunk_preprocess;
+ pkunk_desc.postprocess_func = pkunk_postprocess;
+ pkunk_desc.init_weapon_func = initialize_bug_missile;
+ pkunk_desc.cyborg_control.intelligence_func = pkunk_intelligence;
+
+ /* copy initial ship settings to the new descriptor */
+ new_pkunk_desc = pkunk_desc;
+ SetCustomShipData (&new_pkunk_desc, &empty_data);
+
+ RaceDescPtr = &new_pkunk_desc;
+
+ LastSound = 0;
+ // We need to reinitialise it at least each battle, to ensure
+ // that NetPlay is synchronised if one player played another
+ // game before playing against a networked opponent.
+
+ return (RaceDescPtr);
+}
diff --git a/src/uqm/ships/pkunk/pkunk.h b/src/uqm/ships/pkunk/pkunk.h
new file mode 100644
index 0000000..91681b8
--- /dev/null
+++ b/src/uqm/ships/pkunk/pkunk.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PKUNK_H
+#define PKUNK_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_pkunk (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* PKUNK_H */
+
diff --git a/src/uqm/ships/pkunk/resinst.h b/src/uqm/ships/pkunk/resinst.h
new file mode 100644
index 0000000..9c1168e
--- /dev/null
+++ b/src/uqm/ships/pkunk/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define BUG_BIG_MASK_PMAP_ANIM "ship.pkunk.graphics.bug.large"
+#define BUG_MED_MASK_PMAP_ANIM "ship.pkunk.graphics.bug.medium"
+#define BUG_SML_MASK_PMAP_ANIM "ship.pkunk.graphics.bug.small"
+#define PKUNK_BIG_MASK_PMAP_ANIM "ship.pkunk.graphics.fury.large"
+#define PKUNK_CAPTAIN_MASK_PMAP_ANIM "ship.pkunk.graphics.captain"
+#define PKUNK_ICON_MASK_PMAP_ANIM "ship.pkunk.icons"
+#define PKUNK_MED_MASK_PMAP_ANIM "ship.pkunk.graphics.fury.medium"
+#define PKUNK_MICON_MASK_PMAP_ANIM "ship.pkunk.meleeicons"
+#define PKUNK_RACE_STRINGS "ship.pkunk.text"
+#define PKUNK_SHIP_SOUNDS "ship.pkunk.sounds"
+#define PKUNK_SML_MASK_PMAP_ANIM "ship.pkunk.graphics.fury.small"
+#define PKUNK_VICTORY_SONG "ship.pkunk.ditty"
diff --git a/src/uqm/ships/probe/Makeinfo b/src/uqm/ships/probe/Makeinfo
new file mode 100644
index 0000000..27e5093
--- /dev/null
+++ b/src/uqm/ships/probe/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="probe.c"
+uqm_HFILES="icode.h probe.h resinst.h"
diff --git a/src/uqm/ships/probe/icode.h b/src/uqm/ships/probe/icode.h
new file mode 100644
index 0000000..badab2a
--- /dev/null
+++ b/src/uqm/ships/probe/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define URQUAN_DRONE_CODE "ship.drone.code"
diff --git a/src/uqm/ships/probe/probe.c b/src/uqm/ships/probe/probe.c
new file mode 100644
index 0000000..32d9149
--- /dev/null
+++ b/src/uqm/ships/probe/probe.c
@@ -0,0 +1,118 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "probe.h"
+#include "resinst.h"
+
+#define MAX_CREW 1
+#define MAX_ENERGY 1
+#define ENERGY_REGENERATION 0
+#define WEAPON_ENERGY_COST 0
+#define SPECIAL_ENERGY_COST 0
+#define ENERGY_WAIT 0
+#define MAX_THRUST 0
+#define THRUST_INCREMENT 0
+#define TURN_WAIT 0
+#define THRUST_WAIT 0
+#define WEAPON_WAIT 0
+#define SPECIAL_WAIT 0
+
+#define SHIP_MASS 0
+
+static RACE_DESC probe_desc =
+{
+ { /* SHIP_INFO */
+ 0,
+ 0, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ 0,
+ 0,
+ URQUAN_DRONE_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 0, 0,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ NULL_RESOURCE,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ 0,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+RACE_DESC*
+init_probe (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ RaceDescPtr = &probe_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/probe/probe.h b/src/uqm/ships/probe/probe.h
new file mode 100644
index 0000000..c588970
--- /dev/null
+++ b/src/uqm/ships/probe/probe.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PROBE_H
+#define PROBE_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_probe (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* PROBE_H */
+
diff --git a/src/uqm/ships/probe/resinst.h b/src/uqm/ships/probe/resinst.h
new file mode 100644
index 0000000..5365c6d
--- /dev/null
+++ b/src/uqm/ships/probe/resinst.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define URQUAN_DRONE_MICON_MASK_PMAP_ANIM "ship.drone.meleeicons"
diff --git a/src/uqm/ships/ship.h b/src/uqm/ships/ship.h
new file mode 100644
index 0000000..6a9c6d2
--- /dev/null
+++ b/src/uqm/ships/ship.h
@@ -0,0 +1,37 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * This file contains definitions that are common to all ship files.
+ */
+
+#ifndef UQM_SHIPS_SHIP_H_
+#define UQM_SHIPS_SHIP_H_
+
+#include "uqm/collide.h"
+
+// XXX: Do we really need this one?
+//#include "reslib.h"
+#include "uqm/intel.h"
+#include "uqm/races.h"
+#include "uqm/status.h"
+#include "uqm/sounds.h"
+#include "uqm/weapon.h"
+#include "uqm/ship.h"
+
+
+#endif /* UQM_SHIPS_SHIP_H_ */
+
diff --git a/src/uqm/ships/shofixti/Makeinfo b/src/uqm/ships/shofixti/Makeinfo
new file mode 100644
index 0000000..52d9121
--- /dev/null
+++ b/src/uqm/ships/shofixti/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="shofixti.c"
+uqm_HFILES="icode.h resinst.h shofixti.h"
diff --git a/src/uqm/ships/shofixti/icode.h b/src/uqm/ships/shofixti/icode.h
new file mode 100644
index 0000000..4afe8d3
--- /dev/null
+++ b/src/uqm/ships/shofixti/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SHOFIXTI_CODE "ship.shofixti.code"
diff --git a/src/uqm/ships/shofixti/resinst.h b/src/uqm/ships/shofixti/resinst.h
new file mode 100644
index 0000000..bd95cb2
--- /dev/null
+++ b/src/uqm/ships/shofixti/resinst.h
@@ -0,0 +1,23 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define DART_BIG_MASK_PMAP_ANIM "ship.shofixti.graphics.missile.large"
+#define DART_MED_MASK_PMAP_ANIM "ship.shofixti.graphics.missile.medium"
+#define DART_SML_MASK_PMAP_ANIM "ship.shofixti.graphics.missile.small"
+#define DESTRUCT_BIG_MASK_ANIM "ship.shofixti.graphics.destruct.large"
+#define DESTRUCT_MED_MASK_ANIM "ship.shofixti.graphics.destruct.medium"
+#define DESTRUCT_SML_MASK_ANIM "ship.shofixti.graphics.destruct.small"
+#define OLDSHOF_BIG_MASK_PMAP_ANIM "ship.shofixti.graphics.oldscout.large"
+#define OLDSHOF_CAPTAIN_MASK_PMAP_ANIM "ship.shofixti.graphics.oldcaptain"
+#define OLDSHOF_MED_MASK_PMAP_ANIM "ship.shofixti.graphics.oldscout.medium"
+#define OLDSHOF_SML_MASK_PMAP_ANIM "ship.shofixti.graphics.oldscout.small"
+#define SHOFIXTI_BIG_MASK_PMAP_ANIM "ship.shofixti.graphics.scout.large"
+#define SHOFIXTI_CAPTAIN_MASK_PMAP_ANIM "ship.shofixti.graphics.captain"
+#define SHOFIXTI_ICON_MASK_PMAP_ANIM "ship.shofixti.icons"
+#define SHOFIXTI_MED_MASK_PMAP_ANIM "ship.shofixti.graphics.scout.medium"
+#define SHOFIXTI_MICON_MASK_PMAP_ANIM "ship.shofixti.meleeicons"
+#define SHOFIXTI_RACE_STRINGS "ship.shofixti.text"
+#define SHOFIXTI_SHIP_SOUNDS "ship.shofixti.sounds"
+#define SHOFIXTI_SML_MASK_PMAP_ANIM "ship.shofixti.graphics.scout.small"
+#define SHOFIXTI_VICTORY_SONG "ship.shofixti.ditty"
diff --git a/src/uqm/ships/shofixti/shofixti.c b/src/uqm/ships/shofixti/shofixti.c
new file mode 100644
index 0000000..03de57e
--- /dev/null
+++ b/src/uqm/ships/shofixti/shofixti.c
@@ -0,0 +1,521 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "shofixti.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+#include "uqm/tactrans.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 6
+#define MAX_ENERGY 4
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 9
+#define MAX_THRUST 35
+#define THRUST_INCREMENT 5
+#define TURN_WAIT 1
+#define THRUST_WAIT 0
+#define WEAPON_WAIT 3
+#define SPECIAL_WAIT 0
+#define SHIP_MASS 1
+
+// Dart Gun
+#define WEAPON_ENERGY_COST 1
+#define SHOFIXTI_OFFSET 15
+#define MISSILE_OFFSET 1
+#define MISSILE_SPEED DISPLAY_TO_WORLD (24)
+#define MISSILE_LIFE 10
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 1
+
+// Glory Device
+#define SPECIAL_ENERGY_COST 0
+#define DESTRUCT_RANGE 180
+#define MAX_DESTRUCTION (DESTRUCT_RANGE / 10)
+
+// Full game: Tanaka/Katana's damaged ships
+#define NUM_LIMPETS 3
+
+static RACE_DESC shofixti_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 5, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ SHOFIXTI_RACE_STRINGS,
+ SHOFIXTI_ICON_MASK_PMAP_ANIM,
+ SHOFIXTI_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 0, 0,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ SHOFIXTI_BIG_MASK_PMAP_ANIM,
+ SHOFIXTI_MED_MASK_PMAP_ANIM,
+ SHOFIXTI_SML_MASK_PMAP_ANIM,
+ },
+ {
+ DART_BIG_MASK_PMAP_ANIM,
+ DART_MED_MASK_PMAP_ANIM,
+ DART_SML_MASK_PMAP_ANIM,
+ },
+ {
+ DESTRUCT_BIG_MASK_ANIM,
+ DESTRUCT_MED_MASK_ANIM,
+ DESTRUCT_SML_MASK_ANIM,
+ },
+ {
+ SHOFIXTI_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ SHOFIXTI_VICTORY_SONG,
+ SHOFIXTI_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ MISSILE_SPEED * MISSILE_LIFE,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static COUNT
+initialize_standard_missile (ELEMENT *ShipPtr, HELEMENT MissileArray[])
+{
+
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = SHOFIXTI_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ MissileArray[0] = initialize_missile (&MissileBlock);
+
+ return (1);
+}
+
+static void
+destruct_preprocess (ELEMENT *ElementPtr)
+{
+#define DESTRUCT_SWITCH ((NUM_EXPLOSION_FRAMES * 3) - 3)
+ PRIMITIVE *lpPrim;
+
+ // ship_death() set the ship element's life_span to
+ // (NUM_EXPLOSION_FRAMES * 3)
+ lpPrim = &(GLOBAL (DisplayArray))[ElementPtr->PrimIndex];
+ ElementPtr->state_flags |= CHANGING;
+ if (ElementPtr->life_span > DESTRUCT_SWITCH)
+ {
+ // First, stamp-fill the ship's own element with changing colors
+ // for 3 frames. No explosion element yet.
+ SetPrimType (lpPrim, STAMPFILL_PRIM);
+ if (ElementPtr->life_span == DESTRUCT_SWITCH + 2)
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E));
+ else
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F));
+ }
+ else if (ElementPtr->life_span < DESTRUCT_SWITCH)
+ {
+ // Stamp-fill the explosion element with cycling colors for the
+ // remainder of the glory explosion frames.
+ Color color = GetPrimColor (lpPrim);
+
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E));
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C));
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x0A, 0x00), 0x06));
+ else if (sameColor (color,
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x0A, 0x00), 0x06)))
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04));
+ }
+ else
+ {
+ HELEMENT hDestruct;
+
+ SetPrimType (lpPrim, NO_PRIM);
+ // The ship's own element will not be drawn anymore but will remain
+ // alive all through the glory explosion.
+ ElementPtr->preprocess_func = NULL;
+
+ // Spawn a separate glory explosion element.
+ // XXX: Why? Why not keep using the ship's element?
+ // Is it because of conflicting state_flags, hit_points or
+ // mass_points?
+ hDestruct = AllocElement ();
+ if (hDestruct)
+ {
+ ELEMENT *DestructPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ PutElement (hDestruct);
+ LockElement (hDestruct, &DestructPtr);
+ SetElementStarShip (DestructPtr, StarShipPtr);
+ DestructPtr->hit_points = 0;
+ DestructPtr->mass_points = 0;
+ DestructPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ DestructPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ SetPrimType (&(GLOBAL (DisplayArray))[DestructPtr->PrimIndex],
+ STAMPFILL_PRIM);
+ SetPrimColor (&(GLOBAL (DisplayArray))[DestructPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F));
+ DestructPtr->current.image.farray =
+ StarShipPtr->RaceDescPtr->ship_data.special;
+ DestructPtr->current.image.frame =
+ StarShipPtr->RaceDescPtr->ship_data.special[0];
+ DestructPtr->life_span = GetFrameCount (
+ DestructPtr->current.image.frame);
+ DestructPtr->current.location = ElementPtr->current.location;
+ DestructPtr->preprocess_func = destruct_preprocess;
+ DestructPtr->postprocess_func = NULL;
+ DestructPtr->death_func = NULL;
+ ZeroVelocityComponents (&DestructPtr->velocity);
+ UnlockElement (hDestruct);
+ }
+ }
+}
+
+/* In order to detect any Orz Marines that have boarded the ship
+ when it self-destructs, we'll need to see some Orz functions */
+#include "../orz/orz.h"
+#define ORZ_MARINE(ptr) (ptr->preprocess_func == intruder_preprocess && \
+ ptr->collision_func == marine_collision)
+
+static void
+self_destruct_kill_objects (ELEMENT *ElementPtr)
+{
+ // This is called during PostProcessQueue(), close to or at the end,
+ // for the temporary destruct element to apply the effects of glory
+ // explosion. The effects are not seen until the next frame.
+ HELEMENT hElement, hNextElement;
+
+ for (hElement = GetHeadElement (); hElement != 0; hElement = hNextElement)
+ {
+ ELEMENT *ObjPtr;
+ SIZE delta_x, delta_y;
+ DWORD dist;
+
+ LockElement (hElement, &ObjPtr);
+ hNextElement = GetSuccElement (ObjPtr);
+
+ if (!CollidingElement (ObjPtr) && !ORZ_MARINE (ObjPtr))
+ {
+ UnlockElement (hElement);
+ continue;
+ }
+
+ delta_x = ObjPtr->next.location.x - ElementPtr->next.location.x;
+ if (delta_x < 0)
+ delta_x = -delta_x;
+ delta_y = ObjPtr->next.location.y - ElementPtr->next.location.y;
+ if (delta_y < 0)
+ delta_y = -delta_y;
+ delta_x = WORLD_TO_DISPLAY (delta_x);
+ delta_y = WORLD_TO_DISPLAY (delta_y);
+ dist = delta_x * delta_x + delta_y * delta_y;
+ if (delta_x <= DESTRUCT_RANGE && delta_y <= DESTRUCT_RANGE
+ && dist <= DESTRUCT_RANGE * DESTRUCT_RANGE)
+ {
+ int destruction = 1 + MAX_DESTRUCTION *
+ (DESTRUCT_RANGE - square_root (dist)) / DESTRUCT_RANGE;
+
+ // XXX: Why not simply call do_damage()?
+ if (ObjPtr->state_flags & PLAYER_SHIP)
+ {
+ if (!DeltaCrew (ObjPtr, -destruction))
+ ObjPtr->life_span = 0;
+ }
+ else if (!GRAVITY_MASS (ObjPtr->mass_points))
+ {
+ if (destruction < ObjPtr->hit_points)
+ ObjPtr->hit_points -= destruction;
+ else
+ {
+ ObjPtr->hit_points = 0;
+ ObjPtr->life_span = 0;
+ }
+ }
+ }
+
+ UnlockElement (hElement);
+ }
+}
+
+// This function is called when the ship dies via Glory Device.
+// The generic ship_death() function is not called for the ship in this case.
+static void
+shofixti_destruct_death (ELEMENT *ShipPtr)
+{
+ STARSHIP *StarShip;
+ STARSHIP *winner;
+
+ GetElementStarShip (ShipPtr, &StarShip);
+
+ StopAllBattleMusic ();
+
+ StartShipExplosion (ShipPtr, false);
+ // We process the explosion ourselves because it is different
+ ShipPtr->preprocess_func = destruct_preprocess;
+
+ PlaySound (SetAbsSoundIndex (StarShip->RaceDescPtr->ship_data.ship_sounds,
+ 1), CalcSoundPosition (ShipPtr), ShipPtr, GAME_SOUND_PRIORITY + 1);
+
+ winner = GetWinnerStarShip ();
+ if (winner == NULL)
+ { // No winner determined yet
+ winner = FindAliveStarShip (ShipPtr);
+ if (winner == NULL)
+ { // No ships left alive after the Glory Device thus Shofixti wins
+ winner = StarShip;
+ }
+ SetWinnerStarShip (winner);
+ }
+ else if (winner == StarShip)
+ { // This ship is the winner
+ // It may have self-destructed before the ditty started playing,
+ // and in that case, there should be no ditty
+ StarShip->cur_status_flags &= ~PLAY_VICTORY_DITTY;
+ }
+ RecordShipDeath (ShipPtr);
+}
+
+static void
+self_destruct (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ HELEMENT hDestruct;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ // Spawn a temporary element, which dies in this same frame, in order
+ // to defer the effects of the glory explosion.
+ // It will be the last element (or one of the last) for which the
+ // death_func() will be called from PostProcessQueue() in this frame.
+ // XXX: Why at the end? Why not just do it now?
+ hDestruct = AllocElement ();
+ if (hDestruct)
+ {
+ ELEMENT *DestructPtr;
+
+ LockElement (hDestruct, &DestructPtr);
+ DestructPtr->playerNr = ElementPtr->playerNr;
+ DestructPtr->state_flags = APPEARING | NONSOLID | FINITE_LIFE;
+ DestructPtr->next.location = ElementPtr->next.location;
+ DestructPtr->life_span = 0;
+ DestructPtr->pParent = ElementPtr->pParent;
+ DestructPtr->hTarget = 0;
+
+ DestructPtr->death_func = self_destruct_kill_objects;
+
+ UnlockElement (hDestruct);
+
+ PutElement (hDestruct);
+ }
+
+ // Must kill off the remaining crew ourselves
+ DeltaCrew (ElementPtr, -(int)ElementPtr->crew_level);
+
+ ElementPtr->state_flags |= NONSOLID;
+ ElementPtr->life_span = 0;
+ // The ship is now dead. It's death_func, i.e. shofixti_destruct_death(),
+ // will be called the next frame.
+ ElementPtr->death_func = shofixti_destruct_death;
+}
+
+static void
+shofixti_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ STARSHIP *StarShipPtr;
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->special_counter != 0)
+ return;
+
+ if (StarShipPtr->ship_input_state & SPECIAL)
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ else
+ {
+ EVALUATE_DESC *lpWeaponEvalDesc;
+ EVALUATE_DESC *lpShipEvalDesc;
+
+ lpWeaponEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ lpShipEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (StarShipPtr->RaceDescPtr->ship_data.special[0]
+ && (GetFrameCount (StarShipPtr->RaceDescPtr->ship_data.
+ captain_control.special)
+ - GetFrameIndex (StarShipPtr->RaceDescPtr->ship_data.
+ captain_control.special) > 5
+ || (lpShipEvalDesc->ObjectPtr != NULL
+ && lpShipEvalDesc->which_turn <= 4)
+ || (lpWeaponEvalDesc->ObjectPtr != NULL
+ /* means IMMEDIATE WEAPON */
+ && (((lpWeaponEvalDesc->ObjectPtr->state_flags & PLAYER_SHIP)
+ && ShipPtr->crew_level == 1)
+ || (PlotIntercept (lpWeaponEvalDesc->ObjectPtr, ShipPtr, 2, 0)
+ && lpWeaponEvalDesc->ObjectPtr->mass_points >=
+ ShipPtr->crew_level
+ && (TFB_Random () & 1))))))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+}
+
+static void
+shofixti_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags
+ ^ StarShipPtr->old_status_flags) & SPECIAL)
+ {
+ StarShipPtr->RaceDescPtr->ship_data.captain_control.special =
+ IncFrameIndex (StarShipPtr->RaceDescPtr->ship_data.
+ captain_control.special);
+ if (GetFrameCount (StarShipPtr->RaceDescPtr->ship_data.
+ captain_control.special)
+ - GetFrameIndex (StarShipPtr->RaceDescPtr->ship_data.
+ captain_control.special) == 3)
+ self_destruct (ElementPtr);
+ }
+}
+
+RACE_DESC*
+init_shofixti (void)
+{
+ RACE_DESC *RaceDescPtr;
+ // The caller of this func will copy the struct
+ static RACE_DESC new_shofixti_desc;
+
+ shofixti_desc.postprocess_func = shofixti_postprocess;
+ shofixti_desc.init_weapon_func = initialize_standard_missile;
+ shofixti_desc.cyborg_control.intelligence_func = shofixti_intelligence;
+
+ new_shofixti_desc = shofixti_desc;
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER
+ && !GET_GAME_STATE (SHOFIXTI_RECRUITED))
+ {
+ // Tanaka/Katana flies in a damaged ship.
+ COUNT i;
+
+ new_shofixti_desc.ship_data.ship_rsc[0] = OLDSHOF_BIG_MASK_PMAP_ANIM;
+ new_shofixti_desc.ship_data.ship_rsc[1] = OLDSHOF_MED_MASK_PMAP_ANIM;
+ new_shofixti_desc.ship_data.ship_rsc[2] = OLDSHOF_SML_MASK_PMAP_ANIM;
+ new_shofixti_desc.ship_data.special_rsc[0] = NULL_RESOURCE;
+ new_shofixti_desc.ship_data.special_rsc[1] = NULL_RESOURCE;
+ new_shofixti_desc.ship_data.special_rsc[2] = NULL_RESOURCE;
+ new_shofixti_desc.ship_data.captain_control.captain_rsc =
+ OLDSHOF_CAPTAIN_MASK_PMAP_ANIM;
+
+ /* Weapon doesn't work as well */
+ new_shofixti_desc.characteristics.weapon_wait = 10;
+
+ /* Simulate VUX limpets */
+ for (i = 0; i < NUM_LIMPETS; ++i)
+ {
+ if (++new_shofixti_desc.characteristics.turn_wait == 0)
+ --new_shofixti_desc.characteristics.turn_wait;
+ if (++new_shofixti_desc.characteristics.thrust_wait == 0)
+ --new_shofixti_desc.characteristics.thrust_wait;
+
+/* This should be the same as MIN_THRUST_INCREMENT in vux.c */
+#define MIN_THRUST_INCREMENT DISPLAY_TO_WORLD (1)
+
+ if (new_shofixti_desc.characteristics.thrust_increment <=
+ MIN_THRUST_INCREMENT)
+ {
+ new_shofixti_desc.characteristics.max_thrust =
+ new_shofixti_desc.characteristics.thrust_increment << 1;
+ }
+ else
+ {
+ COUNT num_thrusts;
+
+ num_thrusts = new_shofixti_desc.characteristics.max_thrust /
+ new_shofixti_desc.characteristics.thrust_increment;
+ --new_shofixti_desc.characteristics.thrust_increment;
+ new_shofixti_desc.characteristics.max_thrust =
+ new_shofixti_desc.characteristics.thrust_increment *
+ num_thrusts;
+ }
+ }
+ }
+
+ RaceDescPtr = &new_shofixti_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/shofixti/shofixti.h b/src/uqm/ships/shofixti/shofixti.h
new file mode 100644
index 0000000..e4fdd0c
--- /dev/null
+++ b/src/uqm/ships/shofixti/shofixti.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SHOFIXTI_H
+#define SHOFIXTI_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_shofixti (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SHOFIXTI_H */
+
diff --git a/src/uqm/ships/sis_ship/Makeinfo b/src/uqm/ships/sis_ship/Makeinfo
new file mode 100644
index 0000000..540e3a6
--- /dev/null
+++ b/src/uqm/ships/sis_ship/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="sis_ship.c"
+uqm_HFILES="icode.h resinst.h sis_ship.h"
diff --git a/src/uqm/ships/sis_ship/icode.h b/src/uqm/ships/sis_ship/icode.h
new file mode 100644
index 0000000..3d57449
--- /dev/null
+++ b/src/uqm/ships/sis_ship/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SIS_CODE "ship.flagship.code"
diff --git a/src/uqm/ships/sis_ship/resinst.h b/src/uqm/ships/sis_ship/resinst.h
new file mode 100644
index 0000000..cf8d074
--- /dev/null
+++ b/src/uqm/ships/sis_ship/resinst.h
@@ -0,0 +1,15 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define BLASTER_BIG_MASK_PMAP_ANIM "ship.flagship.graphics.blaster.large"
+#define BLASTER_MED_MASK_PMAP_ANIM "ship.flagship.graphics.blaster.medium"
+#define BLASTER_SML_MASK_PMAP_ANIM "ship.flagship.graphics.blaster.small"
+#define SIS_BIG_MASK_PMAP_ANIM "ship.flagship.graphics.flagship.large"
+#define SIS_CAPTAIN_MASK_PMAP_ANIM "ship.flagship.graphics.captain"
+#define SIS_HYPER_MASK_PMAP_ANIM "ship.flagship.graphics.hyperspace"
+#define SIS_ICON_MASK_PMAP_ANIM "ship.flagship.icons"
+#define SIS_MED_MASK_PMAP_ANIM "ship.flagship.graphics.flagship.medium"
+#define SIS_SHIP_SOUNDS "ship.flagship.sounds"
+#define SIS_SML_MASK_PMAP_ANIM "ship.flagship.graphics.flagship.small"
+#define SIS_VICTORY_SONG "ship.flagship.ditty"
diff --git a/src/uqm/ships/sis_ship/sis_ship.c b/src/uqm/ships/sis_ship/sis_ship.c
new file mode 100644
index 0000000..d589827
--- /dev/null
+++ b/src/uqm/ships/sis_ship/sis_ship.c
@@ -0,0 +1,1002 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "sis_ship.h"
+#include "resinst.h"
+
+#include "uqm/colors.h"
+#include "uqm/controls.h"
+#include "uqm/globdata.h"
+#include "uqm/hyper.h"
+#include "libs/mathlib.h"
+
+/* Core characteristics.
+ * All of these are changed at init time by some module, except for
+ * MAX_ENERGY, THRUST_INCREMENT, and SHIP_MASS. */
+
+#define MAX_CREW MAX_CREW_SIZE
+ /* This value gets thrown out - actual max crew is determined by the
+ * number of crew pods. The minimum value is 1 (just the Captain). */
+
+#define MAX_ENERGY MAX_ENERGY_SIZE
+#define ENERGY_REGENERATION 1
+ /* Shiva furnaces increase this by 1 each. */
+#define SHIVA_ENERGY_REGEN_INC 1
+
+#define ENERGY_WAIT 10
+ /* Dynamos decrease this by 2 each, to a minimum of 4. */
+#define MIN_ENERGY_WAIT 4
+#define DYNAMO_UNIT_ENERGY_WAIT_DEC 2
+
+#define MAX_THRUST 10
+ /* Thrusters increase this and decrease THRUST_WAIT based on
+ * THRUST_INCREMENT, see InitDriveSlots near the bottom of this file
+ * for details. */
+#define THRUST_INCREMENT 4
+#define THRUST_WAIT 6
+#define TURN_WAIT 17
+ /* Turning jets decrease by 2 each */
+#define SHIP_MASS MAX_SHIP_MASS
+
+
+/* Primary weapon - energy cost and damage change at init time based on
+ * the number and type of weapon modules installed. */
+
+#define BLASTER_DAMAGE 2
+ /* This is the damage value for the basic ion bolt guns. Fusion
+ * blasters and hellbore cannons end up doing (BLASTER_DAMAGE * 2)
+ * and (BLASTER_DAMAGE * 3) damage, respectively, but this depends
+ * on enum values. */
+
+#define BLASTER_HITS 2 /* Hitpoints for ion bolt guns, see BLASTER_DAMAGE */
+
+#define WEAPON_ENERGY_COST 1
+ /* This value gets thrown out and reset in an ugly manner based on
+ * the enum that is used for module IDs. Bigger gun = higher value.
+ */
+#define WEAPON_WAIT 6
+#define BLASTER_SPEED DISPLAY_TO_WORLD (24)
+#define BLASTER_LIFE 12
+ /* This value is greatly increased, based in part on the enum used
+ * for module IDs (bigger gun == longer life). See the first half of
+ * InitWeaponSlots */
+#define MAX_TRACKING 3
+#define TRACKER_ENERGY_COST 3
+#define BLASTER_OFFSET 8
+#define SIS_VERT_OFFSET 28
+ /* Used for foward, spread, and rear slots */
+#define SIS_HORZ_OFFSET 20
+ /* Used for side slot */
+
+
+/* Secondary weapon */
+#define SPECIAL_ENERGY_COST 0
+ /* Increased by 1 for each point defense module */
+#define ANTIMISSILE_ENERGY_INC 1
+#define SPECIAL_WAIT 9
+#define LASER_RANGE (UWORD)100
+#define MAX_DEFENSE 8
+
+
+static RACE_DESC sis_desc =
+{
+ { /* SHIP_INFO */
+ 0,
+ 16, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ NULL_RESOURCE,
+ SIS_ICON_MASK_PMAP_ANIM,
+ NULL_RESOURCE,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 0, 0,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ SIS_BIG_MASK_PMAP_ANIM,
+ SIS_MED_MASK_PMAP_ANIM,
+ SIS_SML_MASK_PMAP_ANIM,
+ },
+ {
+ BLASTER_BIG_MASK_PMAP_ANIM,
+ BLASTER_MED_MASK_PMAP_ANIM,
+ BLASTER_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ SIS_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ SIS_VICTORY_SONG,
+ SIS_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ BLASTER_SPEED * BLASTER_LIFE,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+// Private per-instance SIS data
+typedef struct
+{
+ COUNT num_trackers;
+ COUNT num_blasters;
+ MISSILE_BLOCK MissileBlock[6];
+
+} SIS_DATA;
+
+static void InitWeaponSlots (RACE_DESC *RaceDescPtr,
+ const BYTE *ModuleSlots);
+static void InitModuleSlots (RACE_DESC *RaceDescPtr,
+ const BYTE *ModuleSlots);
+static void InitDriveSlots (RACE_DESC *RaceDescPtr,
+ const BYTE *DriveSlots);
+static void InitJetSlots (RACE_DESC *RaceDescPtr,
+ const BYTE *JetSlots);
+static void uninit_sis (RACE_DESC *pRaceDesc);
+
+
+// Local typedef
+typedef SIS_DATA CustomShipData_t;
+
+// Retrieve race-specific ship data from a race desc
+static CustomShipData_t *
+GetCustomShipData (RACE_DESC *pRaceDesc)
+{
+ return pRaceDesc->data;
+}
+
+// Set the race-specific data in a race desc
+// (Re)Allocates its own storage for the data.
+static void
+SetCustomShipData (RACE_DESC *pRaceDesc, const CustomShipData_t *data)
+{
+ if (pRaceDesc->data == data)
+ return; // no-op
+
+ if (pRaceDesc->data) // Out with the old
+ {
+ HFree (pRaceDesc->data);
+ pRaceDesc->data = NULL;
+ }
+
+ if (data) // In with the new
+ {
+ CustomShipData_t* newData = HMalloc (sizeof (*data));
+ *newData = *data;
+ pRaceDesc->data = newData;
+ }
+}
+
+static void
+sis_hyper_preprocess (ELEMENT *ElementPtr)
+{
+ SIZE udx = 0, udy = 0;
+ SIZE dx = 0, dy = 0;
+ SIZE AccelerateDirection;
+ STARSHIP *StarShipPtr;
+
+ if (ElementPtr->state_flags & APPEARING)
+ ElementPtr->velocity = GLOBAL (velocity);
+
+ AccelerateDirection = 0;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ ++StarShipPtr->weapon_counter; /* no shooting in hyperspace! */
+ if ((GLOBAL (autopilot)).x == ~0
+ || (GLOBAL (autopilot)).y == ~0
+ || (StarShipPtr->cur_status_flags & (LEFT | RIGHT | THRUST)))
+ {
+LeaveAutoPilot:
+ (GLOBAL (autopilot)).x =
+ (GLOBAL (autopilot)).y = ~0;
+ if (!(StarShipPtr->cur_status_flags & THRUST)
+ || (GLOBAL_SIS (FuelOnBoard) == 0
+ && GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1))
+ {
+ AccelerateDirection = -1;
+ GetCurrentVelocityComponents (&ElementPtr->velocity,
+ &dx, &dy);
+ udx = dx << 4;
+ udy = dy << 4;
+
+ StarShipPtr->cur_status_flags &= ~THRUST;
+ }
+ }
+ else
+ {
+ SIZE facing;
+ POINT universe;
+
+ universe.x = LOGX_TO_UNIVERSE (GLOBAL_SIS (log_x));
+ universe.y = LOGY_TO_UNIVERSE (GLOBAL_SIS (log_y));
+ udx = (GLOBAL (autopilot)).x - universe.x;
+ udy = -((GLOBAL (autopilot)).y - universe.y);
+ if ((dx = udx) < 0)
+ dx = -dx;
+ if ((dy = udy) < 0)
+ dy = -dy;
+ if (dx <= 1 && dy <= 1)
+ goto LeaveAutoPilot;
+
+ facing = NORMALIZE_FACING (ANGLE_TO_FACING (ARCTAN (udx, udy)));
+
+ /* This prevents ship from flying backwards on auto-pilot.
+ * It could also theoretically abort autopilot in a bad savegame */
+ if ((StarShipPtr->cur_status_flags & SHIP_AT_MAX_SPEED)
+ /*|| (ElementPtr->state_flags & APPEARING)*/ )
+ {
+ if (NORMALIZE_FACING (StarShipPtr->ShipFacing
+ + ANGLE_TO_FACING (QUADRANT)
+ - facing) > ANGLE_TO_FACING (HALF_CIRCLE))
+ goto LeaveAutoPilot;
+
+ facing = StarShipPtr->ShipFacing;
+ }
+ else if ((int)facing != (int)StarShipPtr->ShipFacing
+ && ElementPtr->turn_wait == 0)
+ {
+ if (NORMALIZE_FACING (
+ StarShipPtr->ShipFacing - facing
+ ) >= ANGLE_TO_FACING (HALF_CIRCLE))
+ {
+ facing = NORMALIZE_FACING (facing - 1);
+ StarShipPtr->cur_status_flags |= RIGHT;
+ }
+ else if ((int)StarShipPtr->ShipFacing != (int)facing)
+ {
+ facing = NORMALIZE_FACING (facing + 1);
+ StarShipPtr->cur_status_flags |= LEFT;
+ }
+
+ if ((int)facing == (int)StarShipPtr->ShipFacing)
+ {
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ }
+ }
+
+ GetCurrentVelocityComponents (&ElementPtr->velocity, &dx, &dy);
+ if ((GLOBAL_SIS (FuelOnBoard)
+ || GET_GAME_STATE (ARILOU_SPACE_SIDE) > 1)
+ && (int)facing == (int)StarShipPtr->ShipFacing)
+ {
+ StarShipPtr->cur_status_flags |= SHIP_AT_MAX_SPEED;
+ AccelerateDirection = 1;
+ }
+ else
+ {
+ AccelerateDirection = -1;
+ udx = dx << 4;
+ udy = dy << 4;
+ }
+ }
+
+ if (ElementPtr->thrust_wait == 0 && AccelerateDirection)
+ {
+ COUNT dist;
+ SIZE speed, velocity_increment;
+
+ velocity_increment = WORLD_TO_VELOCITY (
+ StarShipPtr->RaceDescPtr->characteristics.thrust_increment);
+
+ if ((dist = square_root ((long)udx * udx + (long)udy * udy)) == 0)
+ dist = 1; /* prevent divide by zero */
+
+ speed = square_root ((long)dx * dx + (long)dy * dy);
+ if (AccelerateDirection < 0)
+ {
+ dy = (speed / velocity_increment - 1) * velocity_increment;
+ if (dy < speed - velocity_increment)
+ dy = speed - velocity_increment;
+ if ((speed = dy) < 0)
+ speed = 0;
+
+ StarShipPtr->cur_status_flags &= ~SHIP_AT_MAX_SPEED;
+ }
+ else
+ {
+ SIZE max_velocity;
+
+ AccelerateDirection = 0;
+
+ max_velocity = WORLD_TO_VELOCITY (
+ StarShipPtr->RaceDescPtr->characteristics.max_thrust);
+
+ dy = (speed / velocity_increment + 1)
+ * velocity_increment;
+ if (dy < speed + velocity_increment)
+ dy = speed + velocity_increment;
+ if ((speed = dy) > max_velocity)
+ {
+ speed = max_velocity;
+ StarShipPtr->cur_status_flags |= SHIP_AT_MAX_SPEED;
+ }
+ }
+
+ dx = (SIZE)((long)udx * speed / (long)dist);
+ dy = (SIZE)((long)udy * speed / (long)dist);
+ SetVelocityComponents (&ElementPtr->velocity, dx, dy);
+
+ ElementPtr->thrust_wait =
+ StarShipPtr->RaceDescPtr->characteristics.thrust_wait;
+ }
+}
+
+static void
+sis_hyper_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GLOBAL (velocity) = ElementPtr->velocity;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (((StarShipPtr->cur_status_flags & WEAPON) ||
+ PulsedInputState.menu[KEY_MENU_CANCEL])
+ && StarShipPtr->special_counter == 0)
+ {
+#define MENU_DELAY 10
+ HyperspaceMenu ();
+ StarShipPtr->cur_status_flags &= ~SHIP_AT_MAX_SPEED;
+ StarShipPtr->special_counter = MENU_DELAY;
+ }
+}
+
+static void
+spawn_point_defense (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ {
+ HELEMENT hDefense;
+
+ hDefense = AllocElement ();
+ if (hDefense)
+ {
+ ELEMENT *DefensePtr;
+
+ LockElement (hDefense, &DefensePtr);
+ DefensePtr->playerNr = ElementPtr->playerNr;
+ DefensePtr->state_flags = APPEARING | NONSOLID | FINITE_LIFE;
+ DefensePtr->death_func = spawn_point_defense;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ SetElementStarShip (DefensePtr, StarShipPtr);
+ UnlockElement (hDefense);
+
+ PutElement (hDefense);
+ }
+ }
+ else
+ {
+ BOOLEAN PaidFor;
+ HELEMENT hObject, hNextObject;
+ ELEMENT *ShipPtr;
+ Color LaserColor;
+ static const Color ColorRange[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7F),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7E),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7C),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x18, 0x00), 0x79),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1C, 0x00), 0x78),
+ };
+
+ PaidFor = FALSE;
+
+ LaserColor = ColorRange[
+ StarShipPtr->RaceDescPtr->characteristics.special_energy_cost
+ ];
+ LockElement (StarShipPtr->hShip, &ShipPtr);
+ for (hObject = GetTailElement (); hObject; hObject = hNextObject)
+ {
+ ELEMENT *ObjectPtr;
+
+ LockElement (hObject, &ObjectPtr);
+ hNextObject = GetPredElement (ObjectPtr);
+ if (ObjectPtr != ShipPtr && CollidingElement (ObjectPtr) &&
+ !OBJECT_CLOAKED (ObjectPtr))
+ {
+ SIZE delta_x, delta_y;
+
+ delta_x = ObjectPtr->next.location.x -
+ ShipPtr->next.location.x;
+ delta_y = ObjectPtr->next.location.y -
+ ShipPtr->next.location.y;
+ if (delta_x < 0)
+ delta_x = -delta_x;
+ if (delta_y < 0)
+ delta_y = -delta_y;
+ delta_x = WORLD_TO_DISPLAY (delta_x);
+ delta_y = WORLD_TO_DISPLAY (delta_y);
+ if ((UWORD)delta_x <= LASER_RANGE &&
+ (UWORD)delta_y <= LASER_RANGE &&
+ (UWORD)delta_x * (UWORD)delta_x +
+ (UWORD)delta_y * (UWORD)delta_y <=
+ LASER_RANGE * LASER_RANGE)
+ {
+ HELEMENT hPointDefense;
+ LASER_BLOCK LaserBlock;
+
+ if (!PaidFor)
+ {
+ if (!DeltaEnergy (ShipPtr,
+ -(StarShipPtr->RaceDescPtr->characteristics.special_energy_cost
+ << 2)))
+ break;
+
+ ProcessSound (SetAbsSoundIndex (
+ /* POINT_DEFENSE_LASER */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ PaidFor = TRUE;
+ }
+
+ LaserBlock.cx = ShipPtr->next.location.x;
+ LaserBlock.cy = ShipPtr->next.location.y;
+ LaserBlock.face = 0;
+ LaserBlock.ex = ObjectPtr->next.location.x
+ - ShipPtr->next.location.x;
+ LaserBlock.ey = ObjectPtr->next.location.y
+ - ShipPtr->next.location.y;
+ LaserBlock.sender = ShipPtr->playerNr;
+ LaserBlock.flags = IGNORE_SIMILAR;
+ LaserBlock.pixoffs = 0;
+ LaserBlock.color = LaserColor;
+ hPointDefense = initialize_laser (&LaserBlock);
+ if (hPointDefense)
+ {
+ ELEMENT *PDPtr;
+
+ LockElement (hPointDefense, &PDPtr);
+ PDPtr->mass_points =
+ StarShipPtr->RaceDescPtr->characteristics.special_energy_cost;
+ SetElementStarShip (PDPtr, StarShipPtr);
+ PDPtr->hTarget = 0;
+ UnlockElement (hPointDefense);
+
+ PutElement (hPointDefense);
+ }
+ }
+ }
+ UnlockElement (hObject);
+ }
+ UnlockElement (StarShipPtr->hShip);
+ }
+}
+
+static void
+sis_battle_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->RaceDescPtr->characteristics.special_energy_cost == 0)
+ {
+ StarShipPtr->cur_status_flags &= ~SPECIAL;
+ StarShipPtr->special_counter = 2;
+ }
+ if (!(StarShipPtr->RaceDescPtr->ship_info.ship_flags
+ & (FIRES_FORE | FIRES_RIGHT | FIRES_AFT | FIRES_LEFT)))
+ {
+ StarShipPtr->cur_status_flags &= ~WEAPON;
+ StarShipPtr->weapon_counter = 2;
+ }
+}
+
+static void
+sis_battle_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && StarShipPtr->RaceDescPtr->characteristics.special_energy_cost)
+ {
+ spawn_point_defense (ElementPtr);
+ }
+}
+
+static void
+blaster_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ HELEMENT hBlastElement;
+
+ hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ if (hBlastElement)
+ {
+ ELEMENT *BlastElementPtr;
+
+ LockElement (hBlastElement, &BlastElementPtr);
+ switch (ElementPtr0->mass_points)
+ {
+ case BLASTER_DAMAGE * 1:
+ BlastElementPtr->life_span = 2;
+ BlastElementPtr->current.image.frame =
+ SetAbsFrameIndex (ElementPtr0->current.image.frame, 0);
+ BlastElementPtr->preprocess_func = NULL;
+ break;
+ case BLASTER_DAMAGE * 2:
+ BlastElementPtr->life_span = 6;
+ BlastElementPtr->current.image.frame =
+ IncFrameIndex (ElementPtr0->current.image.frame);
+ break;
+ case BLASTER_DAMAGE * 3:
+ BlastElementPtr->life_span = 7;
+ BlastElementPtr->current.image.frame =
+ SetAbsFrameIndex (ElementPtr0->current.image.frame, 20);
+ break;
+ }
+ UnlockElement (hBlastElement);
+ }
+}
+
+static void
+blaster_preprocess (ELEMENT *ElementPtr)
+{
+ BYTE wait;
+
+ switch (ElementPtr->mass_points)
+ {
+ case BLASTER_DAMAGE * 1:
+ if (GetFrameIndex (ElementPtr->current.image.frame) < 8)
+ {
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+ }
+ break;
+ case BLASTER_DAMAGE * 3:
+ if (GetFrameIndex (ElementPtr->current.image.frame) < 19)
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->current.image.frame);
+ else
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame, 16);
+ ElementPtr->state_flags |= CHANGING;
+ break;
+ }
+
+ if (LONIBBLE (ElementPtr->turn_wait))
+ --ElementPtr->turn_wait;
+ else if ((wait = HINIBBLE (ElementPtr->turn_wait)))
+ {
+ COUNT facing;
+
+ facing = NORMALIZE_FACING (ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&ElementPtr->velocity)));
+ if (TrackShip (ElementPtr, &facing) > 0)
+ SetVelocityVector (&ElementPtr->velocity, BLASTER_SPEED, facing);
+
+ ElementPtr->turn_wait = MAKE_BYTE (wait, wait);
+ }
+}
+
+static COUNT
+initialize_blasters (ELEMENT *ShipPtr, HELEMENT BlasterArray[])
+{
+ BYTE nt;
+ COUNT i;
+ STARSHIP *StarShipPtr;
+ SIS_DATA *SisData;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ SisData = GetCustomShipData (StarShipPtr->RaceDescPtr);
+
+ nt = (BYTE)((4 - SisData->num_trackers) & 3);
+
+ for (i = 0; i < SisData->num_blasters; ++i)
+ {
+ MISSILE_BLOCK MissileBlock = SisData->MissileBlock[i];
+
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.face = NORMALIZE_FACING (StarShipPtr->ShipFacing
+ + MissileBlock.face);
+
+ BlasterArray[i] = initialize_missile (&MissileBlock);
+ if (BlasterArray[i])
+ {
+ ELEMENT *BlasterPtr;
+
+ LockElement (BlasterArray[i], &BlasterPtr);
+ BlasterPtr->collision_func = blaster_collision;
+ BlasterPtr->turn_wait = MAKE_BYTE (nt, nt);
+ UnlockElement (BlasterArray[i]);
+ }
+
+ }
+
+ return SisData->num_blasters;
+}
+
+static void
+sis_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+ SIS_DATA *SisData;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ SisData = GetCustomShipData (StarShipPtr->RaceDescPtr);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (lpEvalDesc->ObjectPtr)
+ {
+ if (StarShipPtr->RaceDescPtr->characteristics.special_energy_cost)
+ {
+ if (StarShipPtr->special_counter == 0
+ && ((lpEvalDesc->ObjectPtr
+ && lpEvalDesc->which_turn <= 2)
+ || (ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr != NULL
+ && ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn <= 4)))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ else
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ lpEvalDesc->ObjectPtr = NULL;
+ }
+ else if (MANEUVERABILITY (&StarShipPtr->RaceDescPtr->cyborg_control)
+ < MEDIUM_SHIP
+ && lpEvalDesc->MoveState == ENTICE
+ && (!(lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT)
+ || lpEvalDesc->which_turn <= 8)
+ && (!(lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE)
+ || (lpEvalDesc->ObjectPtr->mass_points >= 4
+ && lpEvalDesc->which_turn == 2
+ && ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn > 16)))
+ lpEvalDesc->MoveState = PURSUE;
+ }
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (SisData->num_trackers
+ && StarShipPtr->weapon_counter == 0
+ && !(StarShipPtr->ship_input_state & WEAPON)
+ && lpEvalDesc->ObjectPtr
+ && lpEvalDesc->which_turn <= 16)
+ {
+ COUNT direction_facing;
+ SIZE delta_x, delta_y;
+ UWORD fire_flags, ship_flags;
+ COUNT facing;
+
+ delta_x = lpEvalDesc->ObjectPtr->current.location.x
+ - ShipPtr->current.location.x;
+ delta_y = lpEvalDesc->ObjectPtr->current.location.y
+ - ShipPtr->current.location.y;
+ direction_facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y)));
+
+ ship_flags = StarShipPtr->RaceDescPtr->ship_info.ship_flags;
+ for (fire_flags = FIRES_FORE, facing = StarShipPtr->ShipFacing;
+ fire_flags <= FIRES_LEFT;
+ fire_flags <<= 1, facing += QUADRANT)
+ {
+ if ((ship_flags & fire_flags) && NORMALIZE_FACING (
+ direction_facing - facing + ANGLE_TO_FACING (OCTANT)
+ ) <= ANGLE_TO_FACING (QUADRANT))
+ {
+ StarShipPtr->ship_input_state |= WEAPON;
+ break;
+ }
+ }
+ }
+}
+
+static void
+InitWeaponSlots (RACE_DESC *RaceDescPtr, const BYTE *ModuleSlots)
+{
+ COUNT i;
+ SIS_DATA *SisData = GetCustomShipData (RaceDescPtr);
+ MISSILE_BLOCK *lpMB = SisData->MissileBlock;
+
+ SisData->num_blasters = 0;
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ COUNT which_gun;
+
+ if (i == 3)
+ i = NUM_MODULE_SLOTS - 1;
+
+ which_gun = ModuleSlots[(NUM_MODULE_SLOTS - 1) - i];
+
+ if (which_gun < GUN_WEAPON || which_gun > CANNON_WEAPON)
+ continue; /* not a gun */
+
+ which_gun -= GUN_WEAPON - 1;
+ RaceDescPtr->characteristics.weapon_energy_cost +=
+ which_gun * 2;
+
+ lpMB->flags = IGNORE_SIMILAR;
+ lpMB->blast_offs = BLASTER_OFFSET;
+ lpMB->speed = BLASTER_SPEED;
+ lpMB->preprocess_func = blaster_preprocess;
+ lpMB->hit_points = BLASTER_HITS * which_gun;
+ lpMB->damage = BLASTER_DAMAGE * which_gun;
+ lpMB->life = BLASTER_LIFE + ((BLASTER_LIFE >> 2) * (which_gun - 1));
+
+ if (which_gun == 1)
+ lpMB->index = 0;
+ else if (which_gun == 2)
+ lpMB->index = 9;
+ else
+ lpMB->index = 16;
+
+ switch (i)
+ {
+ case 0: /* NOSE WEAPON */
+ RaceDescPtr->ship_info.ship_flags |= FIRES_FORE;
+ lpMB->pixoffs = SIS_VERT_OFFSET;
+ lpMB->face = 0;
+ break;
+ case 1: /* SPREAD WEAPON */
+ RaceDescPtr->ship_info.ship_flags |= FIRES_FORE;
+ lpMB->pixoffs = SIS_VERT_OFFSET;
+ lpMB->face = +1;
+ /* copy it because there are two */
+ lpMB[1] = lpMB[0];
+ ++lpMB;
+ ++SisData->num_blasters;
+ lpMB->face = NORMALIZE_FACING (-1);
+ break;
+ case 2: /* SIDE WEAPON */
+ RaceDescPtr->ship_info.ship_flags |=
+ FIRES_LEFT | FIRES_RIGHT;
+ lpMB->pixoffs = SIS_HORZ_OFFSET;
+ lpMB->face = ANGLE_TO_FACING (QUADRANT);
+ /* copy it because there are two */
+ lpMB[1] = lpMB[0];
+ ++lpMB;
+ ++SisData->num_blasters;
+ lpMB->face = NORMALIZE_FACING (-ANGLE_TO_FACING (QUADRANT));
+ break;
+ case NUM_MODULE_SLOTS - 1: /* TAIL WEAPON */
+ RaceDescPtr->ship_info.ship_flags |= FIRES_AFT;
+ lpMB->pixoffs = SIS_VERT_OFFSET;
+ lpMB->face = ANGLE_TO_FACING (HALF_CIRCLE);
+ break;
+ }
+
+ ++lpMB;
+ ++SisData->num_blasters;
+ }
+}
+
+static void
+InitModuleSlots (RACE_DESC *RaceDescPtr, const BYTE *ModuleSlots)
+{
+ COUNT i;
+ COUNT num_trackers;
+ SIS_DATA *SisData = GetCustomShipData (RaceDescPtr);
+
+ RaceDescPtr->ship_info.max_crew = 0;
+ num_trackers = 0;
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ BYTE which_mod;
+
+ which_mod = ModuleSlots[(NUM_MODULE_SLOTS - 1) - i];
+ switch (which_mod)
+ {
+ case CREW_POD:
+ RaceDescPtr->ship_info.max_crew += CREW_POD_CAPACITY;
+ break;
+ case TRACKING_SYSTEM:
+ ++num_trackers;
+ break;
+ case ANTIMISSILE_DEFENSE:
+ RaceDescPtr->characteristics.special_energy_cost +=
+ ANTIMISSILE_ENERGY_INC;
+ break;
+ case SHIVA_FURNACE:
+ RaceDescPtr->characteristics.energy_regeneration +=
+ SHIVA_ENERGY_REGEN_INC;
+ break;
+ case DYNAMO_UNIT:
+ RaceDescPtr->characteristics.energy_wait -=
+ DYNAMO_UNIT_ENERGY_WAIT_DEC;
+ if (RaceDescPtr->characteristics.energy_wait < MIN_ENERGY_WAIT)
+ RaceDescPtr->characteristics.energy_wait = MIN_ENERGY_WAIT;
+ break;
+ }
+ }
+
+ if (num_trackers > MAX_TRACKING)
+ num_trackers = MAX_TRACKING;
+ RaceDescPtr->characteristics.weapon_energy_cost +=
+ num_trackers * TRACKER_ENERGY_COST;
+ SisData->num_trackers = num_trackers;
+ if (RaceDescPtr->characteristics.special_energy_cost)
+ {
+ RaceDescPtr->ship_info.ship_flags |= POINT_DEFENSE;
+ if (RaceDescPtr->characteristics.special_energy_cost > MAX_DEFENSE)
+ RaceDescPtr->characteristics.special_energy_cost = MAX_DEFENSE;
+ }
+}
+
+static void
+InitDriveSlots (RACE_DESC *RaceDescPtr, const BYTE *DriveSlots)
+{
+ COUNT i;
+
+ // NB. RaceDescPtr->characteristics.max_thrust is already initialised.
+ RaceDescPtr->characteristics.thrust_wait = 0;
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ switch (DriveSlots[i])
+ {
+ case FUSION_THRUSTER:
+ RaceDescPtr->characteristics.max_thrust += 2;
+ ++RaceDescPtr->characteristics.thrust_wait;
+ break;
+ }
+ }
+ RaceDescPtr->characteristics.thrust_wait = (BYTE)(
+ THRUST_WAIT - (RaceDescPtr->characteristics.thrust_wait >> 1));
+ RaceDescPtr->characteristics.max_thrust =
+ ((RaceDescPtr->characteristics.max_thrust /
+ RaceDescPtr->characteristics.thrust_increment) + 1)
+ * RaceDescPtr->characteristics.thrust_increment;
+}
+
+static void
+InitJetSlots (RACE_DESC *RaceDescPtr, const BYTE *JetSlots)
+{
+ COUNT i;
+
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ switch (JetSlots[i])
+ {
+ case TURNING_JETS:
+ RaceDescPtr->characteristics.turn_wait -= 2;
+ break;
+ }
+ }
+}
+
+RACE_DESC*
+init_sis (void)
+{
+ RACE_DESC *RaceDescPtr;
+ COUNT i;
+ // The caller of this func will copy the struct
+ static RACE_DESC new_sis_desc;
+ SIS_DATA empty_data;
+ memset (&empty_data, 0, sizeof (empty_data));
+
+ /* copy initial ship settings to new_sis_desc */
+ new_sis_desc = sis_desc;
+
+ new_sis_desc.uninit_func = uninit_sis;
+
+ if (inHQSpace ())
+ {
+ for (i = 0; i < NUM_VIEWS; ++i)
+ {
+ new_sis_desc.ship_data.ship_rsc[i] = NULL_RESOURCE;
+ new_sis_desc.ship_data.weapon_rsc[i] = NULL_RESOURCE;
+ new_sis_desc.ship_data.special_rsc[i] = NULL_RESOURCE;
+ }
+ new_sis_desc.ship_info.icons_rsc = NULL_RESOURCE;
+ new_sis_desc.ship_data.captain_control.captain_rsc = NULL_RESOURCE;
+ new_sis_desc.ship_data.victory_ditty_rsc = NULL_RESOURCE;
+ new_sis_desc.ship_data.ship_sounds_rsc = NULL_RESOURCE;
+
+ new_sis_desc.ship_data.ship_rsc[0] = SIS_HYPER_MASK_PMAP_ANIM;
+
+ new_sis_desc.preprocess_func = sis_hyper_preprocess;
+ new_sis_desc.postprocess_func = sis_hyper_postprocess;
+
+ new_sis_desc.characteristics.max_thrust -= 4;
+ }
+ else
+ {
+ new_sis_desc.preprocess_func = sis_battle_preprocess;
+ new_sis_desc.postprocess_func = sis_battle_postprocess;
+ new_sis_desc.init_weapon_func = initialize_blasters;
+ new_sis_desc.cyborg_control.intelligence_func = sis_intelligence;
+
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 3)
+ SET_GAME_STATE (BOMB_CARRIER, 1);
+ }
+
+ SetCustomShipData (&new_sis_desc, &empty_data);
+ InitModuleSlots (&new_sis_desc, GLOBAL_SIS (ModuleSlots));
+ InitWeaponSlots (&new_sis_desc, GLOBAL_SIS (ModuleSlots));
+ InitDriveSlots (&new_sis_desc, GLOBAL_SIS (DriveSlots));
+ InitJetSlots (&new_sis_desc, GLOBAL_SIS (JetSlots));
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ {
+ new_sis_desc.ship_info.crew_level = new_sis_desc.ship_info.max_crew;
+ }
+ else
+ {
+ // Count the captain too.
+ new_sis_desc.ship_info.max_crew++;
+ new_sis_desc.ship_info.crew_level = GLOBAL_SIS (CrewEnlisted) + 1;
+ new_sis_desc.ship_info.ship_flags |= PLAYER_CAPTAIN;
+ }
+
+ new_sis_desc.ship_info.energy_level = new_sis_desc.ship_info.max_energy;
+
+ RaceDescPtr = &new_sis_desc;
+
+ return (RaceDescPtr);
+}
+
+static void
+uninit_sis (RACE_DESC *pRaceDesc)
+{
+ if (!inHQSpace ())
+ {
+ GLOBAL_SIS (CrewEnlisted) = pRaceDesc->ship_info.crew_level;
+ if (pRaceDesc->ship_info.ship_flags & PLAYER_CAPTAIN)
+ GLOBAL_SIS (CrewEnlisted)--;
+ }
+
+ SetCustomShipData (pRaceDesc, NULL);
+}
+
+
diff --git a/src/uqm/ships/sis_ship/sis_ship.h b/src/uqm/ships/sis_ship/sis_ship.h
new file mode 100644
index 0000000..ffe0776
--- /dev/null
+++ b/src/uqm/ships/sis_ship/sis_ship.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SIS_SHIP_H
+#define SIS_SHIP_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_sis (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SIS_SHIP_H */
+
diff --git a/src/uqm/ships/slylandr/Makeinfo b/src/uqm/ships/slylandr/Makeinfo
new file mode 100644
index 0000000..309b1d4
--- /dev/null
+++ b/src/uqm/ships/slylandr/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="slylandr.c"
+uqm_HFILES="icode.h resinst.h slylandr.h"
diff --git a/src/uqm/ships/slylandr/icode.h b/src/uqm/ships/slylandr/icode.h
new file mode 100644
index 0000000..9897234
--- /dev/null
+++ b/src/uqm/ships/slylandr/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SLYLANDRO_CODE "ship.slylandro.code"
diff --git a/src/uqm/ships/slylandr/resinst.h b/src/uqm/ships/slylandr/resinst.h
new file mode 100644
index 0000000..00df833
--- /dev/null
+++ b/src/uqm/ships/slylandr/resinst.h
@@ -0,0 +1,13 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SLYLANDRO_BIG_MASK_PMAP_ANIM "ship.slylandro.graphics.probe.large"
+#define SLYLANDRO_CAPTAIN_MASK_PMAP_ANIM "ship.slylandro.graphics.captain"
+#define SLYLANDRO_ICON_MASK_PMAP_ANIM "ship.slylandro.icons"
+#define SLYLANDRO_MED_MASK_PMAP_ANIM "ship.slylandro.graphics.probe.medium"
+#define SLYLANDRO_MICON_MASK_PMAP_ANIM "ship.slylandro.meleeicons"
+#define SLYLANDRO_RACE_STRINGS "ship.slylandro.text"
+#define SLYLANDRO_SHIP_SOUNDS "ship.slylandro.sounds"
+#define SLYLANDRO_SML_MASK_PMAP_ANIM "ship.slylandro.graphics.probe.small"
+#define SLYLANDRO_VICTORY_SONG "ship.slylandro.ditty"
diff --git a/src/uqm/ships/slylandr/slylandr.c b/src/uqm/ships/slylandr/slylandr.c
new file mode 100644
index 0000000..8c4ca95
--- /dev/null
+++ b/src/uqm/ships/slylandr/slylandr.c
@@ -0,0 +1,438 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "slylandr.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 12
+#define MAX_ENERGY 20
+#define ENERGY_REGENERATION 0
+#define ENERGY_WAIT 10
+#define MAX_THRUST 60
+#define THRUST_INCREMENT MAX_THRUST
+#define THRUST_WAIT 0
+#define TURN_WAIT 0
+#define SHIP_MASS 1
+
+// Lightning weapon
+#define WEAPON_ENERGY_COST 2
+#define WEAPON_WAIT 17
+#define SLYLANDRO_OFFSET 9
+#define LASER_LENGTH 32
+ /* Total length of lighting bolts. Actual range is usually less than
+ * this, since the lightning rarely is straight. */
+
+// Harvester
+#define SPECIAL_ENERGY_COST 0
+#define SPECIAL_WAIT 20
+#define HARVEST_RANGE (208 * 3 / 8)
+ /* Was originally (SPACE_HEIGHT * 3 / 8) */
+
+static RACE_DESC slylandro_desc =
+{
+ { /* SHIP_INFO */
+ SEEKING_WEAPON | CREW_IMMUNE,
+ 17, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ SLYLANDRO_RACE_STRINGS,
+ SLYLANDRO_ICON_MASK_PMAP_ANIM,
+ SLYLANDRO_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ INFINITE_RADIUS, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 333, 9812,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ SLYLANDRO_BIG_MASK_PMAP_ANIM,
+ SLYLANDRO_MED_MASK_PMAP_ANIM,
+ SLYLANDRO_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ SLYLANDRO_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ SLYLANDRO_VICTORY_SONG,
+ SLYLANDRO_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ CLOSE_RANGE_WEAPON << 1,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static COUNT initialize_lightning (ELEMENT *ElementPtr,
+ HELEMENT LaserArray[]);
+
+static void
+lightning_postprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait
+ && !(ElementPtr->state_flags & COLLISION))
+ {
+ HELEMENT Lightning;
+
+ initialize_lightning (ElementPtr, &Lightning);
+ if (Lightning)
+ PutElement (Lightning);
+ }
+}
+
+static void
+lightning_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ if (StarShipPtr->weapon_counter > WEAPON_WAIT >> 1)
+ StarShipPtr->weapon_counter =
+ WEAPON_WAIT - StarShipPtr->weapon_counter;
+ StarShipPtr->weapon_counter -= ElementPtr0->turn_wait;
+ ElementPtr0->turn_wait = 0;
+
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+}
+
+static COUNT
+initialize_lightning (ELEMENT *ElementPtr, HELEMENT LaserArray[])
+{
+ LASER_BLOCK LaserBlock;
+
+ LaserBlock.cx = ElementPtr->next.location.x;
+ LaserBlock.cy = ElementPtr->next.location.y;
+ LaserBlock.ex = 0;
+ LaserBlock.ey = 0;
+
+ LaserBlock.sender = ElementPtr->playerNr;
+ LaserBlock.flags = IGNORE_SIMILAR;
+ LaserBlock.face = 0;
+ LaserBlock.pixoffs = 0;
+ LaserArray[0] = initialize_laser (&LaserBlock);
+
+ if (LaserArray[0])
+ {
+ SIZE delta;
+ COUNT angle, facing;
+ DWORD rand_val;
+ ELEMENT *LaserPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ LockElement (LaserArray[0], &LaserPtr);
+ LaserPtr->postprocess_func = lightning_postprocess;
+ LaserPtr->collision_func = lightning_collision;
+
+ rand_val = TFB_Random ();
+
+ if (!(ElementPtr->state_flags & PLAYER_SHIP))
+ {
+ angle = GetVelocityTravelAngle (&ElementPtr->velocity);
+ facing = NORMALIZE_FACING (ANGLE_TO_FACING (angle));
+ delta = TrackShip (ElementPtr, &facing);
+
+ LaserPtr->turn_wait = ElementPtr->turn_wait - 1;
+
+ SetPrimColor (&(GLOBAL (DisplayArray))[LaserPtr->PrimIndex],
+ GetPrimColor (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex]));
+ }
+ else
+ {
+ facing = StarShipPtr->ShipFacing;
+ ElementPtr->hTarget = 0;
+ delta = TrackShip (ElementPtr, &facing);
+ ElementPtr->hTarget = 0;
+ angle = FACING_TO_ANGLE (facing);
+
+ if ((LaserPtr->turn_wait = StarShipPtr->weapon_counter) == 0)
+ LaserPtr->turn_wait = WEAPON_WAIT;
+
+ if (LaserPtr->turn_wait > WEAPON_WAIT >> 1)
+ LaserPtr->turn_wait = WEAPON_WAIT - LaserPtr->turn_wait;
+
+ switch (HIBYTE (LOWORD (rand_val)) & 3)
+ {
+ case 0:
+ SetPrimColor (
+ &(GLOBAL (DisplayArray))[LaserPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F)
+ );
+ break;
+ case 1:
+ SetPrimColor (
+ &(GLOBAL (DisplayArray))[LaserPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x16, 0x17, 0x1F), 0x42)
+ );
+ break;
+ case 2:
+ SetPrimColor (
+ &(GLOBAL (DisplayArray))[LaserPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x06, 0x07, 0x1F), 0x4A)
+ );
+ break;
+ case 3:
+ SetPrimColor (
+ &(GLOBAL (DisplayArray))[LaserPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x18), 0x50)
+ );
+ break;
+ }
+ }
+
+ if (delta == -1 || delta == ANGLE_TO_FACING (HALF_CIRCLE))
+ angle += LOWORD (rand_val);
+ else if (delta == 0)
+ angle += LOWORD (rand_val) & 1 ? -1 : 1;
+ else if (delta < ANGLE_TO_FACING (HALF_CIRCLE))
+ angle += LOWORD (rand_val) & (QUADRANT - 1);
+ else
+ angle -= LOWORD (rand_val) & (QUADRANT - 1);
+ delta = WORLD_TO_VELOCITY (
+ DISPLAY_TO_WORLD ((HIWORD (rand_val) & (LASER_LENGTH - 1)) + 4)
+ );
+ SetVelocityComponents (&LaserPtr->velocity,
+ COSINE (angle, delta), SINE (angle, delta));
+
+ SetElementStarShip (LaserPtr, StarShipPtr);
+ UnlockElement (LaserArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+slylandro_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER)
+ /* no dodging in role playing game */
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr = 0;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->special_counter == 0
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level == 0
+ && ObjectsOfConcern[GRAVITY_MASS_INDEX].ObjectPtr == 0)
+ ConcernCounter = FIRST_EMPTY_INDEX + 1;
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->MoveState == PURSUE
+ && lpEvalDesc->which_turn <= 6)
+ lpEvalDesc->MoveState = ENTICE;
+
+ ++ShipPtr->thrust_wait;
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+ --ShipPtr->thrust_wait;
+
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->which_turn <= 14)
+ StarShipPtr->ship_input_state |= WEAPON;
+ else
+ StarShipPtr->ship_input_state &= ~WEAPON;
+
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level <
+ StarShipPtr->RaceDescPtr->ship_info.max_energy)
+ {
+ lpEvalDesc = &ObjectsOfConcern[FIRST_EMPTY_INDEX];
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->which_turn <= 14)
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+}
+
+static BOOLEAN
+harvest_space_junk (ELEMENT *ElementPtr)
+{
+ BOOLEAN retval;
+ HELEMENT hElement, hNextElement;
+
+ retval = FALSE;
+ for (hElement = GetHeadElement ();
+ hElement; hElement = hNextElement)
+ {
+ ELEMENT *ObjPtr;
+
+ LockElement (hElement, &ObjPtr);
+ hNextElement = GetSuccElement (ObjPtr);
+
+ if (!(ObjPtr->state_flags & (APPEARING | PLAYER_SHIP | FINITE_LIFE))
+ && ObjPtr->playerNr == NEUTRAL_PLAYER_NUM
+ && !GRAVITY_MASS (ObjPtr->mass_points)
+ && CollisionPossible (ObjPtr, ElementPtr))
+ {
+ SIZE dx, dy;
+
+ if ((dx = ObjPtr->next.location.x
+ - ElementPtr->next.location.x) < 0)
+ dx = -dx;
+ if ((dy = ObjPtr->next.location.y
+ - ElementPtr->next.location.y) < 0)
+ dy = -dy;
+ dx = WORLD_TO_DISPLAY (dx);
+ dy = WORLD_TO_DISPLAY (dy);
+ if (dx <= HARVEST_RANGE && dy <= HARVEST_RANGE
+ && dx * dx + dy * dy <= HARVEST_RANGE * HARVEST_RANGE)
+ {
+ ObjPtr->life_span = 0;
+ ObjPtr->state_flags |= NONSOLID;
+
+ if (!retval)
+ {
+ STARSHIP *StarShipPtr;
+
+ retval = TRUE;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ DeltaEnergy (ElementPtr, MAX_ENERGY);
+ }
+ }
+ }
+
+ UnlockElement (hElement);
+ }
+
+ return (retval);
+}
+
+static void
+slylandro_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->weapon_counter
+ && StarShipPtr->weapon_counter < WEAPON_WAIT)
+ {
+ HELEMENT Lightning;
+
+ initialize_lightning (ElementPtr, &Lightning);
+ if (Lightning)
+ PutElement (Lightning);
+ }
+
+ if (StarShipPtr->special_counter == 0
+ && (StarShipPtr->cur_status_flags & SPECIAL)
+ && harvest_space_junk (ElementPtr))
+ {
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+}
+
+static void
+slylandro_preprocess (ELEMENT *ElementPtr)
+{
+ if (!(ElementPtr->state_flags & (APPEARING | NONSOLID)))
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & THRUST)
+ && !(StarShipPtr->old_status_flags & THRUST))
+ StarShipPtr->ShipFacing += ANGLE_TO_FACING (HALF_CIRCLE);
+
+ if (ElementPtr->turn_wait == 0)
+ {
+ ElementPtr->turn_wait +=
+ StarShipPtr->RaceDescPtr->characteristics.turn_wait + 1;
+ if (StarShipPtr->cur_status_flags & LEFT)
+ --StarShipPtr->ShipFacing;
+ else if (StarShipPtr->cur_status_flags & RIGHT)
+ ++StarShipPtr->ShipFacing;
+ }
+
+ StarShipPtr->ShipFacing = NORMALIZE_FACING (StarShipPtr->ShipFacing);
+
+ if (ElementPtr->thrust_wait == 0)
+ {
+ ElementPtr->thrust_wait +=
+ StarShipPtr->RaceDescPtr->characteristics.thrust_wait + 1;
+
+ SetVelocityVector (&ElementPtr->velocity,
+ StarShipPtr->RaceDescPtr->characteristics.max_thrust,
+ StarShipPtr->ShipFacing);
+ StarShipPtr->cur_status_flags |= SHIP_AT_MAX_SPEED;
+ StarShipPtr->cur_status_flags &= ~SHIP_IN_GRAVITY_WELL;
+ }
+
+ ElementPtr->next.image.frame = IncFrameIndex (ElementPtr->next.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+RACE_DESC*
+init_slylandro (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ slylandro_desc.preprocess_func = slylandro_preprocess;
+ slylandro_desc.postprocess_func = slylandro_postprocess;
+ slylandro_desc.init_weapon_func = initialize_lightning;
+ slylandro_desc.cyborg_control.intelligence_func = slylandro_intelligence;
+
+ RaceDescPtr = &slylandro_desc;
+
+ return (RaceDescPtr);
+}
diff --git a/src/uqm/ships/slylandr/slylandr.h b/src/uqm/ships/slylandr/slylandr.h
new file mode 100644
index 0000000..a55362d
--- /dev/null
+++ b/src/uqm/ships/slylandr/slylandr.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SLYLANDR_H
+#define SLYLANDR_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_slylandro (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SLYLANDR_H */
+
diff --git a/src/uqm/ships/spathi/Makeinfo b/src/uqm/ships/spathi/Makeinfo
new file mode 100644
index 0000000..48dc3f9
--- /dev/null
+++ b/src/uqm/ships/spathi/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="spathi.c"
+uqm_HFILES="icode.h resinst.h spathi.h"
diff --git a/src/uqm/ships/spathi/icode.h b/src/uqm/ships/spathi/icode.h
new file mode 100644
index 0000000..aa5f6ef
--- /dev/null
+++ b/src/uqm/ships/spathi/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SPATHI_CODE "ship.spathi.code"
diff --git a/src/uqm/ships/spathi/resinst.h b/src/uqm/ships/spathi/resinst.h
new file mode 100644
index 0000000..1d4ecf6
--- /dev/null
+++ b/src/uqm/ships/spathi/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define DISCRIM_BIG_MASK_PMAP_ANIM "ship.spathi.graphics.butt.large"
+#define DISCRIM_MED_MASK_PMAP_ANIM "ship.spathi.graphics.butt.medium"
+#define DISCRIM_SML_MASK_PMAP_ANIM "ship.spathi.graphics.butt.small"
+#define MISSILE_BIG_MASK_PMAP_ANIM "ship.spathi.graphics.missile.large"
+#define MISSILE_MED_MASK_PMAP_ANIM "ship.spathi.graphics.missile.medium"
+#define MISSILE_SML_MASK_PMAP_ANIM "ship.spathi.graphics.missile.small"
+#define SPATHI_BIG_MASK_PMAP_ANIM "ship.spathi.graphics.eluder.large"
+#define SPATHI_CAPTAIN_MASK_PMAP_ANIM "ship.spathi.graphics.captain"
+#define SPATHI_ICON_MASK_PMAP_ANIM "ship.spathi.icons"
+#define SPATHI_MED_MASK_PMAP_ANIM "ship.spathi.graphics.eluder.medium"
+#define SPATHI_MICON_MASK_PMAP_ANIM "ship.spathi.meleeicons"
+#define SPATHI_RACE_STRINGS "ship.spathi.text"
+#define SPATHI_SHIP_SOUNDS "ship.spathi.sounds"
+#define SPATHI_SML_MASK_PMAP_ANIM "ship.spathi.graphics.eluder.small"
+#define SPATHI_VICTORY_SONG "ship.spathi.ditty"
diff --git a/src/uqm/ships/spathi/spathi.c b/src/uqm/ships/spathi/spathi.c
new file mode 100644
index 0000000..aa4bd00
--- /dev/null
+++ b/src/uqm/ships/spathi/spathi.c
@@ -0,0 +1,301 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "spathi.h"
+#include "resinst.h"
+
+// Core characteristics
+#define MAX_CREW 30
+#define MAX_ENERGY 10
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 10
+#define MAX_THRUST 48
+#define THRUST_INCREMENT 12
+#define THRUST_WAIT 1
+#define TURN_WAIT 1
+#define SHIP_MASS 5
+
+// Forward gun
+#define WEAPON_ENERGY_COST 2
+#define WEAPON_WAIT 0
+#define SPATHI_FORWARD_OFFSET 16
+#define MISSILE_SPEED DISPLAY_TO_WORLD (30)
+#define MISSILE_LIFE 10
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 1
+#define MISSILE_OFFSET 1
+#define MISSILE_RANGE (MISSILE_SPEED * MISSILE_LIFE)
+ /* This is for the cyborg only. */
+
+// B.U.T.T.
+#define SPECIAL_ENERGY_COST 3
+#define SPECIAL_WAIT 7
+#define SPATHI_REAR_OFFSET 20
+#define DISCRIMINATOR_SPEED DISPLAY_TO_WORLD (8)
+#define DISCRIMINATOR_LIFE 30
+#define DISCRIMINATOR_HITS 1
+#define DISCRIMINATOR_DAMAGE 2
+#define DISCRIMINATOR_OFFSET 4
+#define TRACK_WAIT 1
+
+static RACE_DESC spathi_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | FIRES_AFT | SEEKING_SPECIAL | DONT_CHASE,
+ 18, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ SPATHI_RACE_STRINGS,
+ SPATHI_ICON_MASK_PMAP_ANIM,
+ SPATHI_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 1000 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 2549, 3600,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ SPATHI_BIG_MASK_PMAP_ANIM,
+ SPATHI_MED_MASK_PMAP_ANIM,
+ SPATHI_SML_MASK_PMAP_ANIM,
+ },
+ {
+ MISSILE_BIG_MASK_PMAP_ANIM,
+ MISSILE_MED_MASK_PMAP_ANIM,
+ MISSILE_SML_MASK_PMAP_ANIM,
+ },
+ {
+ DISCRIM_BIG_MASK_PMAP_ANIM,
+ DISCRIM_MED_MASK_PMAP_ANIM,
+ DISCRIM_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SPATHI_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ SPATHI_VICTORY_SONG,
+ SPATHI_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ MISSILE_RANGE,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+butt_missile_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ COUNT facing;
+
+ facing = GetFrameIndex (ElementPtr->next.image.frame);
+ if (TrackShip (ElementPtr, &facing) > 0)
+ {
+ ElementPtr->next.image.frame =
+ SetAbsFrameIndex (ElementPtr->next.image.frame,
+ facing);
+ ElementPtr->state_flags |= CHANGING;
+
+ SetVelocityVector (&ElementPtr->velocity,
+ DISCRIMINATOR_SPEED, facing);
+ }
+
+ ElementPtr->turn_wait = TRACK_WAIT;
+ }
+}
+
+static void
+spawn_butt_missile (ELEMENT *ShipPtr)
+{
+ HELEMENT ButtMissile;
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK ButtMissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ ButtMissileBlock.cx = ShipPtr->next.location.x;
+ ButtMissileBlock.cy = ShipPtr->next.location.y;
+ ButtMissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ ButtMissileBlock.face = ButtMissileBlock.index =
+ NORMALIZE_FACING (StarShipPtr->ShipFacing
+ + ANGLE_TO_FACING (HALF_CIRCLE));
+ ButtMissileBlock.sender = ShipPtr->playerNr;
+ ButtMissileBlock.flags = 0;
+ ButtMissileBlock.pixoffs = SPATHI_REAR_OFFSET;
+ ButtMissileBlock.speed = DISCRIMINATOR_SPEED;
+ ButtMissileBlock.hit_points = DISCRIMINATOR_HITS;
+ ButtMissileBlock.damage = DISCRIMINATOR_DAMAGE;
+ ButtMissileBlock.life = DISCRIMINATOR_LIFE;
+ ButtMissileBlock.preprocess_func = butt_missile_preprocess;
+ ButtMissileBlock.blast_offs = DISCRIMINATOR_OFFSET;
+ ButtMissile = initialize_missile (&ButtMissileBlock);
+ if (ButtMissile)
+ {
+ ELEMENT *ButtPtr;
+
+ LockElement (ButtMissile, &ButtPtr);
+ ButtPtr->turn_wait = TRACK_WAIT;
+ SetElementStarShip (ButtPtr, StarShipPtr);
+
+ ProcessSound (SetAbsSoundIndex (
+ /* LAUNCH_BUTT_MISSILE */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ButtPtr);
+
+ UnlockElement (ButtMissile);
+ PutElement (ButtMissile);
+ }
+}
+
+static void
+spathi_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (StarShipPtr->special_counter == 0
+ && lpEvalDesc->ObjectPtr
+ && lpEvalDesc->which_turn <= 24)
+ {
+ COUNT travel_facing, direction_facing;
+ SIZE delta_x, delta_y;
+
+ travel_facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (GetVelocityTravelAngle (&ShipPtr->velocity)
+ + HALF_CIRCLE)
+ );
+ delta_x = lpEvalDesc->ObjectPtr->current.location.x
+ - ShipPtr->current.location.x;
+ delta_y = lpEvalDesc->ObjectPtr->current.location.y
+ - ShipPtr->current.location.y;
+ direction_facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
+ );
+
+ if (NORMALIZE_FACING (direction_facing
+ - (StarShipPtr->ShipFacing + ANGLE_TO_FACING (HALF_CIRCLE))
+ + ANGLE_TO_FACING (QUADRANT))
+ <= ANGLE_TO_FACING (HALF_CIRCLE)
+ && (lpEvalDesc->which_turn <= 8
+ || NORMALIZE_FACING (direction_facing
+ + ANGLE_TO_FACING (HALF_CIRCLE)
+ - ANGLE_TO_FACING (GetVelocityTravelAngle (
+ &lpEvalDesc->ObjectPtr->velocity
+ ))
+ + ANGLE_TO_FACING (QUADRANT))
+ <= ANGLE_TO_FACING (HALF_CIRCLE))
+ && (!(StarShipPtr->cur_status_flags &
+ (SHIP_BEYOND_MAX_SPEED | SHIP_IN_GRAVITY_WELL))
+ || NORMALIZE_FACING (direction_facing
+ - travel_facing + ANGLE_TO_FACING (QUADRANT))
+ <= ANGLE_TO_FACING (HALF_CIRCLE)))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+}
+
+static COUNT
+initialize_standard_missile (ELEMENT *ShipPtr, HELEMENT MissileArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = SPATHI_FORWARD_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ MissileArray[0] = initialize_missile (&MissileBlock);
+
+ return (1);
+}
+
+static void
+spathi_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ spawn_butt_missile (ElementPtr);
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+}
+
+RACE_DESC*
+init_spathi (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ spathi_desc.postprocess_func = spathi_postprocess;
+ spathi_desc.init_weapon_func = initialize_standard_missile;
+ spathi_desc.cyborg_control.intelligence_func = spathi_intelligence;
+
+ RaceDescPtr = &spathi_desc;
+
+ return (RaceDescPtr);
+}
diff --git a/src/uqm/ships/spathi/spathi.h b/src/uqm/ships/spathi/spathi.h
new file mode 100644
index 0000000..900d05c
--- /dev/null
+++ b/src/uqm/ships/spathi/spathi.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SPATHI_H
+#define SPATHI_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_spathi (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SPATHI_H */
+
diff --git a/src/uqm/ships/supox/Makeinfo b/src/uqm/ships/supox/Makeinfo
new file mode 100644
index 0000000..9105cf6
--- /dev/null
+++ b/src/uqm/ships/supox/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="supox.c"
+uqm_HFILES="icode.h resinst.h supox.h"
diff --git a/src/uqm/ships/supox/icode.h b/src/uqm/ships/supox/icode.h
new file mode 100644
index 0000000..d2f82f9
--- /dev/null
+++ b/src/uqm/ships/supox/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SUPOX_CODE "ship.supox.code"
diff --git a/src/uqm/ships/supox/resinst.h b/src/uqm/ships/supox/resinst.h
new file mode 100644
index 0000000..8984c6c
--- /dev/null
+++ b/src/uqm/ships/supox/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define GOB_BIG_MASK_PMAP_ANIM "ship.supox.graphics.glob.large"
+#define GOB_MED_MASK_PMAP_ANIM "ship.supox.graphics.glob.medium"
+#define GOB_SML_MASK_PMAP_ANIM "ship.supox.graphics.glob.small"
+#define SUPOX_BIG_MASK_PMAP_ANIM "ship.supox.graphics.blade.large"
+#define SUPOX_CAPTAIN_MASK_PMAP_ANIM "ship.supox.graphics.captain"
+#define SUPOX_ICON_MASK_PMAP_ANIM "ship.supox.icons"
+#define SUPOX_MED_MASK_PMAP_ANIM "ship.supox.graphics.blade.medium"
+#define SUPOX_MICON_MASK_PMAP_ANIM "ship.supox.meleeicons"
+#define SUPOX_RACE_STRINGS "ship.supox.text"
+#define SUPOX_SHIP_SOUNDS "ship.supox.sounds"
+#define SUPOX_SML_MASK_PMAP_ANIM "ship.supox.graphics.blade.small"
+#define SUPOX_VICTORY_SONG "ship.supox.ditty"
diff --git a/src/uqm/ships/supox/supox.c b/src/uqm/ships/supox/supox.c
new file mode 100644
index 0000000..854c5b3
--- /dev/null
+++ b/src/uqm/ships/supox/supox.c
@@ -0,0 +1,288 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "supox.h"
+#include "resinst.h"
+
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 12
+#define MAX_ENERGY 16
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 4
+#define MAX_THRUST 40
+#define THRUST_INCREMENT 8
+#define THRUST_WAIT 0
+#define TURN_WAIT 1
+#define SHIP_MASS 4
+
+// Gob launcher
+#define WEAPON_ENERGY_COST 1
+#define WEAPON_WAIT 2
+#define SUPOX_OFFSET 23
+#define MISSILE_OFFSET 2
+#define MISSILE_SPEED DISPLAY_TO_WORLD (30)
+#define MISSILE_LIFE 10
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 1
+
+// Lateral/reverse thrust
+#define SPECIAL_ENERGY_COST 1
+ /* Unused - uncomment below to enable. */
+#define SPECIAL_WAIT 0
+ /* Unused except to initialize supox_desc.special_wait */
+
+static RACE_DESC supox_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 16, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ SUPOX_RACE_STRINGS,
+ SUPOX_ICON_MASK_PMAP_ANIM,
+ SUPOX_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 333 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 7468, 9246,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ SUPOX_BIG_MASK_PMAP_ANIM,
+ SUPOX_MED_MASK_PMAP_ANIM,
+ SUPOX_SML_MASK_PMAP_ANIM,
+ },
+ {
+ GOB_BIG_MASK_PMAP_ANIM,
+ GOB_MED_MASK_PMAP_ANIM,
+ GOB_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ SUPOX_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ SUPOX_VICTORY_SONG,
+ SUPOX_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ (MISSILE_SPEED * MISSILE_LIFE) >> 1,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+supox_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (StarShipPtr->special_counter || lpEvalDesc->ObjectPtr == 0)
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ else
+ {
+ BOOLEAN LinedUp;
+ COUNT direction_angle;
+ SIZE delta_x, delta_y;
+
+ delta_x = lpEvalDesc->ObjectPtr->next.location.x
+ - ShipPtr->next.location.x;
+ delta_y = lpEvalDesc->ObjectPtr->next.location.y
+ - ShipPtr->next.location.y;
+ direction_angle = ARCTAN (delta_x, delta_y);
+
+ LinedUp = (BOOLEAN)(NORMALIZE_ANGLE (NORMALIZE_ANGLE (direction_angle
+ - FACING_TO_ANGLE (StarShipPtr->ShipFacing))
+ + QUADRANT) <= HALF_CIRCLE);
+
+ if (!LinedUp
+ || lpEvalDesc->which_turn > 20
+ || NORMALIZE_ANGLE (
+ lpEvalDesc->facing
+ - (FACING_TO_ANGLE (StarShipPtr->ShipFacing)
+ + HALF_CIRCLE) + OCTANT
+ ) > QUADRANT)
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ else if (LinedUp && lpEvalDesc->which_turn <= 12)
+ StarShipPtr->ship_input_state |= SPECIAL;
+
+ if (StarShipPtr->ship_input_state & SPECIAL)
+ lpEvalDesc->MoveState = PURSUE;
+ }
+
+ ship_intelligence (ShipPtr,
+ ObjectsOfConcern, ConcernCounter);
+
+ if (StarShipPtr->ship_input_state & SPECIAL)
+ StarShipPtr->ship_input_state |= THRUST | WEAPON;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (StarShipPtr->special_counter == 0
+ && lpEvalDesc->ObjectPtr
+ && lpEvalDesc->MoveState == AVOID
+ && ShipPtr->turn_wait == 0)
+ {
+ StarShipPtr->ship_input_state &= ~THRUST;
+ StarShipPtr->ship_input_state |= SPECIAL;
+ if (!(StarShipPtr->cur_status_flags & (LEFT | RIGHT)))
+ StarShipPtr->ship_input_state |= 1 << ((BYTE)TFB_Random () & 1);
+ else
+ StarShipPtr->ship_input_state |=
+ StarShipPtr->cur_status_flags & (LEFT | RIGHT);
+ }
+}
+
+static COUNT
+initialize_horn (ELEMENT *ShipPtr, HELEMENT HornArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = SUPOX_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ HornArray[0] = initialize_missile (&MissileBlock);
+ return (1);
+}
+
+static void
+supox_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+/*
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST)
+*/
+ )
+ {
+ SIZE add_facing;
+
+ add_facing = 0;
+ if (StarShipPtr->cur_status_flags & THRUST)
+ {
+ if (ElementPtr->thrust_wait == 0)
+ ++ElementPtr->thrust_wait;
+
+ add_facing = ANGLE_TO_FACING (HALF_CIRCLE);
+ }
+ if (StarShipPtr->cur_status_flags & LEFT)
+ {
+ if (ElementPtr->turn_wait == 0)
+ ++ElementPtr->turn_wait;
+
+ if (add_facing)
+ add_facing += ANGLE_TO_FACING (OCTANT);
+ else
+ add_facing = -ANGLE_TO_FACING (QUADRANT);
+ }
+ else if (StarShipPtr->cur_status_flags & RIGHT)
+ {
+ if (ElementPtr->turn_wait == 0)
+ ++ElementPtr->turn_wait;
+
+ if (add_facing)
+ add_facing -= ANGLE_TO_FACING (OCTANT);
+ else
+ add_facing = ANGLE_TO_FACING (QUADRANT);
+ }
+
+ if (add_facing)
+ {
+ COUNT facing;
+ STATUS_FLAGS thrust_status;
+
+ facing = StarShipPtr->ShipFacing;
+ StarShipPtr->ShipFacing = NORMALIZE_FACING (
+ facing + add_facing
+ );
+ thrust_status = inertial_thrust (ElementPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED
+ | SHIP_BEYOND_MAX_SPEED
+ | SHIP_IN_GRAVITY_WELL);
+ StarShipPtr->cur_status_flags |= thrust_status;
+ StarShipPtr->ShipFacing = facing;
+ }
+ }
+}
+
+RACE_DESC*
+init_supox (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ supox_desc.preprocess_func = supox_preprocess;
+ supox_desc.init_weapon_func = initialize_horn;
+ supox_desc.cyborg_control.intelligence_func = supox_intelligence;
+
+ RaceDescPtr = &supox_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/supox/supox.h b/src/uqm/ships/supox/supox.h
new file mode 100644
index 0000000..b066320
--- /dev/null
+++ b/src/uqm/ships/supox/supox.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SUPOX_H
+#define SUPOX_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_supox (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SUPOX_H */
+
diff --git a/src/uqm/ships/syreen/Makeinfo b/src/uqm/ships/syreen/Makeinfo
new file mode 100644
index 0000000..9485c78
--- /dev/null
+++ b/src/uqm/ships/syreen/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="syreen.c"
+uqm_HFILES="icode.h resinst.h syreen.h"
diff --git a/src/uqm/ships/syreen/icode.h b/src/uqm/ships/syreen/icode.h
new file mode 100644
index 0000000..66f3ca4
--- /dev/null
+++ b/src/uqm/ships/syreen/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SYREEN_CODE "ship.syreen.code"
diff --git a/src/uqm/ships/syreen/resinst.h b/src/uqm/ships/syreen/resinst.h
new file mode 100644
index 0000000..7a7cc24
--- /dev/null
+++ b/src/uqm/ships/syreen/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define DAGGER_BIG_MASK_PMAP_ANIM "ship.syreen.graphics.dagger.large"
+#define DAGGER_MED_MASK_PMAP_ANIM "ship.syreen.graphics.dagger.medium"
+#define DAGGER_SML_MASK_PMAP_ANIM "ship.syreen.graphics.dagger.small"
+#define SYREEN_BIG_MASK_PMAP_ANIM "ship.syreen.graphics.penetrator.large"
+#define SYREEN_CAPTAIN_MASK_PMAP_ANIM "ship.syreen.graphics.captain"
+#define SYREEN_ICON_MASK_PMAP_ANIM "ship.syreen.icons"
+#define SYREEN_MED_MASK_PMAP_ANIM "ship.syreen.graphics.penetrator.medium"
+#define SYREEN_MICON_MASK_PMAP_ANIM "ship.syreen.meleeicons"
+#define SYREEN_RACE_STRINGS "ship.syreen.text"
+#define SYREEN_SHIP_SOUNDS "ship.syreen.sounds"
+#define SYREEN_SML_MASK_PMAP_ANIM "ship.syreen.graphics.penetrator.small"
+#define SYREEN_VICTORY_SONG "ship.syreen.ditty"
diff --git a/src/uqm/ships/syreen/syreen.c b/src/uqm/ships/syreen/syreen.c
new file mode 100644
index 0000000..c65ecd2
--- /dev/null
+++ b/src/uqm/ships/syreen/syreen.c
@@ -0,0 +1,284 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "syreen.h"
+#include "resinst.h"
+
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define SYREEN_MAX_CREW_SIZE MAX_CREW_SIZE
+#define MAX_CREW 12
+#define MAX_ENERGY 16
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 6
+#define MAX_THRUST /* DISPLAY_TO_WORLD (8) */ 36
+#define THRUST_INCREMENT /* DISPLAY_TO_WORLD (2) */ 9
+#define THRUST_WAIT 1
+#define TURN_WAIT 1
+#define SHIP_MASS 2
+
+// Particle Beam Stiletto
+#define WEAPON_ENERGY_COST 1
+#define WEAPON_WAIT 8
+#define SYREEN_OFFSET 30
+#define MISSILE_SPEED DISPLAY_TO_WORLD (30)
+#define MISSILE_LIFE 10
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 2
+#define MISSILE_OFFSET 3
+
+// Syreen song
+#define SPECIAL_ENERGY_COST 5
+#define SPECIAL_WAIT 20
+#define ABANDONER_RANGE 208 /* originally SPACE_HEIGHT */
+#define MAX_ABANDONERS 8
+
+static RACE_DESC syreen_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 13, /* Super Melee cost */
+ MAX_CREW, SYREEN_MAX_CREW_SIZE,
+ MAX_ENERGY, MAX_ENERGY,
+ SYREEN_RACE_STRINGS,
+ SYREEN_ICON_MASK_PMAP_ANIM,
+ SYREEN_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 0, /* Initial sphere of influence radius */
+ { /* Known location (center of SoI) */
+ 0, 0,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ SYREEN_BIG_MASK_PMAP_ANIM,
+ SYREEN_MED_MASK_PMAP_ANIM,
+ SYREEN_SML_MASK_PMAP_ANIM,
+ },
+ {
+ DAGGER_BIG_MASK_PMAP_ANIM,
+ DAGGER_MED_MASK_PMAP_ANIM,
+ DAGGER_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ SYREEN_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ SYREEN_VICTORY_SONG,
+ SYREEN_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ (MISSILE_SPEED * MISSILE_LIFE * 2 / 3),
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static COUNT
+initialize_dagger (ELEMENT *ShipPtr, HELEMENT DaggerArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = SYREEN_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ DaggerArray[0] = initialize_missile (&MissileBlock);
+
+ return (1);
+}
+
+static void
+spawn_crew (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ {
+ HELEMENT hCrew;
+
+ hCrew = AllocElement ();
+ if (hCrew != 0)
+ {
+ ELEMENT *CrewPtr;
+
+ LockElement (hCrew, &CrewPtr);
+ CrewPtr->next.location = ElementPtr->next.location;
+ CrewPtr->playerNr = ElementPtr->playerNr;
+ CrewPtr->state_flags = APPEARING | NONSOLID | FINITE_LIFE;
+ CrewPtr->life_span = 0;
+ CrewPtr->death_func = spawn_crew;
+ CrewPtr->pParent = ElementPtr->pParent;
+ CrewPtr->hTarget = 0;
+ UnlockElement (hCrew);
+
+ PutElement (hCrew);
+ }
+ }
+ else
+ {
+ HELEMENT hElement, hNextElement;
+
+ for (hElement = GetHeadElement ();
+ hElement != 0; hElement = hNextElement)
+ {
+ ELEMENT *ObjPtr;
+
+ LockElement (hElement, &ObjPtr);
+ hNextElement = GetSuccElement (ObjPtr);
+
+ if ((ObjPtr->state_flags & PLAYER_SHIP)
+ && !elementsOfSamePlayer (ObjPtr, ElementPtr)
+ && ObjPtr->crew_level > 1)
+ {
+ SIZE dx, dy;
+ DWORD d_squared;
+
+ dx = ObjPtr->next.location.x - ElementPtr->next.location.x;
+ if (dx < 0)
+ dx = -dx;
+ dy = ObjPtr->next.location.y - ElementPtr->next.location.y;
+ if (dy < 0)
+ dy = -dy;
+
+ dx = WORLD_TO_DISPLAY (dx);
+ dy = WORLD_TO_DISPLAY (dy);
+ if (dx <= ABANDONER_RANGE && dy <= ABANDONER_RANGE
+ && (d_squared = (DWORD)((UWORD)dx * (UWORD)dx)
+ + (DWORD)((UWORD)dy * (UWORD)dy)) <=
+ (DWORD)((UWORD)ABANDONER_RANGE * (UWORD)ABANDONER_RANGE))
+ {
+ COUNT crew_loss;
+
+ crew_loss = ((MAX_ABANDONERS
+ * (ABANDONER_RANGE - square_root (d_squared)))
+ / ABANDONER_RANGE) + 1;
+ if (crew_loss >= ObjPtr->crew_level)
+ crew_loss = ObjPtr->crew_level - 1;
+
+ AbandonShip (ObjPtr, ElementPtr, crew_loss);
+ }
+ }
+
+ UnlockElement (hElement);
+ }
+ }
+}
+
+static void
+syreen_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+
+ ship_intelligence (ShipPtr,
+ ObjectsOfConcern, ConcernCounter);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEvalDesc->ObjectPtr != NULL)
+ {
+ STARSHIP *StarShipPtr;
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if (!(EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags & CREW_IMMUNE)
+ && StarShipPtr->special_counter == 0
+ && lpEvalDesc->ObjectPtr->crew_level > 1
+ && lpEvalDesc->which_turn <= 14)
+ StarShipPtr->ship_input_state |= SPECIAL;
+ else
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ }
+}
+
+static void
+syreen_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ ProcessSound (SetAbsSoundIndex (
+ /* SYREEN_SONG */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ spawn_crew (ElementPtr);
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+}
+
+RACE_DESC*
+init_syreen (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ syreen_desc.postprocess_func = syreen_postprocess;
+ syreen_desc.init_weapon_func = initialize_dagger;
+ syreen_desc.cyborg_control.intelligence_func = syreen_intelligence;
+
+ RaceDescPtr = &syreen_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/syreen/syreen.h b/src/uqm/ships/syreen/syreen.h
new file mode 100644
index 0000000..1930a1a
--- /dev/null
+++ b/src/uqm/ships/syreen/syreen.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SYREEN_H
+#define SYREEN_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_syreen (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SYREEN_H */
+
diff --git a/src/uqm/ships/thradd/Makeinfo b/src/uqm/ships/thradd/Makeinfo
new file mode 100644
index 0000000..f555509
--- /dev/null
+++ b/src/uqm/ships/thradd/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="thradd.c"
+uqm_HFILES="icode.h resinst.h thradd.h"
diff --git a/src/uqm/ships/thradd/icode.h b/src/uqm/ships/thradd/icode.h
new file mode 100644
index 0000000..070353a
--- /dev/null
+++ b/src/uqm/ships/thradd/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define THRADDASH_CODE "ship.thraddash.code"
diff --git a/src/uqm/ships/thradd/resinst.h b/src/uqm/ships/thradd/resinst.h
new file mode 100644
index 0000000..191d263
--- /dev/null
+++ b/src/uqm/ships/thradd/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define HORN_BIG_MASK_PMAP_ANIM "ship.thraddash.graphics.horn.large"
+#define HORN_MED_MASK_PMAP_ANIM "ship.thraddash.graphics.horn.medium"
+#define HORN_SML_MASK_PMAP_ANIM "ship.thraddash.graphics.horn.small"
+#define NAPALM_BIG_MASK_PMAP_ANIM "ship.thraddash.graphics.napalm.large"
+#define NAPALM_MED_MASK_PMAP_ANIM "ship.thraddash.graphics.napalm.medium"
+#define NAPALM_SML_MASK_PMAP_ANIM "ship.thraddash.graphics.napalm.small"
+#define THRADDASH_BIG_MASK_PMAP_ANIM "ship.thraddash.graphics.torch.large"
+#define THRADDASH_CAPTAIN_MASK_PMAP_ANIM "ship.thraddash.graphics.captain"
+#define THRADDASH_ICON_MASK_PMAP_ANIM "ship.thraddash.icons"
+#define THRADDASH_MED_MASK_PMAP_ANIM "ship.thraddash.graphics.torch.medium"
+#define THRADDASH_MICON_MASK_PMAP_ANIM "ship.thraddash.meleeicons"
+#define THRADDASH_RACE_STRINGS "ship.thraddash.text"
+#define THRADDASH_SHIP_SOUNDS "ship.thraddash.sounds"
+#define THRADDASH_SML_MASK_PMAP_ANIM "ship.thraddash.graphics.torch.small"
+#define THRADDASH_VICTORY_SONG "ship.thraddash.ditty"
diff --git a/src/uqm/ships/thradd/thradd.c b/src/uqm/ships/thradd/thradd.c
new file mode 100644
index 0000000..0d7a8e2
--- /dev/null
+++ b/src/uqm/ships/thradd/thradd.c
@@ -0,0 +1,400 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "thradd.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+
+// Core characteristics
+#define MAX_CREW 8
+#define MAX_ENERGY 24
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 6
+#define MAX_THRUST 28
+#define THRUST_INCREMENT 7
+#define THRUST_WAIT 0
+#define TURN_WAIT 1
+#define SHIP_MASS 7
+
+// Ion Blasters
+#define WEAPON_ENERGY_COST 2
+#define WEAPON_WAIT 12
+#define MISSILE_SPEED DISPLAY_TO_WORLD (30)
+#define MISSILE_LIFE 15
+#define MISSILE_OFFSET 3
+#define THRADDASH_OFFSET 9
+#define MISSILE_HITS 2
+#define MISSILE_DAMAGE 1
+
+// Afterburner
+#define SPECIAL_ENERGY_COST 1
+#define SPECIAL_WAIT 0
+#define SPECIAL_THRUST_INCREMENT 12
+#define SPECIAL_MAX_THRUST 72
+#define NAPALM_LIFE 48
+#define NAPALM_OFFSET 0
+#define NAPALM_HITS 1
+#define NAPALM_DAMAGE 2
+#define NAPALM_DECAY_RATE 5
+ /* Controls the speed of the afterburner "decay" animation; it will
+ * decay one step (one animation frame) per NAPALM_DECAY_RATE
+ * frames. */
+#define NUM_NAPALM_FADES 6
+
+
+static RACE_DESC thraddash_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 10, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ THRADDASH_RACE_STRINGS,
+ THRADDASH_ICON_MASK_PMAP_ANIM,
+ THRADDASH_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 833 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 2535, 8358,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ THRADDASH_BIG_MASK_PMAP_ANIM,
+ THRADDASH_MED_MASK_PMAP_ANIM,
+ THRADDASH_SML_MASK_PMAP_ANIM,
+ },
+ {
+ HORN_BIG_MASK_PMAP_ANIM,
+ HORN_MED_MASK_PMAP_ANIM,
+ HORN_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NAPALM_BIG_MASK_PMAP_ANIM,
+ NAPALM_MED_MASK_PMAP_ANIM,
+ NAPALM_SML_MASK_PMAP_ANIM,
+ },
+ {
+ THRADDASH_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ THRADDASH_VICTORY_SONG,
+ THRADDASH_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ (MISSILE_SPEED * MISSILE_LIFE) >> 1,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+thraddash_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEvalDesc->ObjectPtr)
+ {
+#define STATIONARY_SPEED WORLD_TO_VELOCITY (DISPLAY_TO_WORLD (4))
+ SIZE dx, dy;
+
+ GetCurrentVelocityComponents (
+ &lpEvalDesc->ObjectPtr->velocity, &dx, &dy
+ );
+ if (lpEvalDesc->which_turn > 8
+ || (long)dx * dx + (long)dy * dy <=
+ (long)STATIONARY_SPEED * STATIONARY_SPEED)
+ lpEvalDesc->MoveState = PURSUE;
+ else
+ lpEvalDesc->MoveState = ENTICE;
+ }
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->special_counter == 0)
+ {
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].MoveState == ENTICE)
+ {
+ if ((StarShipPtr->ship_input_state & THRUST)
+ || (ShipPtr->turn_wait == 0
+ && !(StarShipPtr->ship_input_state & (LEFT | RIGHT)))
+ || NORMALIZE_FACING (ANGLE_TO_FACING (
+ GetVelocityTravelAngle (
+ &ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr->velocity
+ ) + HALF_CIRCLE + OCTANT)
+ - StarShipPtr->ShipFacing) > ANGLE_TO_FACING (QUADRANT))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ else if (lpEvalDesc->ObjectPtr)
+ {
+ if (lpEvalDesc->MoveState == PURSUE)
+ {
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level >= WEAPON_ENERGY_COST
+ + SPECIAL_ENERGY_COST
+ && ShipPtr->turn_wait == 0
+ && !(StarShipPtr->ship_input_state & (LEFT | RIGHT))
+ && (!(StarShipPtr->cur_status_flags & SPECIAL)
+ || !(StarShipPtr->cur_status_flags
+ & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED))))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ else if (lpEvalDesc->MoveState == ENTICE)
+ {
+ COUNT direction_angle;
+ SIZE delta_x, delta_y;
+
+ delta_x = lpEvalDesc->ObjectPtr->next.location.x
+ - ShipPtr->next.location.x;
+ delta_y = lpEvalDesc->ObjectPtr->next.location.y
+ - ShipPtr->next.location.y;
+ direction_angle = ARCTAN (delta_x, delta_y);
+
+ if ((lpEvalDesc->which_turn > 24
+ && !(StarShipPtr->ship_input_state & (LEFT | RIGHT)))
+ || (lpEvalDesc->which_turn <= 16
+ && NORMALIZE_ANGLE (direction_angle
+ - (FACING_TO_ANGLE (StarShipPtr->ShipFacing) + HALF_CIRCLE)
+ + QUADRANT) <= HALF_CIRCLE
+ && (lpEvalDesc->which_turn < 12
+ || NORMALIZE_ANGLE (direction_angle
+ - (GetVelocityTravelAngle (
+ &lpEvalDesc->ObjectPtr->velocity
+ ) + HALF_CIRCLE)
+ + (OCTANT + 2)) <= ((OCTANT + 2) << 1))))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ }
+
+ if ((StarShipPtr->ship_input_state & SPECIAL)
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >=
+ SPECIAL_ENERGY_COST)
+ StarShipPtr->ship_input_state &= ~THRUST;
+ }
+}
+
+static void
+flame_napalm_preprocess (ELEMENT *ElementPtr)
+{
+ ZeroVelocityComponents (&ElementPtr->velocity);
+
+ if (ElementPtr->state_flags & NONSOLID)
+ {
+ ElementPtr->state_flags &= ~NONSOLID;
+ ElementPtr->state_flags |= APPEARING;
+ SetPrimType (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ STAMP_PRIM);
+
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectFrame (ElementPtr);
+ }
+ /* turn_wait is abused here to store the speed of the decay animation */
+ else if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ if (ElementPtr->life_span <= NUM_NAPALM_FADES * (NAPALM_DECAY_RATE + 1)
+ || GetFrameIndex (
+ ElementPtr->current.image.frame
+ ) != NUM_NAPALM_FADES)
+ ElementPtr->next.image.frame =
+ DecFrameIndex (ElementPtr->current.image.frame);
+ else if (ElementPtr->life_span > NUM_NAPALM_FADES * (NAPALM_DECAY_RATE + 1))
+ ElementPtr->next.image.frame = SetAbsFrameIndex (
+ ElementPtr->current.image.frame,
+ GetFrameCount (ElementPtr->current.image.frame) - 1
+ );
+
+ /* turn_wait is abused here to store the speed of the decay
+ * animation. */
+ ElementPtr->turn_wait = NAPALM_DECAY_RATE;
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+static COUNT
+initialize_horn (ELEMENT *ShipPtr, HELEMENT HornArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = THRADDASH_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ HornArray[0] = initialize_missile (&MissileBlock);
+
+ return (1);
+}
+
+static void
+thraddash_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (!(StarShipPtr->cur_status_flags & SPECIAL))
+ {
+ if ((StarShipPtr->old_status_flags & SPECIAL)
+ && (StarShipPtr->cur_status_flags & SHIP_AT_MAX_SPEED))
+ StarShipPtr->cur_status_flags |= SHIP_BEYOND_MAX_SPEED;
+ }
+ else if (DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ COUNT max_thrust, thrust_increment;
+ STATUS_FLAGS thrust_status;
+ HELEMENT hTrailElement;
+
+ if (!(StarShipPtr->old_status_flags & SPECIAL))
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+
+ if (ElementPtr->thrust_wait == 0)
+ ++ElementPtr->thrust_wait;
+
+ thrust_increment =
+ StarShipPtr->RaceDescPtr->characteristics.thrust_increment;
+ max_thrust = StarShipPtr->RaceDescPtr->characteristics.max_thrust;
+ StarShipPtr->RaceDescPtr->characteristics.thrust_increment =
+ SPECIAL_THRUST_INCREMENT;
+ StarShipPtr->RaceDescPtr->characteristics.max_thrust =
+ SPECIAL_MAX_THRUST;
+
+ thrust_status = inertial_thrust (ElementPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED
+ | SHIP_BEYOND_MAX_SPEED
+ | SHIP_IN_GRAVITY_WELL);
+ StarShipPtr->cur_status_flags |= thrust_status;
+
+ StarShipPtr->RaceDescPtr->characteristics.thrust_increment =
+ thrust_increment;
+ StarShipPtr->RaceDescPtr->characteristics.max_thrust = max_thrust;
+
+ {
+ MISSILE_BLOCK MissileBlock;
+
+ MissileBlock.cx = ElementPtr->next.location.x;
+ MissileBlock.cy = ElementPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ MissileBlock.face = 0;
+ MissileBlock.index = GetFrameCount (
+ StarShipPtr->RaceDescPtr->ship_data.special[0]
+ ) - 1;
+ MissileBlock.sender = ElementPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = 0;
+ MissileBlock.speed = 0;
+ MissileBlock.hit_points = NAPALM_HITS;
+ MissileBlock.damage = NAPALM_DAMAGE;
+ MissileBlock.life = NAPALM_LIFE;
+ MissileBlock.preprocess_func = flame_napalm_preprocess;
+ MissileBlock.blast_offs = NAPALM_OFFSET;
+
+ hTrailElement = initialize_missile (&MissileBlock);
+ if (hTrailElement)
+ {
+ ELEMENT *TrailElementPtr;
+
+ LockElement (hTrailElement, &TrailElementPtr);
+ SetElementStarShip (TrailElementPtr, StarShipPtr);
+ TrailElementPtr->hTarget = 0;
+
+ /* turn_wait is abused here to store the speed of the decay
+ * animation */
+ TrailElementPtr->turn_wait = NAPALM_DECAY_RATE;
+
+ TrailElementPtr->state_flags |= NONSOLID;
+ SetPrimType (
+ &(GLOBAL (DisplayArray))[TrailElementPtr->PrimIndex],
+ NO_PRIM
+ );
+
+ /* normally done during preprocess, but because
+ * object is being inserted at head rather than
+ * appended after tail it may never get preprocessed.
+ */
+ TrailElementPtr->next = TrailElementPtr->current;
+ TrailElementPtr->state_flags |= PRE_PROCESS;
+
+ UnlockElement (hTrailElement);
+ InsertElement (hTrailElement, GetHeadElement ());
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ }
+ }
+ }
+}
+
+RACE_DESC*
+init_thraddash (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ thraddash_desc.preprocess_func = thraddash_preprocess;
+ thraddash_desc.init_weapon_func = initialize_horn;
+ thraddash_desc.cyborg_control.intelligence_func = thraddash_intelligence;
+
+ RaceDescPtr = &thraddash_desc;
+
+ return (RaceDescPtr);
+}
diff --git a/src/uqm/ships/thradd/thradd.h b/src/uqm/ships/thradd/thradd.h
new file mode 100644
index 0000000..fb2a542
--- /dev/null
+++ b/src/uqm/ships/thradd/thradd.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef THRADD_H
+#define THRADD_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_thraddash (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* THRADD_H */
+
diff --git a/src/uqm/ships/umgah/Makeinfo b/src/uqm/ships/umgah/Makeinfo
new file mode 100644
index 0000000..a66b4ce
--- /dev/null
+++ b/src/uqm/ships/umgah/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="umgah.c"
+uqm_HFILES="icode.h resinst.h umgah.h"
diff --git a/src/uqm/ships/umgah/icode.h b/src/uqm/ships/umgah/icode.h
new file mode 100644
index 0000000..103f5d2
--- /dev/null
+++ b/src/uqm/ships/umgah/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define UMGAH_CODE "ship.umgah.code"
diff --git a/src/uqm/ships/umgah/resinst.h b/src/uqm/ships/umgah/resinst.h
new file mode 100644
index 0000000..4df4b07
--- /dev/null
+++ b/src/uqm/ships/umgah/resinst.h
@@ -0,0 +1,17 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define CONE_BIG_MASK_ANIM "ship.umgah.graphics.cone.large"
+#define CONE_MED_MASK_ANIM "ship.umgah.graphics.cone.medium"
+#define CONE_SML_MASK_ANIM "ship.umgah.graphics.cone.small"
+#define SPRITZ_MASK_PMAP_ANIM "ship.umgah.graphics.spritz"
+#define UMGAH_BIG_MASK_PMAP_ANIM "ship.umgah.graphics.drone.large"
+#define UMGAH_CAPTAIN_MASK_PMAP_ANIM "ship.umgah.graphics.captain"
+#define UMGAH_ICON_MASK_PMAP_ANIM "ship.umgah.icons"
+#define UMGAH_MED_MASK_PMAP_ANIM "ship.umgah.graphics.drone.medium"
+#define UMGAH_MICON_MASK_PMAP_ANIM "ship.umgah.meleeicons"
+#define UMGAH_RACE_STRINGS "ship.umgah.text"
+#define UMGAH_SHIP_SOUNDS "ship.umgah.sounds"
+#define UMGAH_SML_MASK_PMAP_ANIM "ship.umgah.graphics.drone.small"
+#define UMGAH_VICTORY_SONG "ship.umgah.ditty"
diff --git a/src/uqm/ships/umgah/umgah.c b/src/uqm/ships/umgah/umgah.c
new file mode 100644
index 0000000..370ad40
--- /dev/null
+++ b/src/uqm/ships/umgah/umgah.c
@@ -0,0 +1,434 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "umgah.h"
+#include "resinst.h"
+
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 10
+#define MAX_ENERGY 30
+#define ENERGY_REGENERATION MAX_ENERGY
+#define ENERGY_WAIT 150
+#define MAX_THRUST /* DISPLAY_TO_WORLD (5) */ 18
+#define THRUST_INCREMENT /* DISPLAY_TO_WORLD (2) */ 6
+#define THRUST_WAIT 3
+#define TURN_WAIT 4
+#define SHIP_MASS 1
+
+// Antimatter cone
+#define WEAPON_ENERGY_COST 0
+#define WEAPON_WAIT 0
+#define UMGAH_OFFSET 0
+#define CONE_OFFSET 0
+#define CONE_SPEED 0
+#define CONE_HITS 100
+#define CONE_DAMAGE 1
+#define CONE_LIFE 1
+
+// Retropropulsion
+#define SPECIAL_ENERGY_COST 1
+#define SPECIAL_WAIT 2
+#define JUMP_DIST DISPLAY_TO_WORLD (40)
+
+static RACE_DESC umgah_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | IMMEDIATE_WEAPON,
+ 7, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ UMGAH_RACE_STRINGS,
+ UMGAH_ICON_MASK_PMAP_ANIM,
+ UMGAH_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 833 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 1798, 6000,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ UMGAH_BIG_MASK_PMAP_ANIM,
+ UMGAH_MED_MASK_PMAP_ANIM,
+ UMGAH_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SPRITZ_MASK_PMAP_ANIM,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ CONE_BIG_MASK_ANIM,
+ CONE_MED_MASK_ANIM,
+ CONE_SML_MASK_ANIM,
+ },
+ {
+ UMGAH_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ UMGAH_VICTORY_SONG,
+ UMGAH_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ (LONG_RANGE_WEAPON << 2),
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+
+// Private per-instance ship data
+typedef struct
+{
+ UWORD prevFacing;
+} UMGAH_DATA;
+
+// Local typedef
+typedef UMGAH_DATA CustomShipData_t;
+
+// Retrieve race-specific ship data from a race desc
+static CustomShipData_t *
+GetCustomShipData (RACE_DESC *pRaceDesc)
+{
+ return pRaceDesc->data;
+}
+
+// Set the race-specific data in a race desc
+// (Re)Allocates its own storage for the data.
+static void
+SetCustomShipData (RACE_DESC *pRaceDesc, const CustomShipData_t *data)
+{
+ if (pRaceDesc->data == data)
+ return; // no-op
+
+ if (pRaceDesc->data) // Out with the old
+ {
+ HFree (pRaceDesc->data);
+ pRaceDesc->data = NULL;
+ }
+
+ if (data) // In with the new
+ {
+ CustomShipData_t* newData = HMalloc (sizeof (*data));
+ *newData = *data;
+ pRaceDesc->data = newData;
+ }
+}
+
+
+static void
+cone_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ StarShipPtr->RaceDescPtr->ship_data.special[0] =
+ SetRelFrameIndex (StarShipPtr->RaceDescPtr->ship_data.special[0],
+ ANGLE_TO_FACING (FULL_CIRCLE));
+
+ ElementPtr->state_flags |= APPEARING;
+}
+
+static void
+cone_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ HELEMENT hBlastElement;
+
+ hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ if (hBlastElement)
+ {
+ RemoveElement (hBlastElement);
+ FreeElement (hBlastElement);
+
+ ElementPtr0->state_flags &= ~DISAPPEARING;
+ }
+}
+
+static void
+umgah_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->MoveState == ENTICE)
+ {
+ if (lpEvalDesc->which_turn > 3
+ || (StarShipPtr->old_status_flags & SPECIAL))
+ lpEvalDesc->ObjectPtr = 0;
+ else if ((lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE)
+ && !(lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT))
+ lpEvalDesc->MoveState = AVOID;
+ else
+ lpEvalDesc->MoveState = PURSUE;
+ }
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (StarShipPtr->special_counter
+ || ObjectsOfConcern[GRAVITY_MASS_INDEX].ObjectPtr
+ || lpEvalDesc->ObjectPtr == 0)
+ {
+ StarShipPtr->RaceDescPtr->cyborg_control.WeaponRange = CLOSE_RANGE_WEAPON;
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ if (lpEvalDesc->which_turn < 16)
+ StarShipPtr->ship_input_state |= WEAPON;
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ }
+ else
+ {
+ BYTE this_turn;
+ SIZE delta_x, delta_y;
+ BOOLEAN EnemyBehind, EnoughJuice;
+
+ if (lpEvalDesc->which_turn >= 0xFF + 1)
+ this_turn = 0xFF;
+ else
+ this_turn = (BYTE)lpEvalDesc->which_turn;
+
+ EnoughJuice = (BOOLEAN)(WORLD_TO_TURN (
+ JUMP_DIST * StarShipPtr->RaceDescPtr->ship_info.energy_level
+ / SPECIAL_ENERGY_COST
+ ) > this_turn);
+ delta_x = lpEvalDesc->ObjectPtr->next.location.x -
+ ShipPtr->next.location.x;
+ delta_y = lpEvalDesc->ObjectPtr->next.location.y -
+ ShipPtr->next.location.y;
+ EnemyBehind = (BOOLEAN)(NORMALIZE_ANGLE (
+ ARCTAN (delta_x, delta_y)
+ - (FACING_TO_ANGLE (StarShipPtr->ShipFacing)
+ + HALF_CIRCLE) + (OCTANT + (OCTANT >> 2))
+ ) <= ((OCTANT + (OCTANT >> 2)) << 1));
+
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if (EnoughJuice
+ && ((StarShipPtr->old_status_flags & SPECIAL)
+ || EnemyBehind
+ || (this_turn > 6
+ && MANEUVERABILITY (
+ &EnemyStarShipPtr->RaceDescPtr->cyborg_control
+ ) <= SLOW_SHIP)
+ || (this_turn >= 16 && this_turn <= 24)))
+ StarShipPtr->RaceDescPtr->cyborg_control.WeaponRange = (LONG_RANGE_WEAPON << 3);
+ else
+ StarShipPtr->RaceDescPtr->cyborg_control.WeaponRange = CLOSE_RANGE_WEAPON;
+
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+
+ if (StarShipPtr->RaceDescPtr->cyborg_control.WeaponRange == CLOSE_RANGE_WEAPON)
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ else
+ {
+ BOOLEAN LinedUp;
+
+ StarShipPtr->ship_input_state &= ~THRUST;
+ LinedUp = (BOOLEAN)(ShipPtr->turn_wait == 0
+ && !(StarShipPtr->old_status_flags & (LEFT | RIGHT)));
+ if (((StarShipPtr->old_status_flags & SPECIAL)
+ && this_turn <= StarShipPtr->RaceDescPtr->characteristics.special_wait)
+ || (!(StarShipPtr->old_status_flags & SPECIAL)
+ && EnemyBehind && (LinedUp || this_turn < 16)))
+ {
+ StarShipPtr->ship_input_state |= SPECIAL;
+ StarShipPtr->RaceDescPtr->characteristics.special_wait = this_turn;
+
+ /* don't want him backing straight into ship */
+ if (this_turn <= 8 && LinedUp)
+ {
+ if (TFB_Random () & 1)
+ StarShipPtr->ship_input_state |= LEFT;
+ else
+ StarShipPtr->ship_input_state |= RIGHT;
+ }
+ }
+ else if (StarShipPtr->old_status_flags & SPECIAL)
+ {
+ StarShipPtr->ship_input_state &= ~(SPECIAL | LEFT | RIGHT);
+ StarShipPtr->ship_input_state |= THRUST;
+ }
+ }
+
+ if (this_turn < 16 && !EnemyBehind)
+ StarShipPtr->ship_input_state |= WEAPON;
+ }
+
+ if (!(StarShipPtr->ship_input_state & SPECIAL))
+ StarShipPtr->RaceDescPtr->characteristics.special_wait = 0xFF;
+}
+
+static COUNT
+initialize_cone (ELEMENT *ShipPtr, HELEMENT ConeArray[])
+{
+ STARSHIP *StarShipPtr;
+ UMGAH_DATA* UmgahData;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = UMGAH_OFFSET;
+ MissileBlock.speed = CONE_SPEED;
+ MissileBlock.hit_points = CONE_HITS;
+ MissileBlock.damage = CONE_DAMAGE;
+ MissileBlock.life = CONE_LIFE;
+ MissileBlock.preprocess_func = cone_preprocess;
+ MissileBlock.blast_offs = CONE_OFFSET;
+
+ // This func is called every frame while the player is holding down WEAPON
+ // Don't reset the cone FRAME to the first image every time
+ UmgahData = GetCustomShipData (StarShipPtr->RaceDescPtr);
+ if (!UmgahData || StarShipPtr->ShipFacing != UmgahData->prevFacing)
+ {
+ const UMGAH_DATA shipData = {StarShipPtr->ShipFacing};
+
+ SetCustomShipData (StarShipPtr->RaceDescPtr, &shipData);
+
+ StarShipPtr->RaceDescPtr->ship_data.special[0] =
+ SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.special[0],
+ StarShipPtr->ShipFacing);
+ }
+
+ MissileBlock.index = GetFrameIndex (StarShipPtr->RaceDescPtr->ship_data.special[0]);
+ ConeArray[0] = initialize_missile (&MissileBlock);
+
+ if (ConeArray[0])
+ {
+ ELEMENT *ConePtr;
+
+ LockElement (ConeArray[0], &ConePtr);
+ ConePtr->collision_func = cone_collision;
+ ConePtr->state_flags &= ~APPEARING;
+ ConePtr->next = ConePtr->current;
+ InitIntersectStartPoint (ConePtr);
+ InitIntersectEndPoint (ConePtr);
+ ConePtr->IntersectControl.IntersectStamp.frame =
+ StarShipPtr->RaceDescPtr->ship_data.special[0];
+ UnlockElement (ConeArray[0]);
+ }
+
+ return (1);
+}
+
+static void
+umgah_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (StarShipPtr->special_counter > 0)
+ {
+ StarShipPtr->special_counter = 0;
+
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ }
+}
+
+static void
+umgah_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ // Reset the value just in case
+ SetCustomShipData (StarShipPtr->RaceDescPtr, NULL);
+ }
+ else
+ {
+ if (ElementPtr->thrust_wait == 0
+ && (StarShipPtr->cur_status_flags & SPECIAL)
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ COUNT facing;
+
+ ProcessSound (SetAbsSoundIndex (
+ /* ZIP_BACKWARDS */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ facing = FACING_TO_ANGLE (StarShipPtr->ShipFacing) + HALF_CIRCLE;
+ DeltaVelocityComponents (&ElementPtr->velocity,
+ COSINE (facing, WORLD_TO_VELOCITY (JUMP_DIST)),
+ SINE (facing, WORLD_TO_VELOCITY (JUMP_DIST)));
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+
+ StarShipPtr->special_counter = SPECIAL_WAIT;
+ }
+ }
+}
+
+static void
+uninit_umgah (RACE_DESC *pRaceDesc)
+{
+ SetCustomShipData (pRaceDesc, NULL);
+}
+
+RACE_DESC*
+init_umgah (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ umgah_desc.uninit_func = uninit_umgah;
+ umgah_desc.preprocess_func = umgah_preprocess;
+ umgah_desc.postprocess_func = umgah_postprocess;
+ umgah_desc.init_weapon_func = initialize_cone;
+ umgah_desc.cyborg_control.intelligence_func = umgah_intelligence;
+
+ RaceDescPtr = &umgah_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/umgah/umgah.h b/src/uqm/ships/umgah/umgah.h
new file mode 100644
index 0000000..8c706bb
--- /dev/null
+++ b/src/uqm/ships/umgah/umgah.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UMGAH_H
+#define UMGAH_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_umgah (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UMGAH_H */
+
diff --git a/src/uqm/ships/urquan/Makeinfo b/src/uqm/ships/urquan/Makeinfo
new file mode 100644
index 0000000..a1d130d
--- /dev/null
+++ b/src/uqm/ships/urquan/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="urquan.c"
+uqm_HFILES="icode.h resinst.h urquan.h"
diff --git a/src/uqm/ships/urquan/icode.h b/src/uqm/ships/urquan/icode.h
new file mode 100644
index 0000000..b26e84a
--- /dev/null
+++ b/src/uqm/ships/urquan/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define URQUAN_CODE "ship.urquan.code"
diff --git a/src/uqm/ships/urquan/resinst.h b/src/uqm/ships/urquan/resinst.h
new file mode 100644
index 0000000..a7b9ecd
--- /dev/null
+++ b/src/uqm/ships/urquan/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define FIGHTER_BIG_MASK_PMAP_ANIM "ship.urquan.graphics.fighter.large"
+#define FIGHTER_MED_MASK_PMAP_ANIM "ship.urquan.graphics.fighter.medium"
+#define FIGHTER_SML_MASK_PMAP_ANIM "ship.urquan.graphics.fighter.small"
+#define FUSION_BIG_MASK_PMAP_ANIM "ship.urquan.graphics.fusion.large"
+#define FUSION_MED_MASK_PMAP_ANIM "ship.urquan.graphics.fusion.medium"
+#define FUSION_SML_MASK_PMAP_ANIM "ship.urquan.graphics.fusion.small"
+#define URQUAN_BIG_MASK_PMAP_ANIM "ship.urquan.graphics.dreadnought.large"
+#define URQUAN_CAPTAIN_MASK_PMAP_ANIM "ship.urquan.graphics.captain"
+#define URQUAN_ICON_MASK_PMAP_ANIM "ship.urquan.icons"
+#define URQUAN_MED_MASK_PMAP_ANIM "ship.urquan.graphics.dreadnought.medium"
+#define URQUAN_MICON_MASK_PMAP_ANIM "ship.urquan.meleeicons"
+#define URQUAN_RACE_STRINGS "ship.urquan.text"
+#define URQUAN_SHIP_SOUNDS "ship.urquan.sounds"
+#define URQUAN_SML_MASK_PMAP_ANIM "ship.urquan.graphics.dreadnought.small"
+#define URQUAN_VICTORY_SONG "ship.urquan.ditty"
diff --git a/src/uqm/ships/urquan/urquan.c b/src/uqm/ships/urquan/urquan.c
new file mode 100644
index 0000000..9df87d0
--- /dev/null
+++ b/src/uqm/ships/urquan/urquan.c
@@ -0,0 +1,554 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "urquan.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+
+#include <stdlib.h>
+
+// Core characteristics
+#define MAX_CREW MAX_CREW_SIZE
+#define MAX_ENERGY MAX_ENERGY_SIZE
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 4
+#define MAX_THRUST 30
+#define THRUST_INCREMENT 6
+#define THRUST_WAIT 6
+#define TURN_WAIT 4
+#define SHIP_MASS 10
+
+// Fusion blast
+#define WEAPON_ENERGY_COST 6
+#define WEAPON_WAIT 6
+#define MISSILE_SPEED DISPLAY_TO_WORLD (20)
+#define MISSILE_LIFE 20
+#define MISSILE_HITS 10
+#define MISSILE_DAMAGE 6
+#define MISSILE_OFFSET 8
+#define URQUAN_OFFSET 32
+
+// Fighters
+#define SPECIAL_ENERGY_COST 8
+#define SPECIAL_WAIT 9
+#define FIGHTER_OFFSET 4
+#define FIGHTER_SPEED DISPLAY_TO_WORLD (8)
+#define ONE_WAY_FLIGHT 125
+#define TRACK_THRESHOLD 6
+#define FIGHTER_LIFE (ONE_WAY_FLIGHT + ONE_WAY_FLIGHT + 150)
+#define FIGHTER_HITS 1
+#define FIGHTER_MASS 0
+#define FIGHTER_WEAPON_WAIT 8
+#define FIGHTER_LASER_RANGE DISPLAY_TO_WORLD (40 + FIGHTER_OFFSET)
+
+static RACE_DESC urquan_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | SEEKING_SPECIAL,
+ 30, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ URQUAN_RACE_STRINGS,
+ URQUAN_ICON_MASK_PMAP_ANIM,
+ URQUAN_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 2666 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 5750, 6000,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ URQUAN_BIG_MASK_PMAP_ANIM,
+ URQUAN_MED_MASK_PMAP_ANIM,
+ URQUAN_SML_MASK_PMAP_ANIM,
+ },
+ {
+ FUSION_BIG_MASK_PMAP_ANIM,
+ FUSION_MED_MASK_PMAP_ANIM,
+ FUSION_SML_MASK_PMAP_ANIM,
+ },
+ {
+ FIGHTER_BIG_MASK_PMAP_ANIM,
+ FIGHTER_MED_MASK_PMAP_ANIM,
+ FIGHTER_SML_MASK_PMAP_ANIM,
+ },
+ {
+ URQUAN_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ URQUAN_VICTORY_SONG,
+ URQUAN_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ MISSILE_SPEED * MISSILE_LIFE,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static COUNT
+initialize_fusion (ELEMENT *ShipPtr, HELEMENT FusionArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = URQUAN_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ FusionArray[0] = initialize_missile (&MissileBlock);
+
+ return (1);
+}
+
+static void
+fighter_postprocess (ELEMENT *ElementPtr)
+{
+ HELEMENT Laser;
+ STARSHIP *StarShipPtr;
+ LASER_BLOCK LaserBlock;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LaserBlock.cx = ElementPtr->next.location.x;
+ LaserBlock.cy = ElementPtr->next.location.y;
+ LaserBlock.face = ElementPtr->thrust_wait;
+ LaserBlock.ex = COSINE (FACING_TO_ANGLE (LaserBlock.face), FIGHTER_LASER_RANGE);
+ LaserBlock.ey = SINE (FACING_TO_ANGLE (LaserBlock.face), FIGHTER_LASER_RANGE);
+ LaserBlock.sender = ElementPtr->playerNr;
+ LaserBlock.flags = IGNORE_SIMILAR;
+ LaserBlock.pixoffs = FIGHTER_OFFSET;
+ LaserBlock.color = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E);
+ Laser = initialize_laser (&LaserBlock);
+ if (Laser)
+ {
+ ELEMENT *LaserPtr;
+
+ LockElement (Laser, &LaserPtr);
+ SetElementStarShip (LaserPtr, StarShipPtr);
+
+ ProcessSound (SetAbsSoundIndex (
+ /* FIGHTER_ZAP */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2), LaserPtr);
+
+ UnlockElement (Laser);
+ PutElement (Laser);
+ }
+
+ ElementPtr->postprocess_func = 0;
+ ElementPtr->thrust_wait = FIGHTER_WEAPON_WAIT;
+}
+
+static void
+fighter_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+
+ ++StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ if (FIGHTER_LIFE - ElementPtr->life_span > TRACK_THRESHOLD
+ && !(ElementPtr->state_flags & CHANGING))
+ {
+ BOOLEAN Enroute;
+ COUNT orig_facing, facing;
+ SIZE delta_x, delta_y;
+ ELEMENT *eptr;
+
+ Enroute = TRUE;
+
+ delta_x = StarShipPtr->RaceDescPtr->ship_info.crew_level;
+ delta_y = ElementPtr->life_span;
+
+ orig_facing = facing =
+ GetFrameIndex (ElementPtr->current.image.frame);
+ if (((delta_y & 1) || ElementPtr->hTarget
+ || TrackShip (ElementPtr, &facing) >= 0)
+ && (delta_x == 0 || delta_y >= ONE_WAY_FLIGHT))
+ ElementPtr->state_flags |= IGNORE_SIMILAR;
+ else if (delta_x)
+ {
+ LockElement (StarShipPtr->hShip, &eptr);
+ delta_x = eptr->current.location.x
+ - ElementPtr->current.location.x;
+ delta_y = eptr->current.location.y
+ - ElementPtr->current.location.y;
+ UnlockElement (StarShipPtr->hShip);
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = WRAP_DELTA_Y (delta_y);
+ facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
+ );
+
+#ifdef NEVER
+ if (delta_x < 0)
+ delta_x = -delta_x;
+ if (delta_y < 0)
+ delta_y = -delta_y;
+ if (delta_x <= LASER_RANGE && delta_y <= LASER_RANGE)
+#endif /* NEVER */
+ ElementPtr->state_flags &= ~IGNORE_SIMILAR;
+
+ Enroute = FALSE;
+ }
+
+ if (ElementPtr->thrust_wait > 0)
+ --ElementPtr->thrust_wait;
+
+ if (ElementPtr->hTarget)
+ {
+ LockElement (ElementPtr->hTarget, &eptr);
+ delta_x = eptr->current.location.x
+ - ElementPtr->current.location.x;
+ delta_y = eptr->current.location.y
+ - ElementPtr->current.location.y;
+ UnlockElement (ElementPtr->hTarget);
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = WRAP_DELTA_Y (delta_y);
+
+ if (ElementPtr->thrust_wait == 0
+ && abs (delta_x) < FIGHTER_LASER_RANGE * 3 / 4
+ && abs (delta_y) < FIGHTER_LASER_RANGE * 3 / 4
+ && delta_x * delta_x + delta_y * delta_y <
+ (FIGHTER_LASER_RANGE * 3 / 4) * (FIGHTER_LASER_RANGE * 3 / 4))
+ {
+ ElementPtr->thrust_wait =
+ (BYTE)NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
+ );
+ ElementPtr->postprocess_func = fighter_postprocess;
+ }
+
+ if (Enroute)
+ {
+ facing = GetFrameIndex (eptr->current.image.frame);
+ if (ElementPtr->turn_wait & LEFT)
+ {
+ delta_x += COSINE (FACING_TO_ANGLE (facing - 4),
+ DISPLAY_TO_WORLD (30));
+ delta_y += SINE (FACING_TO_ANGLE (facing - 4),
+ DISPLAY_TO_WORLD (30));
+ }
+ else
+ {
+ delta_x += COSINE (FACING_TO_ANGLE (facing + 4),
+ DISPLAY_TO_WORLD (30));
+ delta_y += SINE (FACING_TO_ANGLE (facing + 4),
+ DISPLAY_TO_WORLD (30));
+ }
+ facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
+ );
+ }
+ }
+ ElementPtr->state_flags |= CHANGING;
+
+ if (facing != orig_facing)
+ ElementPtr->next.image.frame = SetAbsFrameIndex (
+ ElementPtr->next.image.frame, facing
+ );
+ SetVelocityVector (
+ &ElementPtr->velocity, FIGHTER_SPEED, facing
+ );
+ }
+}
+
+static void
+fighter_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ if (GRAVITY_MASS (ElementPtr1->mass_points))
+ {
+ HELEMENT hFighterElement;
+
+ hFighterElement = AllocElement ();
+ if (hFighterElement)
+ {
+ COUNT primIndex, travel_facing;
+ SIZE delta_facing;
+ ELEMENT *FighterElementPtr;
+
+ LockElement (hFighterElement, &FighterElementPtr);
+ primIndex = FighterElementPtr->PrimIndex;
+ *FighterElementPtr = *ElementPtr0;
+ FighterElementPtr->PrimIndex = primIndex;
+ (GLOBAL (DisplayArray))[primIndex] =
+ (GLOBAL (DisplayArray))[ElementPtr0->PrimIndex];
+ FighterElementPtr->state_flags &= ~PRE_PROCESS;
+ FighterElementPtr->state_flags |= CHANGING;
+ FighterElementPtr->next = FighterElementPtr->current;
+ travel_facing = GetVelocityTravelAngle (
+ &FighterElementPtr->velocity
+ );
+ delta_facing = NORMALIZE_ANGLE (
+ ARCTAN (pPt1->x - pPt0->x, pPt1->y - pPt0->y)
+ - travel_facing);
+ if (delta_facing == 0)
+ {
+ if (FighterElementPtr->turn_wait & LEFT)
+ travel_facing -= QUADRANT;
+ else
+ travel_facing += QUADRANT;
+ }
+ else if (delta_facing <= HALF_CIRCLE)
+ travel_facing -= QUADRANT;
+ else
+ travel_facing += QUADRANT;
+
+ travel_facing = NORMALIZE_FACING (ANGLE_TO_FACING (
+ NORMALIZE_ANGLE (travel_facing)
+ ));
+ FighterElementPtr->next.image.frame =
+ SetAbsFrameIndex (FighterElementPtr->next.image.frame,
+ travel_facing);
+ SetVelocityVector (&FighterElementPtr->velocity,
+ FIGHTER_SPEED, travel_facing);
+ UnlockElement (hFighterElement);
+
+ PutElement (hFighterElement);
+ }
+
+ ElementPtr0->state_flags |= DISAPPEARING | COLLISION;
+ }
+ else if (ElementPtr0->pParent != ElementPtr1->pParent)
+ {
+ ElementPtr0->blast_offset = 0;
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+ ElementPtr0->state_flags |= DISAPPEARING | COLLISION;
+ }
+ else if (ElementPtr1->state_flags & PLAYER_SHIP)
+ {
+ ProcessSound (SetAbsSoundIndex (
+ /* FIGHTERS_RETURN */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 3), ElementPtr1);
+ DeltaCrew (ElementPtr1, 1);
+ ElementPtr0->state_flags |= DISAPPEARING | COLLISION;
+ }
+
+ if (ElementPtr0->state_flags & DISAPPEARING)
+ {
+ ElementPtr0->state_flags &= ~DISAPPEARING;
+
+ ElementPtr0->hit_points = 0;
+ ElementPtr0->life_span = 0;
+ ElementPtr0->state_flags |= NONSOLID;
+
+ --StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+}
+
+static void
+spawn_fighters (ELEMENT *ElementPtr)
+{
+ SIZE i;
+ COUNT facing;
+ SIZE delta_x, delta_y;
+ HELEMENT hFighterElement;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ facing = StarShipPtr->ShipFacing + ANGLE_TO_FACING (HALF_CIRCLE);
+ delta_x = COSINE (FACING_TO_ANGLE (facing), DISPLAY_TO_WORLD (14));
+ delta_y = SINE (FACING_TO_ANGLE (facing), DISPLAY_TO_WORLD (14));
+
+ i = ElementPtr->crew_level > 2 ? 2 : 1;
+ while (i-- && (hFighterElement = AllocElement ()))
+ {
+ SIZE sx, sy;
+ COUNT fighter_facing;
+ ELEMENT *FighterElementPtr;
+
+ DeltaCrew (ElementPtr, -1);
+
+ PutElement (hFighterElement);
+ LockElement (hFighterElement, &FighterElementPtr);
+ FighterElementPtr->hit_points = FIGHTER_HITS;
+ FighterElementPtr->mass_points = FIGHTER_MASS;
+ FighterElementPtr->thrust_wait = TRACK_THRESHOLD + 1;
+ FighterElementPtr->playerNr = ElementPtr->playerNr;
+ FighterElementPtr->state_flags = APPEARING | FINITE_LIFE
+ | CREW_OBJECT | IGNORE_SIMILAR;
+ FighterElementPtr->life_span = FIGHTER_LIFE;
+ SetPrimType (&(GLOBAL (DisplayArray))[FighterElementPtr->PrimIndex],
+ STAMP_PRIM);
+ {
+ FighterElementPtr->preprocess_func = fighter_preprocess;
+ FighterElementPtr->postprocess_func = 0;
+ FighterElementPtr->collision_func = fighter_collision;
+ FighterElementPtr->death_func = NULL;
+ }
+
+ FighterElementPtr->current.location = ElementPtr->next.location;
+ if (i == 1)
+ {
+ FighterElementPtr->turn_wait = LEFT;
+ fighter_facing = NORMALIZE_FACING (facing + 2);
+ FighterElementPtr->current.location.x += delta_x - delta_y;
+ FighterElementPtr->current.location.y += delta_y + delta_x;
+ }
+ else
+ {
+ FighterElementPtr->turn_wait = RIGHT;
+ fighter_facing = NORMALIZE_FACING (facing - 2);
+ FighterElementPtr->current.location.x += delta_x + delta_y;
+ FighterElementPtr->current.location.y += delta_y - delta_x;
+ }
+ sx = COSINE (FACING_TO_ANGLE (fighter_facing),
+ WORLD_TO_VELOCITY (FIGHTER_SPEED));
+ sy = SINE (FACING_TO_ANGLE (fighter_facing),
+ WORLD_TO_VELOCITY (FIGHTER_SPEED));
+ SetVelocityComponents (&FighterElementPtr->velocity, sx, sy);
+ FighterElementPtr->current.location.x -= VELOCITY_TO_WORLD (sx);
+ FighterElementPtr->current.location.y -= VELOCITY_TO_WORLD (sy);
+
+ FighterElementPtr->current.image.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ FighterElementPtr->current.image.frame =
+ SetAbsFrameIndex (StarShipPtr->RaceDescPtr->ship_data.special[0],
+ fighter_facing);
+ SetElementStarShip (FighterElementPtr, StarShipPtr);
+ UnlockElement (hFighterElement);
+ }
+}
+
+static void
+urquan_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ ObjectsOfConcern[ENEMY_SHIP_INDEX].MoveState = PURSUE;
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (lpEvalDesc->ObjectPtr
+ && lpEvalDesc->MoveState == ENTICE
+ && (!(lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT)
+ || lpEvalDesc->which_turn <= 8)
+ && (!(lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE)
+ || (lpEvalDesc->ObjectPtr->mass_points >= 4
+ && lpEvalDesc->which_turn == 2
+ && ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn > 16)))
+ lpEvalDesc->MoveState = PURSUE;
+
+ ship_intelligence (ShipPtr,
+ ObjectsOfConcern, ConcernCounter);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ {
+ STARSHIP *EnemyStarShipPtr = NULL;
+
+ if (lpEvalDesc->ObjectPtr)
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if (StarShipPtr->special_counter == 0
+ && lpEvalDesc->ObjectPtr
+ && StarShipPtr->RaceDescPtr->ship_info.crew_level >
+ (StarShipPtr->RaceDescPtr->ship_info.max_crew >> 2)
+ && !(EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags
+ & POINT_DEFENSE)
+ && (StarShipPtr->RaceDescPtr->characteristics.special_wait < 6
+ || (MANEUVERABILITY (
+ &EnemyStarShipPtr->RaceDescPtr->cyborg_control
+ ) <= SLOW_SHIP
+ && !(EnemyStarShipPtr->cur_status_flags & SHIP_BEYOND_MAX_SPEED))
+ || (lpEvalDesc->which_turn <= 12
+ && (StarShipPtr->ship_input_state & (LEFT | RIGHT))
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >=
+ (BYTE)(StarShipPtr->RaceDescPtr->ship_info.max_energy >> 1))))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ else
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ }
+
+ StarShipPtr->RaceDescPtr->characteristics.special_wait = 0;
+}
+
+static void
+urquan_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && ElementPtr->crew_level > 1
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ ProcessSound (SetAbsSoundIndex (
+ /* LAUNCH_FIGHTERS */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ spawn_fighters (ElementPtr);
+
+ StarShipPtr->special_counter = SPECIAL_WAIT;
+ }
+}
+
+RACE_DESC*
+init_urquan (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ urquan_desc.postprocess_func = urquan_postprocess;
+ urquan_desc.init_weapon_func = initialize_fusion;
+ urquan_desc.cyborg_control.intelligence_func = urquan_intelligence;
+
+ RaceDescPtr = &urquan_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/urquan/urquan.h b/src/uqm/ships/urquan/urquan.h
new file mode 100644
index 0000000..937d93f
--- /dev/null
+++ b/src/uqm/ships/urquan/urquan.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef URQUAN_H
+#define URQUAN_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_urquan (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* URQUAN_H */
+
diff --git a/src/uqm/ships/utwig/Makeinfo b/src/uqm/ships/utwig/Makeinfo
new file mode 100644
index 0000000..84b1d8c
--- /dev/null
+++ b/src/uqm/ships/utwig/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="utwig.c"
+uqm_HFILES="icode.h resinst.h utwig.h"
diff --git a/src/uqm/ships/utwig/icode.h b/src/uqm/ships/utwig/icode.h
new file mode 100644
index 0000000..4762b89
--- /dev/null
+++ b/src/uqm/ships/utwig/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define UTWIG_CODE "ship.utwig.code"
diff --git a/src/uqm/ships/utwig/resinst.h b/src/uqm/ships/utwig/resinst.h
new file mode 100644
index 0000000..384862e
--- /dev/null
+++ b/src/uqm/ships/utwig/resinst.h
@@ -0,0 +1,16 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define LANCE_BIG_MASK_PMAP_ANIM "ship.utwig.graphics.lance.large"
+#define LANCE_MED_MASK_PMAP_ANIM "ship.utwig.graphics.lance.medium"
+#define LANCE_SML_MASK_PMAP_ANIM "ship.utwig.graphics.lance.small"
+#define UTWIG_BIG_MASK_PMAP_ANIM "ship.utwig.graphics.jugger.large"
+#define UTWIG_CAPTAIN_MASK_PMAP_ANIM "ship.utwig.graphics.captain"
+#define UTWIG_ICON_MASK_PMAP_ANIM "ship.utwig.icons"
+#define UTWIG_MED_MASK_PMAP_ANIM "ship.utwig.graphics.jugger.medium"
+#define UTWIG_MICON_MASK_PMAP_ANIM "ship.utwig.meleeicons"
+#define UTWIG_RACE_STRINGS "ship.utwig.text"
+#define UTWIG_SHIP_SOUNDS "ship.utwig.sounds"
+#define UTWIG_SML_MASK_PMAP_ANIM "ship.utwig.graphics.jugger.small"
+#define UTWIG_VICTORY_SONG "ship.utwig.ditty"
diff --git a/src/uqm/ships/utwig/utwig.c b/src/uqm/ships/utwig/utwig.c
new file mode 100644
index 0000000..cb5f2fd
--- /dev/null
+++ b/src/uqm/ships/utwig/utwig.c
@@ -0,0 +1,380 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "utwig.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 20
+#define MAX_ENERGY 20
+#define ENERGY_REGENERATION 0
+#define ENERGY_WAIT 255
+#define MAX_THRUST 36
+#define THRUST_INCREMENT 6
+#define THRUST_WAIT 6
+#define TURN_WAIT 1
+#define SHIP_MASS 8
+
+// Weapon
+#define WEAPON_ENERGY_COST 0
+#define WEAPON_WAIT 7
+#define UTWIG_OFFSET 9
+#define MISSILE_SPEED DISPLAY_TO_WORLD (30)
+#define MISSILE_LIFE 10
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 1
+#define MISSILE_OFFSET 1
+#define LAUNCH_XOFFS0 DISPLAY_TO_WORLD (5)
+#define LAUNCH_YOFFS0 -DISPLAY_TO_WORLD (18)
+#define LAUNCH_XOFFS1 DISPLAY_TO_WORLD (13)
+#define LAUNCH_YOFFS1 -DISPLAY_TO_WORLD (9)
+#define LAUNCH_XOFFS2 DISPLAY_TO_WORLD (17)
+#define LAUNCH_YOFFS2 -DISPLAY_TO_WORLD (4)
+
+// Shield
+#define SPECIAL_ENERGY_COST 1
+#define SPECIAL_WAIT 12
+
+static RACE_DESC utwig_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | POINT_DEFENSE | SHIELD_DEFENSE,
+ 22, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY >> 1, MAX_ENERGY,
+ UTWIG_RACE_STRINGS,
+ UTWIG_ICON_MASK_PMAP_ANIM,
+ UTWIG_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 666 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 8534, 8797,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ UTWIG_BIG_MASK_PMAP_ANIM,
+ UTWIG_MED_MASK_PMAP_ANIM,
+ UTWIG_SML_MASK_PMAP_ANIM,
+ },
+ {
+ LANCE_BIG_MASK_PMAP_ANIM,
+ LANCE_MED_MASK_PMAP_ANIM,
+ LANCE_SML_MASK_PMAP_ANIM,
+ },
+ {
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ UTWIG_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ UTWIG_VICTORY_SONG,
+ UTWIG_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ CLOSE_RANGE_WEAPON,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static COUNT
+initialize_lance (ELEMENT *ShipPtr, HELEMENT WeaponArray[])
+{
+ COUNT i;
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ MissileBlock.pixoffs = 0;
+
+ for (i = 0; i < 3; ++i)
+ {
+ COUNT angle;
+ SIZE sin0 = 0, cos0 = 0;
+ SIZE sin1sin0, cos1sin0, cos1cos0, sin1cos0;
+
+ switch (i)
+ {
+ case 0:
+ cos0 = LAUNCH_XOFFS0;
+ sin0 = LAUNCH_YOFFS0;
+ break;
+ case 1:
+ cos0 = LAUNCH_XOFFS1;
+ sin0 = LAUNCH_YOFFS1;
+ break;
+ case 2:
+ cos0 = LAUNCH_XOFFS2;
+ sin0 = LAUNCH_YOFFS2;
+ break;
+ }
+ angle = FACING_TO_ANGLE (MissileBlock.face) + QUADRANT;
+ cos1cos0 = COSINE (angle, cos0);
+ sin1sin0 = SINE (angle, sin0);
+ sin1cos0 = SINE (angle, cos0);
+ cos1sin0 = COSINE (angle, sin0);
+
+ cos0 = cos1cos0 - sin1sin0;
+ sin0 = sin1cos0 + cos1sin0;
+ MissileBlock.cx = ShipPtr->next.location.x + cos0;
+ MissileBlock.cy = ShipPtr->next.location.y + sin0;
+ WeaponArray[(i << 1)] = initialize_missile (&MissileBlock);
+
+ cos0 = -cos1cos0 - sin1sin0;
+ sin0 = -sin1cos0 + cos1sin0;
+ MissileBlock.cx = ShipPtr->next.location.x + cos0;
+ MissileBlock.cy = ShipPtr->next.location.y + sin0;
+ WeaponArray[(i << 1) + 1] = initialize_missile (&MissileBlock);
+ }
+
+ return (6);
+}
+
+static void
+utwig_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ SIZE ShieldStatus;
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level == 0)
+ ShieldStatus = 0;
+ else
+ {
+ ShieldStatus = -1;
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->MoveState == ENTICE)
+ {
+ ShieldStatus = 0;
+ if (!(lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE))
+ lpEvalDesc->MoveState = PURSUE;
+ else if (lpEvalDesc->ObjectPtr->mass_points
+ || (lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT))
+ {
+ if ((lpEvalDesc->which_turn >>= 1) == 0)
+ lpEvalDesc->which_turn = 1;
+
+ if (lpEvalDesc->ObjectPtr->mass_points)
+ lpEvalDesc->ObjectPtr = 0;
+ else
+ lpEvalDesc->MoveState = PURSUE;
+ ShieldStatus = 1;
+ }
+ }
+ }
+
+ if (StarShipPtr->special_counter == 0)
+ {
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (ShieldStatus)
+ {
+ if ((ShieldStatus > 0 || lpEvalDesc->ObjectPtr)
+ && lpEvalDesc->which_turn <= 2
+ && (ShieldStatus > 0
+ || (lpEvalDesc->ObjectPtr->state_flags
+ & PLAYER_SHIP) /* means IMMEDIATE WEAPON */
+ || PlotIntercept (lpEvalDesc->ObjectPtr,
+ ShipPtr, 2, 0))
+ && (TFB_Random () & 3))
+ {
+ StarShipPtr->ship_input_state |= SPECIAL;
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ }
+
+ lpEvalDesc->ObjectPtr = 0;
+ }
+ }
+
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level
+ && (lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX])->ObjectPtr)
+ {
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if (!(EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags
+ & IMMEDIATE_WEAPON))
+ lpEvalDesc->MoveState = PURSUE;
+ }
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+}
+
+static void
+utwig_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (ElementPtr0->life_span > NORMAL_LIFE
+ && (ElementPtr1->state_flags & FINITE_LIFE)
+ && ElementPtr1->mass_points)
+ ElementPtr0->life_span += ElementPtr1->mass_points;
+
+ collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+}
+
+static void
+utwig_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ PRIMITIVE *lpPrim;
+
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ ElementPtr->collision_func = utwig_collision;
+ }
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (ElementPtr->life_span > (NORMAL_LIFE + 1))
+ {
+ DeltaEnergy (ElementPtr,
+ ElementPtr->life_span - (NORMAL_LIFE + 1));
+
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1),
+ ElementPtr);
+ }
+
+ if (!(StarShipPtr->cur_status_flags & SPECIAL))
+ StarShipPtr->special_counter = 0;
+ else if (StarShipPtr->special_counter % (SPECIAL_WAIT >> 1) == 0)
+ {
+ if (!DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ StarShipPtr->RaceDescPtr->ship_info.ship_flags &=
+ ~(POINT_DEFENSE | SHIELD_DEFENSE);
+ else if (StarShipPtr->special_counter == 0)
+ {
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ ProcessSound (SetAbsSoundIndex (
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2),
+ ElementPtr);
+ }
+ }
+
+ lpPrim = &(GLOBAL (DisplayArray))[ElementPtr->PrimIndex];
+ if (StarShipPtr->special_counter == 0)
+ {
+ // The shield is off.
+ SetPrimColor (lpPrim,
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1C, 0x00), 0x78));
+ ElementPtr->colorCycleIndex = 0;
+ ElementPtr->life_span = NORMAL_LIFE;
+ SetPrimType (lpPrim, STAMP_PRIM);
+ }
+ else
+ {
+ // The shield is on.
+
+ /* Originally, this table also contained the now commented out
+ * entries. It then used some if statements to skip over these.
+ * The current behaviour is the same as the old behavior,
+ * but I am not sure that the old behavior was intended. - SvdB
+ */
+ static const Color colorTable[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7a),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7c),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7d),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7e),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7f),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x2a),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2c),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2d),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2e),
+ //BUILD_COLOR (MAKE_RGB15_INIT (0x0B, 0x00, 0x00), 0x2f),
+ };
+ const size_t colorTableCount =
+ sizeof colorTable / sizeof colorTable[0];
+
+ if (StarShipPtr->weapon_counter == 0)
+ ++StarShipPtr->weapon_counter;
+
+ // colorCycleIndex is actually 1 higher than the entry in colorTable
+ // which is currently used, as it is 0 when the shield is off,
+ // and we don't want to skip over the first entry of the table.
+ ElementPtr->colorCycleIndex++;
+ if (ElementPtr->colorCycleIndex == colorTableCount + 1)
+ ElementPtr->colorCycleIndex = 1;
+
+ SetPrimColor (lpPrim, colorTable[ElementPtr->colorCycleIndex - 1]);
+
+ ElementPtr->life_span = NORMAL_LIFE + 1;
+ SetPrimType (lpPrim, STAMPFILL_PRIM);
+ }
+}
+
+RACE_DESC*
+init_utwig (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ utwig_desc.preprocess_func = utwig_preprocess;
+ utwig_desc.init_weapon_func = initialize_lance;
+ utwig_desc.cyborg_control.intelligence_func = utwig_intelligence;
+
+ RaceDescPtr = &utwig_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/utwig/utwig.h b/src/uqm/ships/utwig/utwig.h
new file mode 100644
index 0000000..83ab97e
--- /dev/null
+++ b/src/uqm/ships/utwig/utwig.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UTWIG_H
+#define UTWIG_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_utwig (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UTWIG_H */
+
diff --git a/src/uqm/ships/vux/Makeinfo b/src/uqm/ships/vux/Makeinfo
new file mode 100644
index 0000000..13c9264
--- /dev/null
+++ b/src/uqm/ships/vux/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="vux.c"
+uqm_HFILES="icode.h resinst.h vux.h"
diff --git a/src/uqm/ships/vux/icode.h b/src/uqm/ships/vux/icode.h
new file mode 100644
index 0000000..0bd37d7
--- /dev/null
+++ b/src/uqm/ships/vux/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define VUX_CODE "ship.vux.code"
diff --git a/src/uqm/ships/vux/resinst.h b/src/uqm/ships/vux/resinst.h
new file mode 100644
index 0000000..c3e04bf
--- /dev/null
+++ b/src/uqm/ships/vux/resinst.h
@@ -0,0 +1,17 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define LIMPETS_BIG_MASK_PMAP_ANIM "ship.vux.graphics.limpets.large"
+#define LIMPETS_MED_MASK_PMAP_ANIM "ship.vux.graphics.limpets.medium"
+#define LIMPETS_SML_MASK_PMAP_ANIM "ship.vux.graphics.limpets.small"
+#define SLIME_MASK_PMAP_ANIM "ship.vux.graphics.slime"
+#define VUX_BIG_MASK_PMAP_ANIM "ship.vux.graphics.intruder.large"
+#define VUX_CAPTAIN_MASK_PMAP_ANIM "ship.vux.graphics.captain"
+#define VUX_ICON_MASK_PMAP_ANIM "ship.vux.icons"
+#define VUX_MED_MASK_PMAP_ANIM "ship.vux.graphics.intruder.medium"
+#define VUX_MICON_MASK_PMAP_ANIM "ship.vux.meleeicons"
+#define VUX_RACE_STRINGS "ship.vux.text"
+#define VUX_SHIP_SOUNDS "ship.vux.sounds"
+#define VUX_SML_MASK_PMAP_ANIM "ship.vux.graphics.intruder.small"
+#define VUX_VICTORY_SONG "ship.vux.ditty"
diff --git a/src/uqm/ships/vux/vux.c b/src/uqm/ships/vux/vux.c
new file mode 100644
index 0000000..83e6c47
--- /dev/null
+++ b/src/uqm/ships/vux/vux.c
@@ -0,0 +1,398 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "vux.h"
+#include "resinst.h"
+
+#include "uqm/globdata.h"
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 20
+#define MAX_ENERGY 40
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 8
+#define MAX_THRUST /* DISPLAY_TO_WORLD (5) */ 21
+#define THRUST_INCREMENT /* DISPLAY_TO_WORLD (2) */ 7
+#define THRUST_WAIT 4
+#define TURN_WAIT 6
+#define SHIP_MASS 6
+
+// Laser
+#define WEAPON_ENERGY_COST 1
+#define WEAPON_WAIT 0
+#define VUX_OFFSET 12
+#define LASER_BASE 150
+#define LASER_RANGE DISPLAY_TO_WORLD (LASER_BASE + VUX_OFFSET)
+
+// Limpet
+#define SPECIAL_ENERGY_COST 2
+#define SPECIAL_WAIT 7
+#define LIMPET_SPEED 25
+#define LIMPET_OFFSET 8
+#define LIMPET_LIFE 80
+#define LIMPET_HITS 1
+#define LIMPET_DAMAGE 0
+#define MIN_THRUST_INCREMENT DISPLAY_TO_WORLD (1)
+
+// Aggressive Entry
+#define WARP_OFFSET 46
+ /* How far outside of the laser range can the ship warp in. */
+#define MAXX_ENTRY_DIST DISPLAY_TO_WORLD ((LASER_BASE + VUX_OFFSET + WARP_OFFSET) << 1)
+#define MAXY_ENTRY_DIST DISPLAY_TO_WORLD ((LASER_BASE + VUX_OFFSET + WARP_OFFSET) << 1)
+ /* Originally, the warp distance was:
+ * DISPLAY_TO_WORLD (SPACE_HEIGHT << 1)
+ * where SPACE_HEIGHT = SCREEN_HEIGHT - (SAFE_Y * 2)
+ * But in reality this should be relative to the laser-range. */
+
+static RACE_DESC vux_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | SEEKING_SPECIAL | IMMEDIATE_WEAPON,
+ 12, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ VUX_RACE_STRINGS,
+ VUX_ICON_MASK_PMAP_ANIM,
+ VUX_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 900 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 4412, 1558,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ VUX_BIG_MASK_PMAP_ANIM,
+ VUX_MED_MASK_PMAP_ANIM,
+ VUX_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SLIME_MASK_PMAP_ANIM,
+ NULL_RESOURCE,
+ NULL_RESOURCE,
+ },
+ {
+ LIMPETS_BIG_MASK_PMAP_ANIM,
+ LIMPETS_MED_MASK_PMAP_ANIM,
+ LIMPETS_SML_MASK_PMAP_ANIM,
+ },
+ {
+ VUX_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ VUX_VICTORY_SONG,
+ VUX_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ CLOSE_RANGE_WEAPON,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+
+static void
+limpet_preprocess (ELEMENT *ElementPtr)
+{
+ COUNT facing, orig_facing;
+ SIZE delta_facing;
+
+ facing = orig_facing = NORMALIZE_FACING (ANGLE_TO_FACING (
+ GetVelocityTravelAngle (&ElementPtr->velocity)
+ ));
+ if ((delta_facing = TrackShip (ElementPtr, &facing)) > 0)
+ {
+ facing = orig_facing + delta_facing;
+ SetVelocityVector (&ElementPtr->velocity, LIMPET_SPEED, facing);
+ }
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->next.image.frame);
+
+ ElementPtr->state_flags |= CHANGING;
+}
+
+static void
+limpet_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ if (ElementPtr1->state_flags & PLAYER_SHIP)
+ {
+ STAMP s;
+ STARSHIP *StarShipPtr;
+ RACE_DESC *RDPtr;
+
+ GetElementStarShip (ElementPtr1, &StarShipPtr);
+ RDPtr = StarShipPtr->RaceDescPtr;
+
+ if (++RDPtr->characteristics.turn_wait == 0)
+ --RDPtr->characteristics.turn_wait;
+ if (++RDPtr->characteristics.thrust_wait == 0)
+ --RDPtr->characteristics.thrust_wait;
+ if (RDPtr->characteristics.thrust_increment <= MIN_THRUST_INCREMENT)
+ {
+ RDPtr->characteristics.max_thrust =
+ RDPtr->characteristics.thrust_increment << 1;
+ }
+ else
+ {
+ COUNT num_thrusts;
+
+ num_thrusts = RDPtr->characteristics.max_thrust /
+ RDPtr->characteristics.thrust_increment;
+ --RDPtr->characteristics.thrust_increment;
+ RDPtr->characteristics.max_thrust =
+ RDPtr->characteristics.thrust_increment * num_thrusts;
+ }
+ RDPtr->cyborg_control.ManeuverabilityIndex = 0;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ ProcessSound (SetAbsSoundIndex (
+ /* LIMPET_AFFIXES */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2), ElementPtr1);
+ s.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_data.weapon[0], (COUNT)TFB_Random ()
+ );
+ ModifySilhouette (ElementPtr1, &s, MODIFY_IMAGE);
+ }
+
+ ElementPtr0->hit_points = 0;
+ ElementPtr0->life_span = 0;
+ ElementPtr0->state_flags |= COLLISION | DISAPPEARING;
+
+ (void) pPt0; /* Satisfying compiler (unused parameter) */
+ (void) pPt1; /* Satisfying compiler (unused parameter) */
+}
+
+static void
+spawn_limpets (ELEMENT *ElementPtr)
+{
+ HELEMENT Limpet;
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ MissileBlock.face = StarShipPtr->ShipFacing + HALF_CIRCLE;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ElementPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = LIMPET_OFFSET;
+ MissileBlock.speed = LIMPET_SPEED;
+ MissileBlock.hit_points = LIMPET_HITS;
+ MissileBlock.damage = LIMPET_DAMAGE;
+ MissileBlock.life = LIMPET_LIFE;
+ MissileBlock.preprocess_func = limpet_preprocess;
+ MissileBlock.blast_offs = 0;
+
+ MissileBlock.cx = ElementPtr->next.location.x;
+ MissileBlock.cy = ElementPtr->next.location.y;
+ Limpet = initialize_missile (&MissileBlock);
+ if (Limpet)
+ {
+ ELEMENT *LimpetPtr;
+
+ LockElement (Limpet, &LimpetPtr);
+ LimpetPtr->collision_func = limpet_collision;
+ SetElementStarShip (LimpetPtr, StarShipPtr);
+ UnlockElement (Limpet);
+
+ PutElement (Limpet);
+ }
+}
+
+static COUNT
+initialize_horrific_laser (ELEMENT *ShipPtr, HELEMENT LaserArray[])
+{
+ STARSHIP *StarShipPtr;
+ LASER_BLOCK LaserBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ LaserBlock.face = StarShipPtr->ShipFacing;
+ LaserBlock.cx = ShipPtr->next.location.x;
+ LaserBlock.cy = ShipPtr->next.location.y;
+ LaserBlock.ex = COSINE (FACING_TO_ANGLE (LaserBlock.face), LASER_RANGE);
+ LaserBlock.ey = SINE (FACING_TO_ANGLE (LaserBlock.face), LASER_RANGE);
+ LaserBlock.sender = ShipPtr->playerNr;
+ LaserBlock.flags = IGNORE_SIMILAR;
+ LaserBlock.pixoffs = VUX_OFFSET;
+ LaserBlock.color = BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x0A), 0x0A);
+ LaserArray[0] = initialize_laser (&LaserBlock);
+
+ return (1);
+}
+
+static void
+vux_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ EVALUATE_DESC *lpEvalDesc;
+ STARSHIP *StarShipPtr;
+
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ lpEvalDesc->MoveState = PURSUE;
+ if (ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr != 0
+ && ObjectsOfConcern[ENEMY_WEAPON_INDEX].MoveState == ENTICE)
+ {
+ if ((ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr->state_flags
+ & FINITE_LIFE)
+ && !(ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr->state_flags
+ & CREW_OBJECT))
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX].MoveState = AVOID;
+ else
+ ObjectsOfConcern[ENEMY_WEAPON_INDEX].MoveState = PURSUE;
+ }
+
+ ship_intelligence (ShipPtr,
+ ObjectsOfConcern, ConcernCounter);
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->special_counter == 0
+ && lpEvalDesc->ObjectPtr != 0
+ && lpEvalDesc->which_turn <= 12
+ && (StarShipPtr->ship_input_state & (LEFT | RIGHT))
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >=
+ (BYTE)(StarShipPtr->RaceDescPtr->ship_info.max_energy >> 1))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ else
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+}
+
+static void
+vux_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ ProcessSound (SetAbsSoundIndex (
+ /* LAUNCH_LIMPET */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ spawn_limpets (ElementPtr);
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+}
+
+static void
+vux_preprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ COUNT facing;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ facing = StarShipPtr->ShipFacing;
+ if (LOBYTE (GLOBAL (CurrentActivity)) != IN_ENCOUNTER
+ && TrackShip (ElementPtr, &facing) >= 0)
+ {
+ ELEMENT *OtherShipPtr;
+
+ LockElement (ElementPtr->hTarget, &OtherShipPtr);
+
+ do
+ {
+ SIZE dx, dy;
+
+ ElementPtr->current.location.x =
+ (OtherShipPtr->current.location.x -
+ (MAXX_ENTRY_DIST >> 1)) +
+ ((COUNT)TFB_Random () % MAXX_ENTRY_DIST);
+ ElementPtr->current.location.y =
+ (OtherShipPtr->current.location.y -
+ (MAXY_ENTRY_DIST >> 1)) +
+ ((COUNT)TFB_Random () % MAXY_ENTRY_DIST);
+ dx = OtherShipPtr->current.location.x -
+ ElementPtr->current.location.x;
+ dy = OtherShipPtr->current.location.y -
+ ElementPtr->current.location.y;
+ facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (dx, dy))
+ );
+ ElementPtr->current.image.frame =
+ SetAbsFrameIndex (ElementPtr->current.image.frame,
+ facing);
+
+ ElementPtr->current.location.x =
+ WRAP_X (DISPLAY_ALIGN (ElementPtr->current.location.x));
+ ElementPtr->current.location.y =
+ WRAP_Y (DISPLAY_ALIGN (ElementPtr->current.location.y));
+ } while (CalculateGravity (ElementPtr)
+ || TimeSpaceMatterConflict (ElementPtr));
+
+ UnlockElement (ElementPtr->hTarget);
+ ElementPtr->hTarget = 0;
+
+ ElementPtr->next = ElementPtr->current;
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectFrame (ElementPtr);
+
+ StarShipPtr->ShipFacing = facing;
+ }
+
+ StarShipPtr->RaceDescPtr->preprocess_func = 0;
+ }
+}
+
+RACE_DESC*
+init_vux (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ vux_desc.preprocess_func = vux_preprocess;
+ vux_desc.postprocess_func = vux_postprocess;
+ vux_desc.init_weapon_func = initialize_horrific_laser;
+ vux_desc.cyborg_control.intelligence_func = vux_intelligence;
+
+ RaceDescPtr = &vux_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/vux/vux.h b/src/uqm/ships/vux/vux.h
new file mode 100644
index 0000000..3fa2f3f
--- /dev/null
+++ b/src/uqm/ships/vux/vux.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef VUX_H
+#define VUX_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_vux (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* VUX_H */
+
diff --git a/src/uqm/ships/yehat/Makeinfo b/src/uqm/ships/yehat/Makeinfo
new file mode 100644
index 0000000..73d70c3
--- /dev/null
+++ b/src/uqm/ships/yehat/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="yehat.c"
+uqm_HFILES="icode.h resinst.h yehat.h"
diff --git a/src/uqm/ships/yehat/icode.h b/src/uqm/ships/yehat/icode.h
new file mode 100644
index 0000000..81cac0e
--- /dev/null
+++ b/src/uqm/ships/yehat/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define YEHAT_CODE "ship.yehat.code"
diff --git a/src/uqm/ships/yehat/resinst.h b/src/uqm/ships/yehat/resinst.h
new file mode 100644
index 0000000..ad325a5
--- /dev/null
+++ b/src/uqm/ships/yehat/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SHIELD_BIG_MASK_ANIM "ship.yehat.graphics.shield.large"
+#define SHIELD_MED_MASK_ANIM "ship.yehat.graphics.shield.medium"
+#define SHIELD_SML_MASK_ANIM "ship.yehat.graphics.shield.small"
+#define YEHAT_BIG_MASK_PMAP_ANIM "ship.yehat.graphics.terminator.large"
+#define YEHAT_CANNON_BIG_MASK_PMAP_ANIM "ship.yehat.graphics.missile.large"
+#define YEHAT_CANNON_MED_MASK_PMAP_ANIM "ship.yehat.graphics.missile.medium"
+#define YEHAT_CANNON_SML_MASK_PMAP_ANIM "ship.yehat.graphics.missile.small"
+#define YEHAT_CAPTAIN_MASK_PMAP_ANIM "ship.yehat.graphics.captain"
+#define YEHAT_ICON_MASK_PMAP_ANIM "ship.yehat.icons"
+#define YEHAT_MED_MASK_PMAP_ANIM "ship.yehat.graphics.terminator.medium"
+#define YEHAT_MICON_MASK_PMAP_ANIM "ship.yehat.meleeicons"
+#define YEHAT_RACE_STRINGS "ship.yehat.text"
+#define YEHAT_SHIP_SOUNDS "ship.yehat.sounds"
+#define YEHAT_SML_MASK_PMAP_ANIM "ship.yehat.graphics.terminator.small"
+#define YEHAT_VICTORY_SONG "ship.yehat.ditty"
diff --git a/src/uqm/ships/yehat/yehat.c b/src/uqm/ships/yehat/yehat.c
new file mode 100644
index 0000000..f3d0fb8
--- /dev/null
+++ b/src/uqm/ships/yehat/yehat.c
@@ -0,0 +1,369 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "yehat.h"
+#include "resinst.h"
+
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 20
+#define MAX_ENERGY 10
+#define ENERGY_REGENERATION 2
+#define ENERGY_WAIT 6
+#define MAX_THRUST 30
+#define THRUST_INCREMENT 6
+#define THRUST_WAIT 2
+#define TURN_WAIT 2
+#define SHIP_MASS 3
+
+// Twin Pulse Cannon
+#define WEAPON_ENERGY_COST 1
+#define WEAPON_WAIT 0
+#define YEHAT_OFFSET 16
+#define LAUNCH_OFFS DISPLAY_TO_WORLD (8)
+#define MISSILE_SPEED DISPLAY_TO_WORLD (20)
+#define MISSILE_LIFE 10
+#define MISSILE_HITS 1
+#define MISSILE_DAMAGE 1
+#define MISSILE_OFFSET 1
+
+// Force Shield
+#define SPECIAL_ENERGY_COST 3
+#define SPECIAL_WAIT 2
+#define SHIELD_LIFE 10
+
+static RACE_DESC yehat_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE | SHIELD_DEFENSE,
+ 23, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ YEHAT_RACE_STRINGS,
+ YEHAT_ICON_MASK_PMAP_ANIM,
+ YEHAT_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 750 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 4970, 40,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ YEHAT_BIG_MASK_PMAP_ANIM,
+ YEHAT_MED_MASK_PMAP_ANIM,
+ YEHAT_SML_MASK_PMAP_ANIM,
+ },
+ {
+ YEHAT_CANNON_BIG_MASK_PMAP_ANIM,
+ YEHAT_CANNON_MED_MASK_PMAP_ANIM,
+ YEHAT_CANNON_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SHIELD_BIG_MASK_ANIM,
+ SHIELD_MED_MASK_ANIM,
+ SHIELD_SML_MASK_ANIM,
+ },
+ {
+ YEHAT_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ YEHAT_VICTORY_SONG,
+ YEHAT_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ MISSILE_SPEED * MISSILE_LIFE / 3,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static COUNT
+initialize_standard_missiles (ELEMENT *ShipPtr, HELEMENT MissileArray[])
+{
+ SIZE offs_x, offs_y;
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = YEHAT_OFFSET;
+ MissileBlock.speed = MISSILE_SPEED;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = NULL;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+
+ offs_x = -SINE (FACING_TO_ANGLE (MissileBlock.face), LAUNCH_OFFS);
+ offs_y = COSINE (FACING_TO_ANGLE (MissileBlock.face), LAUNCH_OFFS);
+
+ MissileBlock.cx = ShipPtr->next.location.x + offs_x;
+ MissileBlock.cy = ShipPtr->next.location.y + offs_y;
+ MissileArray[0] = initialize_missile (&MissileBlock);
+
+ MissileBlock.cx = ShipPtr->next.location.x - offs_x;
+ MissileBlock.cy = ShipPtr->next.location.y - offs_y;
+ MissileArray[1] = initialize_missile (&MissileBlock);
+
+ return (2);
+}
+
+static void
+yehat_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ SIZE ShieldStatus;
+ STARSHIP *StarShipPtr;
+ EVALUATE_DESC *lpEvalDesc;
+
+ ShieldStatus = -1;
+ lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
+ if (lpEvalDesc->ObjectPtr && lpEvalDesc->MoveState == ENTICE)
+ {
+ ShieldStatus = 0;
+ if (!(lpEvalDesc->ObjectPtr->state_flags & (FINITE_LIFE | CREW_OBJECT)))
+ lpEvalDesc->MoveState = PURSUE;
+ else if (lpEvalDesc->ObjectPtr->mass_points
+ || (lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT))
+ {
+ if (!(lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE))
+ lpEvalDesc->which_turn <<= 1;
+ else
+ {
+ if ((lpEvalDesc->which_turn >>= 1) == 0)
+ lpEvalDesc->which_turn = 1;
+
+ if (lpEvalDesc->ObjectPtr->mass_points)
+ lpEvalDesc->ObjectPtr = 0;
+ else
+ lpEvalDesc->MoveState = PURSUE;
+ }
+ ShieldStatus = 1;
+ }
+ }
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->special_counter == 0)
+ {
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+ if (ShieldStatus)
+ {
+ if (ShipPtr->life_span <= NORMAL_LIFE + 1
+ && (ShieldStatus > 0 || lpEvalDesc->ObjectPtr)
+ && lpEvalDesc->which_turn <= 2
+ && (ShieldStatus > 0
+ || (lpEvalDesc->ObjectPtr->state_flags
+ & PLAYER_SHIP) /* means IMMEDIATE WEAPON */
+ || PlotIntercept (lpEvalDesc->ObjectPtr,
+ ShipPtr, 2, 0))
+ && (TFB_Random () & 3))
+ StarShipPtr->ship_input_state |= SPECIAL;
+
+ if (lpEvalDesc->ObjectPtr
+ && !(lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT))
+ lpEvalDesc->ObjectPtr = 0;
+ }
+ }
+
+ if ((lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX])->ObjectPtr)
+ {
+ STARSHIP *EnemyStarShipPtr;
+
+ GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
+ if (!(EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags
+ & IMMEDIATE_WEAPON))
+ lpEvalDesc->MoveState = PURSUE;
+ }
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+/*
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level <= SPECIAL_ENERGY_COST)
+ StarShipPtr->ship_input_state &= ~WEAPON;
+*/
+}
+
+static void
+yehat_postprocess (ELEMENT *ElementPtr)
+{
+ if (!(ElementPtr->state_flags & NONSOLID))
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ /* take care of shield effect */
+ if (StarShipPtr->special_counter > 0)
+ {
+ if (ElementPtr->life_span == NORMAL_LIFE)
+ StarShipPtr->special_counter = 0;
+ else
+ {
+#ifdef OLD
+ SetPrimColor (
+ &(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F)
+ );
+ SetPrimType (
+ &(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ STAMPFILL_PRIM
+ );
+#endif /* OLD */
+
+ ProcessSound (SetAbsSoundIndex (
+ /* YEHAT_SHIELD_ON */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+ DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST);
+ }
+ }
+
+#ifdef OLD
+ if (ElementPtr->life_span > NORMAL_LIFE)
+ {
+ HELEMENT hShipElement;
+
+ if (hShipElement = AllocElement ())
+ {
+ ELEMENT *ShipElementPtr;
+
+ InsertElement (hShipElement, GetSuccElement (ElementPtr));
+ LockElement (hShipElement, &ShipElementPtr);
+ ShipElementPtr->playerNr = ElementPtr->playerNr;
+ ShipElementPtr->state_flags =
+ /* in place of APPEARING */
+ (CHANGING | PRE_PROCESS | POST_PROCESS)
+ | FINITE_LIFE | NONSOLID;
+ SetPrimType (
+ &(GLOBAL (DisplayArray))[ShipElementPtr->PrimIndex],
+ STAMP_PRIM
+ );
+
+ ShipElementPtr->life_span = 0; /* because preprocessing
+ * will not be done
+ */
+ ShipElementPtr->current.location = ElementPtr->next.location;
+ ShipElementPtr->current.image.farray = StarShipPtr->RaceDescPtr->ship_data.ship;
+ ShipElementPtr->current.image.frame =
+ SetAbsFrameIndex (StarShipPtr->RaceDescPtr->ship_data.ship[0],
+ StarShipPtr->ShipFacing);
+ ShipElementPtr->next = ShipElementPtr->current;
+ ShipElementPtr->preprocess_func =
+ ShipElementPtr->postprocess_func =
+ ShipElementPtr->death_func = NULL;
+ ZeroVelocityComponents (&ShipElementPtr->velocity);
+
+ UnlockElement (hShipElement);
+ }
+ }
+#endif /* OLD */
+ }
+}
+
+static void
+yehat_preprocess (ELEMENT *ElementPtr)
+{
+ if (!(ElementPtr->state_flags & APPEARING))
+ {
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((ElementPtr->life_span > NORMAL_LIFE
+ /* take care of shield effect */
+ && --ElementPtr->life_span == NORMAL_LIFE)
+ || (ElementPtr->life_span == NORMAL_LIFE
+ && ElementPtr->next.image.farray
+ == StarShipPtr->RaceDescPtr->ship_data.special))
+ {
+#ifdef NEVER
+ SetPrimType (
+ &(GLOBAL (DisplayArray))[ElementPtr->PrimIndex],
+ STAMP_PRIM
+ );
+#endif /* NEVER */
+
+ ElementPtr->next.image.farray = StarShipPtr->RaceDescPtr->ship_data.ship;
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (StarShipPtr->RaceDescPtr->ship_data.ship[0],
+ ElementPtr->next.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+ }
+
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0)
+ {
+ if (StarShipPtr->RaceDescPtr->ship_info.energy_level < SPECIAL_ENERGY_COST)
+ DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST); /* so text will flash */
+ else
+ {
+ ElementPtr->life_span = SHIELD_LIFE + NORMAL_LIFE;
+
+ ElementPtr->next.image.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (StarShipPtr->RaceDescPtr->ship_data.special[0],
+ ElementPtr->next.image.frame);
+ ElementPtr->state_flags |= CHANGING;
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+ }
+ }
+}
+
+RACE_DESC*
+init_yehat (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ yehat_desc.preprocess_func = yehat_preprocess;
+ yehat_desc.postprocess_func = yehat_postprocess;
+ yehat_desc.init_weapon_func = initialize_standard_missiles;
+ yehat_desc.cyborg_control.intelligence_func = yehat_intelligence;
+
+ RaceDescPtr = &yehat_desc;
+
+ return (RaceDescPtr);
+}
diff --git a/src/uqm/ships/yehat/yehat.h b/src/uqm/ships/yehat/yehat.h
new file mode 100644
index 0000000..8b3dd5a
--- /dev/null
+++ b/src/uqm/ships/yehat/yehat.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef YEHAT_H
+#define YEHAT_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_yehat (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* YEHAT_H */
+
diff --git a/src/uqm/ships/zoqfot/Makeinfo b/src/uqm/ships/zoqfot/Makeinfo
new file mode 100644
index 0000000..e795e78
--- /dev/null
+++ b/src/uqm/ships/zoqfot/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="zoqfot.c"
+uqm_HFILES="icode.h resinst.h zoqfot.h"
diff --git a/src/uqm/ships/zoqfot/icode.h b/src/uqm/ships/zoqfot/icode.h
new file mode 100644
index 0000000..0fe635f
--- /dev/null
+++ b/src/uqm/ships/zoqfot/icode.h
@@ -0,0 +1,5 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define ZOQFOTPIK_CODE "ship.zoqfotpik.code"
diff --git a/src/uqm/ships/zoqfot/resinst.h b/src/uqm/ships/zoqfot/resinst.h
new file mode 100644
index 0000000..5939142
--- /dev/null
+++ b/src/uqm/ships/zoqfot/resinst.h
@@ -0,0 +1,19 @@
+/* This file was auto-generated by the gen_resfiles utility and
+ should not be edited directly. Modify the master resource list
+ instead and regenerate. */
+
+#define SPIT_BIG_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.spit.large"
+#define SPIT_MED_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.spit.medium"
+#define SPIT_SML_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.spit.small"
+#define STINGER_BIG_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.proboscis.large"
+#define STINGER_MED_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.proboscis.medium"
+#define STINGER_SML_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.proboscis.small"
+#define ZOQFOTPIK_BIG_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.stinger.large"
+#define ZOQFOTPIK_CAPTAIN_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.captain"
+#define ZOQFOTPIK_ICON_MASK_PMAP_ANIM "ship.zoqfotpik.icons"
+#define ZOQFOTPIK_MED_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.stinger.medium"
+#define ZOQFOTPIK_MICON_MASK_PMAP_ANIM "ship.zoqfotpik.meleeicons"
+#define ZOQFOTPIK_RACE_STRINGS "ship.zoqfotpik.text"
+#define ZOQFOTPIK_SHIP_SOUNDS "ship.zoqfotpik.sounds"
+#define ZOQFOTPIK_SML_MASK_PMAP_ANIM "ship.zoqfotpik.graphics.stinger.small"
+#define ZOQFOTPIK_VICTORY_SONG "ship.zoqfotpik.ditty"
diff --git a/src/uqm/ships/zoqfot/zoqfot.c b/src/uqm/ships/zoqfot/zoqfot.c
new file mode 100644
index 0000000..15a6024
--- /dev/null
+++ b/src/uqm/ships/zoqfot/zoqfot.c
@@ -0,0 +1,377 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "../ship.h"
+#include "zoqfot.h"
+#include "resinst.h"
+
+#include "libs/mathlib.h"
+
+// Core characteristics
+#define MAX_CREW 10
+#define MAX_ENERGY 10
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 4
+#define MAX_THRUST 40
+#define THRUST_INCREMENT 10
+#define THRUST_WAIT 0
+#define TURN_WAIT 1
+#define SHIP_MASS 5
+
+// Main weapon
+#define WEAPON_ENERGY_COST 1
+#define WEAPON_WAIT 0
+#define ZOQFOTPIK_OFFSET 13
+#define MISSILE_OFFSET 0
+#define MISSILE_SPEED DISPLAY_TO_WORLD (10)
+ /* Used by the cyborg only. */
+#define MISSILE_LIFE 10
+#define MISSILE_RANGE (MISSILE_SPEED * MISSILE_LIFE)
+#define MISSILE_DAMAGE 1
+#define MISSILE_HITS 1
+#define SPIT_WAIT 2
+ /* Controls the main weapon color change animation's speed.
+ * The animation advances one frame every SPIT_WAIT frames. */
+
+// Tongue
+#define SPECIAL_ENERGY_COST (MAX_ENERGY * 3 / 4)
+#define SPECIAL_WAIT 6
+#define TONGUE_SPEED 0
+#define TONGUE_HITS 1
+#define TONGUE_DAMAGE 12
+#define TONGUE_OFFSET 4
+
+static RACE_DESC zoqfotpik_desc =
+{
+ { /* SHIP_INFO */
+ FIRES_FORE,
+ 6, /* Super Melee cost */
+ MAX_CREW, MAX_CREW,
+ MAX_ENERGY, MAX_ENERGY,
+ ZOQFOTPIK_RACE_STRINGS,
+ ZOQFOTPIK_ICON_MASK_PMAP_ANIM,
+ ZOQFOTPIK_MICON_MASK_PMAP_ANIM,
+ NULL, NULL, NULL
+ },
+ { /* FLEET_STUFF */
+ 320 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
+ { /* Known location (center of SoI) */
+ 3761, 5333,
+ },
+ },
+ {
+ MAX_THRUST,
+ THRUST_INCREMENT,
+ ENERGY_REGENERATION,
+ WEAPON_ENERGY_COST,
+ SPECIAL_ENERGY_COST,
+ ENERGY_WAIT,
+ TURN_WAIT,
+ THRUST_WAIT,
+ WEAPON_WAIT,
+ SPECIAL_WAIT,
+ SHIP_MASS,
+ },
+ {
+ {
+ ZOQFOTPIK_BIG_MASK_PMAP_ANIM,
+ ZOQFOTPIK_MED_MASK_PMAP_ANIM,
+ ZOQFOTPIK_SML_MASK_PMAP_ANIM,
+ },
+ {
+ SPIT_BIG_MASK_PMAP_ANIM,
+ SPIT_MED_MASK_PMAP_ANIM,
+ SPIT_SML_MASK_PMAP_ANIM,
+ },
+ {
+ STINGER_BIG_MASK_PMAP_ANIM,
+ STINGER_MED_MASK_PMAP_ANIM,
+ STINGER_SML_MASK_PMAP_ANIM,
+ },
+ {
+ ZOQFOTPIK_CAPTAIN_MASK_PMAP_ANIM,
+ NULL, NULL, NULL, NULL, NULL
+ },
+ ZOQFOTPIK_VICTORY_SONG,
+ ZOQFOTPIK_SHIP_SOUNDS,
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ NULL, NULL
+ },
+ {
+ 0,
+ MISSILE_RANGE,
+ NULL,
+ },
+ (UNINIT_FUNC *) NULL,
+ (PREPROCESS_FUNC *) NULL,
+ (POSTPROCESS_FUNC *) NULL,
+ (INIT_WEAPON_FUNC *) NULL,
+ 0,
+ 0, /* CodeRef */
+};
+
+static void
+spit_preprocess (ELEMENT *ElementPtr)
+{
+ /* turn_wait is abused here to control the animation speed. */
+ if (ElementPtr->turn_wait > 0)
+ --ElementPtr->turn_wait;
+ else
+ {
+ COUNT index, angle, speed;
+
+ ElementPtr->next.image.frame =
+ IncFrameIndex (ElementPtr->next.image.frame);
+ angle = GetVelocityTravelAngle (&ElementPtr->velocity);
+ if ((index = GetFrameIndex (ElementPtr->next.image.frame)) == 1)
+ angle = angle + (((COUNT)TFB_Random () % 3) - 1);
+
+ speed = WORLD_TO_VELOCITY (DISPLAY_TO_WORLD (
+ GetFrameCount (ElementPtr->next.image.frame) - index) << 1);
+ SetVelocityComponents (&ElementPtr->velocity,
+ (SIZE)COSINE (angle, speed),
+ (SIZE)SINE (angle, speed));
+
+ /* turn_wait is abused here to control the animation speed. */
+ ElementPtr->turn_wait = SPIT_WAIT;
+ ElementPtr->state_flags |= CHANGING;
+ }
+}
+
+static COUNT
+initialize_spit (ELEMENT *ShipPtr, HELEMENT SpitArray[])
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK MissileBlock;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ MissileBlock.cx = ShipPtr->next.location.x;
+ MissileBlock.cy = ShipPtr->next.location.y;
+ MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
+ MissileBlock.face = StarShipPtr->ShipFacing;
+ MissileBlock.index = 0;
+ MissileBlock.sender = ShipPtr->playerNr;
+ MissileBlock.flags = IGNORE_SIMILAR;
+ MissileBlock.pixoffs = ZOQFOTPIK_OFFSET;
+ MissileBlock.speed = DISPLAY_TO_WORLD (
+ GetFrameCount (StarShipPtr->RaceDescPtr->ship_data.weapon[0])) << 1;
+ MissileBlock.hit_points = MISSILE_HITS;
+ MissileBlock.damage = MISSILE_DAMAGE;
+ MissileBlock.life = MISSILE_LIFE;
+ MissileBlock.preprocess_func = spit_preprocess;
+ MissileBlock.blast_offs = MISSILE_OFFSET;
+ SpitArray[0] = initialize_missile (&MissileBlock);
+
+ return (1);
+}
+
+static void spawn_tongue (ELEMENT *ElementPtr);
+
+static void
+tongue_postprocess (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->turn_wait)
+ spawn_tongue (ElementPtr);
+}
+
+static void
+tongue_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr0, &StarShipPtr);
+ if (StarShipPtr->special_counter ==
+ StarShipPtr->RaceDescPtr->characteristics.special_wait)
+ weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
+
+ StarShipPtr->special_counter -= ElementPtr0->turn_wait;
+ ElementPtr0->turn_wait = 0;
+ ElementPtr0->state_flags |= NONSOLID;
+}
+
+static void
+spawn_tongue (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+ MISSILE_BLOCK TongueBlock;
+ HELEMENT Tongue;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ TongueBlock.cx = ElementPtr->next.location.x;
+ TongueBlock.cy = ElementPtr->next.location.y;
+ TongueBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special;
+ TongueBlock.face = TongueBlock.index = StarShipPtr->ShipFacing;
+ TongueBlock.sender = ElementPtr->playerNr;
+ TongueBlock.flags = IGNORE_SIMILAR;
+ TongueBlock.pixoffs = 0;
+ TongueBlock.speed = TONGUE_SPEED;
+ TongueBlock.hit_points = TONGUE_HITS;
+ TongueBlock.damage = TONGUE_DAMAGE;
+ TongueBlock.life = 1;
+ TongueBlock.preprocess_func = 0;
+ TongueBlock.blast_offs = TONGUE_OFFSET;
+ Tongue = initialize_missile (&TongueBlock);
+ if (Tongue)
+ {
+ ELEMENT *TonguePtr;
+
+ LockElement (Tongue, &TonguePtr);
+ TonguePtr->postprocess_func = tongue_postprocess;
+ TonguePtr->collision_func = tongue_collision;
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ TonguePtr->turn_wait = StarShipPtr->special_counter;
+ else
+ {
+ COUNT angle;
+ RECT r;
+ SIZE x_offs, y_offs;
+
+ TonguePtr->turn_wait = ElementPtr->turn_wait - 1;
+
+ GetFrameRect (TonguePtr->current.image.frame, &r);
+ x_offs = DISPLAY_TO_WORLD (r.extent.width >> 1);
+ y_offs = DISPLAY_TO_WORLD (r.extent.height >> 1);
+
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+ if (angle > HALF_CIRCLE && angle < FULL_CIRCLE)
+ TonguePtr->current.location.x -= x_offs;
+ else if (angle > 0 && angle < HALF_CIRCLE)
+ TonguePtr->current.location.x += x_offs;
+ if (angle < QUADRANT || angle > FULL_CIRCLE - QUADRANT)
+ TonguePtr->current.location.y -= y_offs;
+ else if (angle > QUADRANT && angle < FULL_CIRCLE - QUADRANT)
+ TonguePtr->current.location.y += y_offs;
+ }
+
+ SetElementStarShip (TonguePtr, StarShipPtr);
+ UnlockElement (Tongue);
+ PutElement (Tongue);
+ }
+}
+
+static void
+zoqfotpik_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
+ COUNT ConcernCounter)
+{
+ BOOLEAN GiveTongueJob;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ GiveTongueJob = FALSE;
+ if (StarShipPtr->special_counter == 0)
+ {
+ EVALUATE_DESC *lpEnemyEvalDesc;
+
+ StarShipPtr->ship_input_state &= ~SPECIAL;
+
+ lpEnemyEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
+ if (lpEnemyEvalDesc->ObjectPtr
+ && lpEnemyEvalDesc->which_turn <= 4
+#ifdef NEVER
+ && StarShipPtr->RaceDescPtr->ship_info.energy_level >= SPECIAL_ENERGY_COST
+#endif /* NEVER */
+ )
+ {
+ SIZE delta_x, delta_y;
+
+ GiveTongueJob = TRUE;
+
+ lpEnemyEvalDesc->MoveState = PURSUE;
+ delta_x = lpEnemyEvalDesc->ObjectPtr->next.location.x
+ - ShipPtr->next.location.x;
+ delta_y = lpEnemyEvalDesc->ObjectPtr->next.location.y
+ - ShipPtr->next.location.y;
+ if (StarShipPtr->ShipFacing == NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
+ ))
+ StarShipPtr->ship_input_state |= SPECIAL;
+ }
+ }
+
+ ++StarShipPtr->weapon_counter;
+ ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter);
+ --StarShipPtr->weapon_counter;
+
+ if (StarShipPtr->weapon_counter == 0)
+ {
+ StarShipPtr->ship_input_state &= ~WEAPON;
+ if (!GiveTongueJob)
+ {
+ ObjectsOfConcern += ConcernCounter;
+ while (ConcernCounter--)
+ {
+ --ObjectsOfConcern;
+ if (ObjectsOfConcern->ObjectPtr
+ && (ConcernCounter == ENEMY_SHIP_INDEX
+ || (ConcernCounter == ENEMY_WEAPON_INDEX
+ && ObjectsOfConcern->MoveState != AVOID
+#ifdef NEVER
+ && !(StarShipPtr->control & STANDARD_RATING)
+#endif /* NEVER */
+ ))
+ && ship_weapons (ShipPtr,
+ ObjectsOfConcern->ObjectPtr, DISPLAY_TO_WORLD (20)))
+ {
+ StarShipPtr->ship_input_state |= WEAPON;
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void
+zoqfotpik_postprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if ((StarShipPtr->cur_status_flags & SPECIAL)
+ && StarShipPtr->special_counter == 0
+ && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
+ {
+ ProcessSound (SetAbsSoundIndex (
+ /* STICK_OUT_TONGUE */
+ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
+
+ StarShipPtr->special_counter =
+ StarShipPtr->RaceDescPtr->characteristics.special_wait;
+ }
+
+ if (StarShipPtr->special_counter)
+ spawn_tongue (ElementPtr);
+}
+
+RACE_DESC*
+init_zoqfotpik (void)
+{
+ RACE_DESC *RaceDescPtr;
+
+ zoqfotpik_desc.postprocess_func = zoqfotpik_postprocess;
+ zoqfotpik_desc.init_weapon_func = initialize_spit;
+ zoqfotpik_desc.cyborg_control.intelligence_func = zoqfotpik_intelligence;
+
+ RaceDescPtr = &zoqfotpik_desc;
+
+ return (RaceDescPtr);
+}
+
diff --git a/src/uqm/ships/zoqfot/zoqfot.h b/src/uqm/ships/zoqfot/zoqfot.h
new file mode 100644
index 0000000..1bdcc85
--- /dev/null
+++ b/src/uqm/ships/zoqfot/zoqfot.h
@@ -0,0 +1,31 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ZOQFOT_H
+#define ZOQFOT_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+RACE_DESC *init_zoqfotpik (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* ZOQFOT_H */
+
diff --git a/src/uqm/shipstat.c b/src/uqm/shipstat.c
new file mode 100644
index 0000000..0405426
--- /dev/null
+++ b/src/uqm/shipstat.c
@@ -0,0 +1,437 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "colors.h"
+#include "globdata.h"
+#include "options.h"
+#include "status.h"
+#include "setup.h"
+#include "libs/gfxlib.h"
+
+
+void
+DrawCrewFuelString (COORD y, SIZE state)
+{
+ STAMP Stamp;
+
+ Stamp.origin.y = y + GAUGE_YOFFS + STARCON_TEXT_HEIGHT;
+ if (state == 0)
+ {
+ Stamp.origin.x = CREW_XOFFS + (STAT_WIDTH >> 1) + 6;
+ if (optWhichMenu == OPT_PC)
+ Stamp.frame = SetAbsFrameIndex (StatusFrame, 4);
+ else
+ Stamp.frame = SetAbsFrameIndex (StatusFrame, 0);
+ DrawStamp (&Stamp);
+ }
+
+ Stamp.origin.x = ENERGY_XOFFS + (STAT_WIDTH >> 1) - 5;
+ if (optWhichMenu == OPT_PC)
+ Stamp.frame = SetAbsFrameIndex (StatusFrame, 5);
+ else
+ Stamp.frame = SetAbsFrameIndex (StatusFrame, 1);
+ if (state >= 0)
+ DrawStamp (&Stamp);
+ else
+ {
+#define LOW_FUEL_COLOR BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)
+ SetContextForeGroundColor (LOW_FUEL_COLOR);
+ DrawFilledStamp (&Stamp);
+ }
+}
+
+static void
+DrawShipNameString (UNICODE *pStr, COUNT CharCount, COORD y)
+{
+ TEXT Text;
+ FONT OldFont;
+
+ OldFont = SetContextFont (StarConFont);
+
+ Text.pStr = pStr;
+ Text.CharCount = CharCount;
+ Text.align = ALIGN_CENTER;
+
+ Text.baseline.y = STARCON_TEXT_HEIGHT + 3 + y;
+ Text.baseline.x = STATUS_WIDTH >> 1;
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19));
+ font_DrawText (&Text);
+ --Text.baseline.y;
+ SetContextForeGroundColor (BLACK_COLOR);
+ font_DrawText (&Text);
+
+ SetContextFont (OldFont);
+}
+
+void
+ClearShipStatus (COORD y)
+{
+ RECT r;
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ r.corner.x = 2;
+ r.corner.y = 3 + y;
+ r.extent.width = STATUS_WIDTH - 4;
+ r.extent.height = SHIP_INFO_HEIGHT - 3;
+ DrawFilledRectangle (&r);
+}
+
+void
+OutlineShipStatus (COORD y)
+{
+ RECT r;
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F));
+ r.corner.x = 0;
+ r.corner.y = 1 + y;
+ r.extent.width = STATUS_WIDTH;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ ++r.corner.y;
+ --r.extent.width;
+ DrawFilledRectangle (&r);
+ r.extent.width = 1;
+ r.extent.height = SHIP_INFO_HEIGHT - 2;
+ DrawFilledRectangle (&r);
+ ++r.corner.x;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19));
+ r.corner.x = STATUS_WIDTH - 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = STATUS_WIDTH - 2;
+ ++r.corner.y;
+ --r.extent.height;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ r.corner.x = 0;
+ r.corner.y = y;
+ r.extent.width = STATUS_WIDTH;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+}
+
+void
+InitShipStatus (SHIP_INFO *SIPtr, STARSHIP *StarShipPtr, RECT *pClipRect)
+{
+ RECT r;
+ COORD y = 0; // default, for Melee menu
+ STAMP Stamp;
+ CONTEXT OldContext;
+ RECT oldClipRect;
+ POINT oldOrigin = {0, 0};
+
+ if (StarShipPtr) // set during battle
+ {
+ assert (StarShipPtr->playerNr >= 0);
+ y = status_y_offsets[StarShipPtr->playerNr];
+ }
+
+ OldContext = SetContext (StatusContext);
+ if (pClipRect)
+ {
+ GetContextClipRect (&oldClipRect);
+ r = oldClipRect;
+ r.corner.x += pClipRect->corner.x;
+ r.corner.y += (pClipRect->corner.y & ~1);
+ r.extent = pClipRect->extent;
+ r.extent.height += pClipRect->corner.y & 1;
+ SetContextClipRect (&r);
+ // Offset the origin so that we draw into the cliprect
+ oldOrigin = SetContextOrigin (MAKE_POINT (-pClipRect->corner.x,
+ -(pClipRect->corner.y & ~1)));
+ }
+
+ BatchGraphics ();
+
+ OutlineShipStatus (y);
+ ClearShipStatus (y);
+
+ Stamp.origin.x = (STATUS_WIDTH >> 1);
+ Stamp.origin.y = 31 + y;
+ Stamp.frame = IncFrameIndex (SIPtr->icons);
+ DrawStamp (&Stamp);
+
+ {
+ SIZE crew_height, energy_height;
+
+#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
+ crew_height = ((MIN(SIPtr->max_crew, MAX_CREW_SIZE) + 1) & ~1) + 1;
+#undef MIN
+ energy_height = (((SIPtr->max_energy + 1) >> 1) << 1) + 1;
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F));
+ r.corner.x = CREW_XOFFS - 1;
+ r.corner.y = GAUGE_YOFFS + 1 + y;
+ r.extent.width = STAT_WIDTH + 2;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = ENERGY_XOFFS - 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = ENERGY_XOFFS + STAT_WIDTH;
+ r.corner.y -= energy_height;
+ r.extent.width = 1;
+ r.extent.height = energy_height;
+ DrawFilledRectangle (&r);
+ r.corner.x = CREW_XOFFS + STAT_WIDTH;
+ r.corner.y = (GAUGE_YOFFS + 1 + y) - crew_height;
+ r.extent.width = 1;
+ r.extent.height = crew_height;
+ DrawFilledRectangle (&r);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19));
+ r.corner.x = CREW_XOFFS - 1;
+ r.corner.y = GAUGE_YOFFS - crew_height + y;
+ r.extent.width = STAT_WIDTH + 2;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = ENERGY_XOFFS - 1;
+ r.corner.y = GAUGE_YOFFS - energy_height + y;
+ DrawFilledRectangle (&r);
+ r.extent.width = 1;
+ r.extent.height = energy_height + 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = CREW_XOFFS - 1;
+ r.corner.y = GAUGE_YOFFS - crew_height + y;
+ r.extent.height = crew_height + 1;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (BLACK_COLOR);
+
+ r.extent.width = STAT_WIDTH;
+ r.corner.x = CREW_XOFFS;
+ r.extent.height = crew_height;
+ r.corner.y = y - r.extent.height + GAUGE_YOFFS + 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = ENERGY_XOFFS;
+ r.extent.height = energy_height;
+ r.corner.y = y - r.extent.height + GAUGE_YOFFS + 1;
+ DrawFilledRectangle (&r);
+ }
+
+ if (!StarShipPtr || StarShipPtr->captains_name_index)
+ { // Any regular ship. SIS and Sa-Matra are separate.
+ // This includes Melee menu.
+ STRING locString;
+
+ DrawCrewFuelString (y, 0);
+
+ locString = SetAbsStringTableIndex (SIPtr->race_strings, 1);
+ DrawShipNameString (
+ (UNICODE *)GetStringAddress (locString),
+ GetStringLength (locString), y);
+
+ {
+ UNICODE buf[30];
+ TEXT Text;
+ FONT OldFont;
+
+ OldFont = SetContextFont (TinyFont);
+
+ if (!StarShipPtr)
+ { // In Melee menu
+ sprintf (buf, "%d", SIPtr->ship_cost);
+ Text.pStr = buf;
+ Text.CharCount = (COUNT)~0;
+ }
+ else
+ {
+ locString = SetAbsStringTableIndex (SIPtr->race_strings,
+ StarShipPtr->captains_name_index);
+ Text.pStr = (UNICODE *)GetStringAddress (locString);
+ Text.CharCount = GetStringLength (locString);
+ }
+ Text.align = ALIGN_CENTER;
+
+ Text.baseline.x = STATUS_WIDTH >> 1;
+ Text.baseline.y = y + GAUGE_YOFFS + 3;
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ font_DrawText (&Text);
+
+ SetContextFont (OldFont);
+ }
+ }
+ else if (StarShipPtr->playerNr == RPG_PLAYER_NUM)
+ { // This is SIS
+ DrawCrewFuelString (y, 0);
+ DrawShipNameString (GLOBAL_SIS (ShipName), (COUNT)~0, y);
+ }
+
+ {
+ SIZE crew_delta, energy_delta;
+
+ crew_delta = SIPtr->crew_level;
+ energy_delta = SIPtr->energy_level;
+ // DeltaStatistics() below will add specified values to these
+ SIPtr->crew_level = 0;
+ SIPtr->energy_level = 0;
+ DeltaStatistics (SIPtr, y, crew_delta, energy_delta);
+ }
+
+ UnbatchGraphics ();
+
+ if (pClipRect)
+ {
+ SetContextOrigin (oldOrigin);
+ SetContextClipRect (&oldClipRect);
+ }
+
+ SetContext (OldContext);
+}
+
+// Pre: -crew_delta <= ShipInfoPtr->crew_level
+// crew_delta <= ShipInfoPtr->max_crew - ShipInfoPtr->crew_level
+void
+DeltaStatistics (SHIP_INFO *ShipInfoPtr, COORD y_offs,
+ SIZE crew_delta, SIZE energy_delta)
+{
+ COORD x, y;
+ RECT r;
+
+ if (crew_delta == 0 && energy_delta == 0)
+ return;
+
+ x = 0;
+ y = GAUGE_YOFFS + y_offs;
+
+ r.extent.width = UNIT_WIDTH;
+ r.extent.height = UNIT_HEIGHT;
+
+ if (crew_delta != 0)
+ {
+ COUNT oldNumBlocks, newNumBlocks, blockI;
+ COUNT newCrewLevel;
+
+#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
+ oldNumBlocks = MIN(ShipInfoPtr->crew_level, MAX_CREW_SIZE);
+ newCrewLevel = ShipInfoPtr->crew_level + crew_delta;
+ newNumBlocks = MIN(newCrewLevel, MAX_CREW_SIZE);
+#undef MIN
+
+ if (crew_delta > 0)
+ {
+ r.corner.y = (y + 1) -
+ (((oldNumBlocks + 1) >> 1) * (UNIT_HEIGHT + 1));
+#define CREW_UNIT_COLOR BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02)
+#define ROBOT_UNIT_COLOR BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08)
+ SetContextForeGroundColor (
+ (ShipInfoPtr->ship_flags & CREW_IMMUNE) ?
+ ROBOT_UNIT_COLOR : CREW_UNIT_COLOR);
+ for (blockI = oldNumBlocks; blockI < newNumBlocks; blockI++)
+ {
+ r.corner.x = x + (CREW_XOFFS + 1);
+ if (!(blockI & 1))
+ {
+ r.corner.x += UNIT_WIDTH + 1;
+ r.corner.y -= UNIT_HEIGHT + 1;
+ }
+ DrawFilledRectangle (&r);
+ }
+ }
+ else /* crew_delta < 0 */
+ {
+ SetContextForeGroundColor (BLACK_COLOR);
+ r.corner.y = (y + 1) -
+ (((oldNumBlocks + 2) >> 1) * (UNIT_HEIGHT + 1));
+ for (blockI = oldNumBlocks; blockI > newNumBlocks; blockI--)
+ {
+ r.corner.x = x + (CREW_XOFFS + 1 + UNIT_WIDTH + 1);
+ if (!(blockI & 1))
+ {
+ r.corner.x -= UNIT_WIDTH + 1;
+ r.corner.y += UNIT_HEIGHT + 1;
+ }
+ DrawFilledRectangle (&r);
+ }
+ }
+
+ if (ShipInfoPtr->ship_flags & PLAYER_CAPTAIN) {
+ if (((ShipInfoPtr->crew_level > MAX_CREW_SIZE) !=
+ (newCrewLevel > MAX_CREW_SIZE) ||
+ ShipInfoPtr->crew_level == 0) && newCrewLevel != 0)
+ {
+ // The block indicating the captain needs to change color.
+#define PLAYER_UNIT_COLOR BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+ SetContextForeGroundColor (
+ (newCrewLevel > MAX_CREW_SIZE) ?
+ CREW_UNIT_COLOR : PLAYER_UNIT_COLOR);
+ r.corner.x = x + (CREW_XOFFS + 1) + (UNIT_WIDTH + 1);
+ r.corner.y = y - UNIT_HEIGHT;
+ DrawFilledRectangle (&r);
+ }
+ }
+
+ ShipInfoPtr->crew_level = newCrewLevel;
+ if (ShipInfoPtr->max_crew > MAX_CREW_SIZE ||
+ ShipInfoPtr->ship_flags & PLAYER_CAPTAIN)
+ {
+ // All crew doesn't fit in the graphics; print a number.
+ // Always print a number for the SIS in the full game.
+ DrawBattleCrewAmount (ShipInfoPtr, y_offs);
+ }
+ }
+
+ if (energy_delta != 0)
+ {
+ if (energy_delta > 0)
+ {
+#define FUEL_UNIT_COLOR BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
+ SetContextForeGroundColor (FUEL_UNIT_COLOR);
+ r.corner.y = (y + 1) -
+ (((ShipInfoPtr->energy_level + 1) >> 1) * (UNIT_HEIGHT + 1));
+ do
+ {
+ r.corner.x = x + (ENERGY_XOFFS + 1);
+ if (!(ShipInfoPtr->energy_level & 1))
+ {
+ r.corner.x += UNIT_WIDTH + 1;
+ r.corner.y -= UNIT_HEIGHT + 1;
+ }
+ DrawFilledRectangle (&r);
+ ++ShipInfoPtr->energy_level;
+ } while (--energy_delta);
+ }
+ else
+ {
+ SetContextForeGroundColor (BLACK_COLOR);
+ r.corner.y = (y + 1) -
+ (((ShipInfoPtr->energy_level + 2) >> 1) * (UNIT_HEIGHT + 1));
+ do
+ {
+ r.corner.x = x + (ENERGY_XOFFS + 1 + UNIT_WIDTH + 1);
+ if (!(ShipInfoPtr->energy_level & 1))
+ {
+ r.corner.x -= UNIT_WIDTH + 1;
+ r.corner.y += UNIT_HEIGHT + 1;
+ }
+ DrawFilledRectangle (&r);
+ --ShipInfoPtr->energy_level;
+ } while (++energy_delta);
+ }
+ }
+}
+
+
diff --git a/src/uqm/shipyard.c b/src/uqm/shipyard.c
new file mode 100644
index 0000000..f317ba3
--- /dev/null
+++ b/src/uqm/shipyard.c
@@ -0,0 +1,1495 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "build.h"
+#include "colors.h"
+#include "controls.h"
+#include "menustat.h"
+#include "fmv.h"
+#include "gameopt.h"
+#include "gamestr.h"
+#include "supermelee/melee.h"
+#include "master.h"
+#include "options.h"
+#include "races.h"
+#include "nameref.h"
+#include "resinst.h"
+#include "settings.h"
+#include "starbase.h"
+#include "setup.h"
+#include "sis.h"
+#include "units.h"
+#include "sounds.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/inplib.h"
+
+
+#ifdef USE_3DO_HANGAR
+// 3DO 4x3 hangar layout
+# define HANGAR_SHIPS_ROW 4
+# define HANGAR_Y 64
+# define HANGAR_DY 44
+
+static const COORD hangar_x_coords[HANGAR_SHIPS_ROW] =
+{
+ 19, 60, 116, 157
+};
+
+#else // use PC hangar
+// modified PC 6x2 hangar layout
+# define HANGAR_SHIPS_ROW 6
+# define HANGAR_Y 88
+# define HANGAR_DY 84
+
+static const COORD hangar_x_coords[HANGAR_SHIPS_ROW] =
+{
+ 0, 38, 76, 131, 169, 207
+};
+#endif // USE_3DO_HANGAR
+
+#define HANGAR_SHIPS 12
+#define HANGAR_ROWS (HANGAR_SHIPS / HANGAR_SHIPS_ROW)
+#define HANGAR_ANIM_RATE 15 // fps
+
+enum
+{
+ SHIPYARD_CREW,
+ SHIPYARD_SAVELOAD,
+ SHIPYARD_EXIT
+};
+
+// Editing mode for DoModifyShips()
+typedef enum {
+ DMS_Mode_navigate, // Navigating the ship slots.
+ DMS_Mode_addEscort, // Selecting a ship to add to an empty slot.
+ DMS_Mode_editCrew, // Hiring or dismissing crew.
+ DMS_Mode_exit, // Leaving DoModifyShips() mode.
+} DMS_Mode;
+
+static COUNT ShipCost[] =
+{
+ RACE_SHIP_COST
+};
+
+
+static void
+animatePowerLines (MENU_STATE *pMS)
+{
+ static STAMP s;
+ static COLORMAP ColorMap;
+ static TimeCount NextTime = 0;
+ TimeCount Now = GetTimeCounter ();
+
+ if (pMS)
+ { // Init animation
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pMS->ModuleFrame, 24);
+ ColorMap = SetAbsColorMapIndex (pMS->CurString, 0);
+ }
+
+ if (Now >= NextTime || pMS)
+ {
+ NextTime = Now + (ONE_SECOND / HANGAR_ANIM_RATE);
+
+ SetColorMap (GetColorMapAddress (ColorMap));
+ DrawStamp (&s);
+ // Advance colomap cycle
+ ColorMap = SetRelColorMapIndex (ColorMap, 1);
+ }
+}
+
+static void
+on_input_frame (void)
+{
+ CONTEXT oldContext;
+
+ oldContext = SetContext (SpaceContext);
+ animatePowerLines (NULL);
+ SetContext (oldContext);
+}
+
+#ifdef WANT_SHIP_SPINS
+static void
+SpinStarShip (MENU_STATE *pMS, HFLEETINFO hStarShip)
+{
+ int Index;
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ Index = FindMasterShipIndex (FleetPtr->SpeciesID);
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+
+ if (Index >= 0 && Index < NUM_MELEE_SHIPS)
+ {
+ DoShipSpin (Index, pMS->hMusic);
+ }
+}
+#endif
+
+// Count the ships which can be built by the player.
+static COUNT
+GetAvailableRaceCount (void)
+{
+ COUNT Index;
+ HFLEETINFO hStarShip, hNextShip;
+
+ Index = 0;
+ for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip; hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ if (FleetPtr->allied_state == GOOD_GUY)
+ ++Index;
+
+ hNextShip = _GetSuccLink (FleetPtr);
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+
+ return Index;
+}
+
+static HFLEETINFO
+GetAvailableRaceFromIndex (BYTE Index)
+{
+ HFLEETINFO hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip; hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ if (FleetPtr->allied_state == GOOD_GUY && Index-- == 0)
+ {
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ return hStarShip;
+ }
+
+ hNextShip = _GetSuccLink (FleetPtr);
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+
+ return 0;
+}
+
+static void
+DrawRaceStrings (MENU_STATE *pMS, BYTE NewRaceItem)
+{
+ RECT r;
+ STAMP s;
+ CONTEXT OldContext;
+
+
+ OldContext = SetContext (StatusContext);
+ GetContextClipRect (&r);
+ s.origin.x = RADAR_X - r.corner.x;
+ s.origin.y = RADAR_Y - r.corner.y;
+ r.corner.x = s.origin.x - 1;
+ r.corner.y = s.origin.y - 11;
+ r.extent.width = RADAR_WIDTH + 2;
+ r.extent.height = 11;
+ BatchGraphics ();
+ ClearSISRect (CLEAR_SIS_RADAR);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ DrawFilledRectangle (&r);
+ r.corner = s.origin;
+ r.extent.width = RADAR_WIDTH;
+ r.extent.height = RADAR_HEIGHT;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ if (NewRaceItem != (BYTE)~0)
+ {
+ TEXT t;
+ HFLEETINFO hStarShip;
+ FLEET_INFO *FleetPtr;
+ UNICODE buf[30];
+
+ hStarShip = GetAvailableRaceFromIndex (NewRaceItem);
+ NewRaceItem = GetIndexFromStarShip (&GLOBAL (avail_race_q),
+ hStarShip);
+
+ // Draw the ship name, above the ship image.
+ s.frame = SetAbsFrameIndex (pMS->ModuleFrame, 3 + NewRaceItem);
+ DrawStamp (&s);
+
+ // Draw the ship image.
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ s.frame = FleetPtr->melee_icon;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ t.baseline.x = s.origin.x + RADAR_WIDTH - 2;
+ t.baseline.y = s.origin.y + RADAR_HEIGHT - 2;
+ s.origin.x += (RADAR_WIDTH >> 1);
+ s.origin.y += (RADAR_HEIGHT >> 1);
+ DrawStamp (&s);
+
+ // Print the ship cost.
+ t.align = ALIGN_RIGHT;
+ t.CharCount = (COUNT)~0;
+ t.pStr = buf;
+ sprintf (buf, "%u", ShipCost[NewRaceItem]);
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x1F, 0x00), 0x02));
+ font_DrawText (&t);
+ }
+ UnbatchGraphics ();
+ SetContext (OldContext);
+
+}
+
+#define SHIP_WIN_WIDTH 34
+#define SHIP_WIN_HEIGHT (SHIP_WIN_WIDTH + 6)
+#define SHIP_WIN_FRAMES ((SHIP_WIN_WIDTH >> 1) + 1)
+
+// Print the crew count of an escort ship on top of its (already drawn)
+// image, either as '30' (full), '28/30' (partially full), or 'SCRAP'
+// (empty).
+// pRect is the rectangle of the ship image.
+static void
+ShowShipCrew (SHIP_FRAGMENT *StarShipPtr, const RECT *pRect)
+{
+ RECT r;
+ TEXT t;
+ UNICODE buf[80];
+ HFLEETINFO hTemplate;
+ FLEET_INFO *TemplatePtr;
+ COUNT maxCrewLevel;
+
+ hTemplate = GetStarShipFromIndex (&GLOBAL (avail_race_q),
+ StarShipPtr->race_id);
+ TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hTemplate);
+ maxCrewLevel = TemplatePtr->crew_level;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hTemplate);
+
+ if (StarShipPtr->crew_level >= maxCrewLevel)
+ sprintf (buf, "%u", StarShipPtr->crew_level);
+ else if (StarShipPtr->crew_level == 0)
+ // XXX: "SCRAP" needs to be moved to starcon.txt
+ utf8StringCopy (buf, sizeof (buf), "SCRAP");
+ else
+ sprintf (buf, "%u/%u", StarShipPtr->crew_level, maxCrewLevel);
+
+ r = *pRect;
+ t.baseline.x = r.corner.x + (r.extent.width >> 1);
+ t.baseline.y = r.corner.y + r.extent.height - 1;
+ t.align = ALIGN_CENTER;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ if (r.corner.y)
+ {
+ r.corner.y = t.baseline.y - 6;
+ r.extent.width = SHIP_WIN_WIDTH;
+ r.extent.height = 6;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ }
+ SetContextForeGroundColor ((StarShipPtr->crew_level != 0) ?
+ (BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02)):
+ (BUILD_COLOR (MAKE_RGB15 (0x12, 0x00, 0x00), 0x2B)));
+ font_DrawText (&t);
+}
+
+static void
+ShowCombatShip (MENU_STATE *pMS, COUNT which_window,
+ SHIP_FRAGMENT *YankedStarShipPtr)
+{
+ COUNT i;
+ COUNT num_ships;
+ HSHIPFRAG hStarShip, hNextShip;
+ SHIP_FRAGMENT *StarShipPtr;
+ struct
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+ POINT finished_s;
+ STAMP ship_s;
+ STAMP lfdoor_s;
+ STAMP rtdoor_s;
+ } ship_win_info[MAX_BUILT_SHIPS], *pship_win_info;
+
+ num_ships = 1;
+ pship_win_info = &ship_win_info[0];
+ if (YankedStarShipPtr)
+ {
+ pship_win_info->StarShipPtr = YankedStarShipPtr;
+
+ pship_win_info->lfdoor_s.origin.x = -(SHIP_WIN_WIDTH >> 1);
+ pship_win_info->rtdoor_s.origin.x = (SHIP_WIN_WIDTH >> 1);
+ pship_win_info->lfdoor_s.origin.y = 0;
+ pship_win_info->rtdoor_s.origin.y = 0;
+ pship_win_info->lfdoor_s.frame = IncFrameIndex (pMS->ModuleFrame);
+ pship_win_info->rtdoor_s.frame =
+ IncFrameIndex (pship_win_info->lfdoor_s.frame);
+
+ pship_win_info->ship_s.origin.x = (SHIP_WIN_WIDTH >> 1) + 1;
+ pship_win_info->ship_s.origin.y = (SHIP_WIN_WIDTH >> 1);
+ pship_win_info->ship_s.frame = YankedStarShipPtr->melee_icon;
+
+ pship_win_info->finished_s.x = hangar_x_coords[
+ which_window % HANGAR_SHIPS_ROW];
+ pship_win_info->finished_s.y = HANGAR_Y + (HANGAR_DY *
+ (which_window / HANGAR_SHIPS_ROW));
+ }
+ else
+ {
+ if (which_window == (COUNT)~0)
+ {
+ hStarShip = GetHeadLink (&GLOBAL (built_ship_q));
+ num_ships = CountLinks (&GLOBAL (built_ship_q));
+ }
+ else
+ {
+ HSHIPFRAG hTailShip;
+
+ hTailShip = GetTailLink (&GLOBAL (built_ship_q));
+ RemoveQueue (&GLOBAL (built_ship_q), hTailShip);
+
+ hStarShip = GetHeadLink (&GLOBAL (built_ship_q));
+ while (hStarShip)
+ {
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ if (StarShipPtr->index > which_window)
+ {
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ break;
+ }
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ hStarShip = hNextShip;
+ }
+ InsertQueue (&GLOBAL (built_ship_q), hTailShip, hStarShip);
+
+ hStarShip = hTailShip;
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ StarShipPtr->index = which_window;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+
+ for (i = 0; i < num_ships; ++i)
+ {
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+
+ pship_win_info->StarShipPtr = StarShipPtr;
+ // XXX BUG: this looks wrong according to the original
+ // semantics of LockShipFrag(): StarShipPtr is not valid
+ // anymore after UnlockShipFrag() is called, but it is
+ // used thereafter.
+
+ pship_win_info->lfdoor_s.origin.x = -1;
+ pship_win_info->rtdoor_s.origin.x = 1;
+ pship_win_info->lfdoor_s.origin.y = 0;
+ pship_win_info->rtdoor_s.origin.y = 0;
+ pship_win_info->lfdoor_s.frame = IncFrameIndex (pMS->ModuleFrame);
+ pship_win_info->rtdoor_s.frame =
+ IncFrameIndex (pship_win_info->lfdoor_s.frame);
+
+ pship_win_info->ship_s.origin.x = (SHIP_WIN_WIDTH >> 1) + 1;
+ pship_win_info->ship_s.origin.y = (SHIP_WIN_WIDTH >> 1);
+ pship_win_info->ship_s.frame = StarShipPtr->melee_icon;
+
+ which_window = StarShipPtr->index;
+ pship_win_info->finished_s.x = hangar_x_coords[
+ which_window % HANGAR_SHIPS_ROW];
+ pship_win_info->finished_s.y = HANGAR_Y + (HANGAR_DY *
+ (which_window / HANGAR_SHIPS_ROW));
+ ++pship_win_info;
+
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hStarShip = hNextShip;
+ }
+ }
+
+ if (num_ships)
+ {
+ BOOLEAN AllDoorsFinished;
+ DWORD TimeIn;
+ RECT r;
+ CONTEXT OldContext;
+ RECT OldClipRect;
+ int j;
+
+ AllDoorsFinished = FALSE;
+ r.corner.x = r.corner.y = 0;
+ r.extent.width = SHIP_WIN_WIDTH;
+ r.extent.height = SHIP_WIN_HEIGHT;
+ FlushInput ();
+ TimeIn = GetTimeCounter ();
+
+ for (j = 0; (j < SHIP_WIN_FRAMES) && !AllDoorsFinished; j++)
+ {
+ SleepThreadUntil (TimeIn + ONE_SECOND / 24);
+ TimeIn = GetTimeCounter ();
+ if (AnyButtonPress (FALSE))
+ {
+ if (YankedStarShipPtr != 0)
+ { // Fully close the doors
+ ship_win_info[0].lfdoor_s.origin.x = 0;
+ ship_win_info[0].rtdoor_s.origin.x = 0;
+ }
+ AllDoorsFinished = TRUE;
+ }
+
+ OldContext = SetContext (SpaceContext);
+ GetContextClipRect (&OldClipRect);
+ SetContextBackGroundColor (BLACK_COLOR);
+
+ BatchGraphics ();
+ pship_win_info = &ship_win_info[0];
+ for (i = 0; i < num_ships; ++i)
+ {
+ {
+ RECT ClipRect;
+
+ ClipRect.corner.x = SIS_ORG_X +
+ pship_win_info->finished_s.x;
+ ClipRect.corner.y = SIS_ORG_Y +
+ pship_win_info->finished_s.y;
+ ClipRect.extent.width = SHIP_WIN_WIDTH;
+ ClipRect.extent.height = SHIP_WIN_HEIGHT;
+ SetContextClipRect (&ClipRect);
+
+ ClearDrawable ();
+ DrawStamp (&pship_win_info->ship_s);
+ ShowShipCrew (pship_win_info->StarShipPtr, &r);
+ if (!AllDoorsFinished || YankedStarShipPtr)
+ {
+ DrawStamp (&pship_win_info->lfdoor_s);
+ DrawStamp (&pship_win_info->rtdoor_s);
+ if (YankedStarShipPtr)
+ { // Close the doors
+ ++pship_win_info->lfdoor_s.origin.x;
+ --pship_win_info->rtdoor_s.origin.x;
+ }
+ else
+ { // Open the doors
+ --pship_win_info->lfdoor_s.origin.x;
+ ++pship_win_info->rtdoor_s.origin.x;
+ }
+ }
+ }
+ ++pship_win_info;
+ }
+
+ SetContextClipRect (&OldClipRect);
+#ifndef USE_3DO_HANGAR
+ animatePowerLines (NULL);
+#endif
+ UnbatchGraphics ();
+ SetContext (OldContext);
+ }
+ }
+}
+
+static void
+CrewTransaction (SIZE crew_delta)
+{
+ if (crew_delta)
+ {
+ SIZE crew_bought;
+
+ crew_bought = (SIZE)MAKE_WORD (
+ GET_GAME_STATE (CREW_PURCHASED0),
+ GET_GAME_STATE (CREW_PURCHASED1)) + crew_delta;
+ if (crew_bought < 0)
+ {
+ if (crew_delta < 0)
+ crew_bought = 0;
+ else
+ crew_bought = 0x7FFF;
+ }
+ else if (crew_delta > 0)
+ {
+ if (crew_bought >= CREW_EXPENSE_THRESHOLD
+ && crew_bought - crew_delta < CREW_EXPENSE_THRESHOLD)
+ {
+ GLOBAL (CrewCost) += 2;
+ DrawMenuStateStrings (PM_CREW, SHIPYARD_CREW);
+ }
+ }
+ else
+ {
+ if (crew_bought < CREW_EXPENSE_THRESHOLD
+ && crew_bought - crew_delta >= CREW_EXPENSE_THRESHOLD)
+ {
+ GLOBAL (CrewCost) -= 2;
+ DrawMenuStateStrings (PM_CREW, SHIPYARD_CREW);
+ }
+ }
+ if (CheckAlliance (SHOFIXTI_SHIP) != GOOD_GUY)
+ {
+ SET_GAME_STATE (CREW_PURCHASED0, LOBYTE (crew_bought));
+ SET_GAME_STATE (CREW_PURCHASED1, HIBYTE (crew_bought));
+ }
+ }
+}
+
+static void
+DMS_FlashFlagShip (void)
+{
+ RECT r;
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = 61;
+ SetFlashRect (&r);
+}
+
+static void
+DMS_GetEscortShipRect (RECT *rOut, BYTE slotNr)
+{
+ BYTE row = slotNr / HANGAR_SHIPS_ROW;
+ BYTE col = slotNr % HANGAR_SHIPS_ROW;
+
+ rOut->corner.x = hangar_x_coords[col];
+ rOut->corner.y = HANGAR_Y + (HANGAR_DY * row);
+ rOut->extent.width = SHIP_WIN_WIDTH;
+ rOut->extent.height = SHIP_WIN_HEIGHT;
+}
+
+static void
+DMS_FlashEscortShip (BYTE slotNr)
+{
+ RECT r;
+ DMS_GetEscortShipRect (&r, slotNr);
+ SetFlashRect (&r);
+}
+
+static void
+DMS_FlashFlagShipCrewCount (void)
+{
+ RECT r;
+ SetContext (StatusContext);
+ GetGaugeRect (&r, TRUE);
+ SetFlashRect (&r);
+ SetContext (SpaceContext);
+}
+
+static void
+DMS_FlashEscortShipCrewCount (BYTE slotNr)
+{
+ RECT r;
+ BYTE row = slotNr / HANGAR_SHIPS_ROW;
+ BYTE col = slotNr % HANGAR_SHIPS_ROW;
+
+ r.corner.x = hangar_x_coords[col];
+ r.corner.y = (HANGAR_Y + (HANGAR_DY * row)) + (SHIP_WIN_HEIGHT - 6);
+ r.extent.width = SHIP_WIN_WIDTH;
+ r.extent.height = 5;
+
+ SetContext (SpaceContext);
+ SetFlashRect (&r);
+}
+
+// Helper function for DoModifyShips(). Called to change the flash
+// rectangle to the currently selected ship (flagship or escort ship).
+static void
+DMS_FlashActiveShip (MENU_STATE *pMS)
+{
+ if (HINIBBLE (pMS->CurState))
+ {
+ // Flash the flag ship.
+ DMS_FlashFlagShip ();
+ }
+ else
+ {
+ // Flash the current escort ship slot.
+ DMS_FlashEscortShip (pMS->CurState);
+ }
+}
+
+// Helper function for DoModifyShips(). Called to switch between
+// the various edit modes.
+// XXX: right now, this only switches the sound and flash rectangle.
+// Perhaps we should move more of the code to modify other aspects
+// here too.
+static void
+DMS_SetMode (MENU_STATE *pMS, DMS_Mode mode)
+{
+ switch (mode) {
+ case DMS_Mode_navigate:
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ DMS_FlashActiveShip (pMS);
+ break;
+ case DMS_Mode_addEscort:
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ SetFlashRect (SFR_MENU_ANY);
+ break;
+ case DMS_Mode_editCrew:
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN,
+ MENU_SOUND_SELECT | MENU_SOUND_CANCEL);
+ if (HINIBBLE (pMS->CurState))
+ {
+ // Enter crew editing mode for the flagship.
+ DMS_FlashFlagShipCrewCount ();
+ }
+ else
+ {
+ // Enter crew editing mode for an escort ship.
+ DMS_FlashEscortShipCrewCount (pMS->CurState);
+ }
+ break;
+ case DMS_Mode_exit:
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ SetFlashRect (SFR_MENU_3DO);
+ break;
+ }
+}
+
+#define MODIFY_CREW_FLAG (1 << 8)
+#ifdef WANT_SHIP_SPINS
+// Helper function for DoModifyShips(), called when the player presses the
+// special button.
+// It works both when the cursor is over an escort ship, while not editing
+// the crew, and when a new ship is added.
+// hStarShip is the ship in the slot under the cursor (or 0 if no such ship).
+static BOOLEAN
+DMS_SpinShip (MENU_STATE *pMS, HSHIPFRAG hStarShip)
+{
+ HFLEETINFO hSpinShip = 0;
+ CONTEXT OldContext;
+ RECT OldClipRect;
+
+ // No spinning the flagship.
+ if (HINIBBLE (pMS->CurState) != 0)
+ return FALSE;
+
+ // We must either be hovering over a used ship slot, or adding a new
+ // ship to the fleet.
+ if ((hStarShip == 0) == !(pMS->delta_item & MODIFY_CREW_FLAG))
+ return FALSE;
+
+ if (!hStarShip)
+ {
+ // Selecting a ship to build.
+ hSpinShip = GetAvailableRaceFromIndex (LOBYTE (pMS->delta_item));
+ if (!hSpinShip)
+ return FALSE;
+ }
+ else
+ {
+ // Hovering over an escort ship.
+ SHIP_FRAGMENT *FragPtr = LockShipFrag (
+ &GLOBAL (built_ship_q), hStarShip);
+ hSpinShip = GetStarShipFromIndex (
+ &GLOBAL (avail_race_q), FragPtr->race_id);
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+
+ SetFlashRect (NULL);
+
+ OldContext = SetContext (ScreenContext);
+ GetContextClipRect (&OldClipRect);
+
+ SpinStarShip (pMS, hSpinShip);
+
+ SetContextClipRect (&OldClipRect);
+ SetContext (OldContext);
+
+ return TRUE;
+}
+#endif /* WANT_SHIP_SPINS */
+
+// Helper function for DoModifyShips(), called when the player presses the
+// up button when modifying the crew of the flagship.
+// Buy crew for the flagship.
+// Returns the change in crew (1 on success, 0 on failure).
+static SIZE
+DMS_HireFlagShipCrew (void)
+{
+ RECT r;
+
+ if (GetCPodCapacity (&r.corner) <= GetCrewCount ())
+ {
+ // At capacity.
+ return 0;
+ }
+
+ if (GLOBAL_SIS (ResUnits) < (DWORD)GLOBAL (CrewCost))
+ {
+ // Not enough RUs.
+ return 0;
+ }
+
+ // Draw a crew member.
+ DrawPoint (&r.corner);
+
+ // Update the crew counter and RU. Note that the crew counter is
+ // flashing.
+ PreUpdateFlashRect ();
+ DeltaSISGauges (1, 0, -GLOBAL (CrewCost));
+ PostUpdateFlashRect ();
+
+ return 1;
+}
+
+// Helper function for DoModifyShips(), called when the player presses the
+// down button when modifying the crew of the flagship.
+// Dismiss crew from the flagship.
+// Returns the change in crew (-1 on success, 0 on failure).
+static SIZE
+DMS_DismissFlagShipCrew (void)
+{
+ SIZE crew_bought;
+ RECT r;
+
+ if (GetCrewCount () == 0)
+ {
+ // No crew to dismiss.
+ return 0;
+ }
+
+ crew_bought = (SIZE)MAKE_WORD (
+ GET_GAME_STATE (CREW_PURCHASED0),
+ GET_GAME_STATE (CREW_PURCHASED1));
+
+ // Update the crew counter and RU. Note that the crew counter is
+ // flashing.
+ PreUpdateFlashRect ();
+ DeltaSISGauges (-1, 0, GLOBAL (CrewCost) -
+ (crew_bought == CREW_EXPENSE_THRESHOLD ? 2 : 0));
+ PostUpdateFlashRect ();
+
+ // Remove the pixel representing the crew member.
+ GetCPodCapacity (&r.corner);
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawPoint (&r.corner);
+
+ return -1;
+}
+
+// Helper function for DoModifyShips(), called when the player presses the
+// up button when modifying the crew of an escort ship.
+// Buy crew for an escort ship
+// Returns the change in crew (1 on success, 0 on failure).
+static SIZE
+DMS_HireEscortShipCrew (SHIP_FRAGMENT *StarShipPtr)
+{
+ COUNT templateMaxCrew;
+ RECT r;
+
+ {
+ // XXX Split this off into a separate function?
+ HFLEETINFO hTemplate = GetStarShipFromIndex (&GLOBAL (avail_race_q),
+ StarShipPtr->race_id);
+ FLEET_INFO *TemplatePtr =
+ LockFleetInfo (&GLOBAL (avail_race_q), hTemplate);
+ templateMaxCrew = TemplatePtr->crew_level;
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hTemplate);
+ }
+
+ if (GLOBAL_SIS (ResUnits) < (DWORD)GLOBAL (CrewCost))
+ {
+ // Not enough money to hire a crew member.
+ return 0;
+ }
+
+ if (StarShipPtr->crew_level >= StarShipPtr->max_crew)
+ {
+ // This ship cannot handle more crew.
+ return 0;
+ }
+
+ if (StarShipPtr->crew_level >= templateMaxCrew)
+ {
+ // A ship of this type cannot handle more crew.
+ return 0;
+ }
+
+ if (StarShipPtr->crew_level > 0)
+ {
+ DeltaSISGauges (0, 0, -GLOBAL (CrewCost));
+ }
+ else
+ {
+ // Buy a ship.
+ DeltaSISGauges (0, 0, -(COUNT)ShipCost[StarShipPtr->race_id]);
+ }
+
+ ++StarShipPtr->crew_level;
+
+ PreUpdateFlashRect ();
+ DMS_GetEscortShipRect (&r, StarShipPtr->index);
+ ShowShipCrew (StarShipPtr, &r);
+ PostUpdateFlashRect ();
+
+ return 1;
+}
+
+// Helper function for DoModifyShips(), called when the player presses the
+// down button when modifying the crew of an escort ship.
+// Dismiss crew from an escort ship
+// Returns the change in crew (-1 on success, 0 on failure).
+static SIZE
+DMS_DismissEscortShipCrew (SHIP_FRAGMENT *StarShipPtr)
+{
+ SIZE crew_delta = 0;
+ RECT r;
+
+ if (StarShipPtr->crew_level > 0)
+ {
+ if (StarShipPtr->crew_level > 1)
+ {
+ // The ship was not at 'scrap'.
+ // Give one crew member worth of RU.
+ SIZE crew_bought = (SIZE)MAKE_WORD (
+ GET_GAME_STATE (CREW_PURCHASED0),
+ GET_GAME_STATE (CREW_PURCHASED1));
+
+ DeltaSISGauges (0, 0, GLOBAL (CrewCost)
+ - (crew_bought == CREW_EXPENSE_THRESHOLD ? 2 : 0));
+ }
+ else
+ {
+ // With the last crew member, the ship will be scrapped.
+ // Give RU for the ship.
+ DeltaSISGauges (0, 0, (COUNT)ShipCost[StarShipPtr->race_id]);
+ }
+ crew_delta = -1;
+ --StarShipPtr->crew_level;
+ }
+ else
+ { // no crew to dismiss
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+
+ PreUpdateFlashRect ();
+ DMS_GetEscortShipRect (&r, StarShipPtr->index);
+ ShowShipCrew (StarShipPtr, &r);
+ PostUpdateFlashRect ();
+
+ return crew_delta;
+}
+
+// Helper function for DoModifyShips(), called when the player presses the
+// up or down button when modifying the crew of the flagship or of an escort
+// ship.
+// 'hStarShip' is the currently escort ship, or 0 if no ship is
+// selected.
+// 'dy' is -1 if the 'up' button was pressed, or '1' if the down button was
+// pressed.
+static void
+DMS_ModifyCrew (MENU_STATE *pMS, HSHIPFRAG hStarShip, SBYTE dy)
+{
+ SIZE crew_delta = 0;
+ SHIP_FRAGMENT *StarShipPtr = NULL;
+
+ if (hStarShip)
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ if (hStarShip == 0)
+ {
+ // Add/Dismiss crew for the flagship.
+ if (dy < 0)
+ {
+ // Add crew for the flagship.
+ crew_delta = DMS_HireFlagShipCrew ();
+ }
+ else
+ {
+ // Dismiss crew from the flagship.
+ crew_delta = DMS_DismissFlagShipCrew ();
+ }
+
+ if (crew_delta != 0)
+ DMS_FlashFlagShipCrewCount ();
+ }
+ else
+ {
+ // Add/Dismiss crew for an escort ship.
+ if (dy < 0)
+ {
+ // Add crew for an escort ship.
+ crew_delta = DMS_HireEscortShipCrew (StarShipPtr);
+ }
+ else
+ {
+ // Dismiss crew from an escort ship.
+ crew_delta = DMS_DismissEscortShipCrew (StarShipPtr);
+ }
+
+ if (crew_delta != 0)
+ DMS_FlashEscortShipCrewCount (StarShipPtr->index);
+ }
+
+ if (crew_delta == 0)
+ PlayMenuSound (MENU_SOUND_FAILURE);
+
+ if (hStarShip)
+ {
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ // Clear out the bought ship index so that flash rects work
+ // correctly.
+ pMS->delta_item &= MODIFY_CREW_FLAG;
+ }
+
+ CrewTransaction (crew_delta);
+}
+
+// Helper function for DoModifyShips(), called when the player presses the
+// select button when the cursor is over an empty escort ship slot.
+// Try to add the currently selected ship as an escort ship.
+static void
+DMS_TryAddEscortShip (MENU_STATE *pMS)
+{
+ HFLEETINFO shipInfo = GetAvailableRaceFromIndex (
+ LOBYTE (pMS->delta_item));
+ COUNT Index = GetIndexFromStarShip (&GLOBAL (avail_race_q), shipInfo);
+
+ if (GLOBAL_SIS (ResUnits) >= (DWORD)ShipCost[Index]
+ && CloneShipFragment (Index, &GLOBAL (built_ship_q), 1))
+ {
+ ShowCombatShip (pMS, pMS->CurState, NULL);
+ // Reset flash rectangle
+ DrawMenuStateStrings (PM_CREW, SHIPYARD_CREW);
+
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA,
+ -((int)ShipCost[Index]));
+ DMS_SetMode (pMS, DMS_Mode_editCrew);
+ }
+ else
+ {
+ // not enough RUs to build, or cloning the ship failed.
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+}
+
+// Helper function for DoModifyShips(), called when the player is in the
+// mode to add a new escort ship to the fleet (after pressing select on an
+// empty slot).
+// LOBYTE (pMS->delta_item) is used to store the currently highlighted ship.
+// Returns FALSE if the flash rectangle needs to be updated.
+static void
+DMS_AddEscortShip (MENU_STATE *pMS, BOOLEAN special, BOOLEAN select,
+ BOOLEAN cancel, SBYTE dx, SBYTE dy)
+{
+ assert (pMS->delta_item & MODIFY_CREW_FLAG);
+
+#ifdef WANT_SHIP_SPINS
+ if (special)
+ {
+ HSHIPFRAG hStarShip = GetEscortByStarShipIndex (pMS->delta_item);
+ if (DMS_SpinShip (pMS, hStarShip))
+ DMS_SetMode (pMS, DMS_Mode_addEscort);
+ return;
+ }
+#else
+ (void) special; // Satisfying compiler.
+#endif /* WANT_SHIP_SPINS */
+
+ if (cancel)
+ {
+ // Cancel selecting an escort ship.
+ pMS->delta_item &= ~MODIFY_CREW_FLAG;
+ SetFlashRect (NULL);
+ DrawMenuStateStrings (PM_CREW, SHIPYARD_CREW);
+ DMS_SetMode (pMS, DMS_Mode_navigate);
+ }
+ else if (select)
+ {
+ // Selected a ship to be inserted in an empty escort
+ // ship slot.
+ DMS_TryAddEscortShip (pMS);
+ }
+ else if (dx || dy)
+ {
+ // Motion key pressed while selecting a ship to be
+ // inserted in an empty escort ship slot.
+ COUNT availableCount = GetAvailableRaceCount ();
+ BYTE currentShip = LOBYTE (pMS->delta_item);
+ if (dx < 0 || dy < 0)
+ {
+ if (currentShip-- == 0)
+ currentShip = availableCount - 1;
+ }
+ else if (dx > 0 || dy > 0)
+ {
+ if (++currentShip == availableCount)
+ currentShip = 0;
+ }
+
+ if (currentShip != LOBYTE (pMS->delta_item))
+ {
+ PreUpdateFlashRect ();
+ DrawRaceStrings (pMS, currentShip);
+ PostUpdateFlashRect ();
+ pMS->delta_item = currentShip | MODIFY_CREW_FLAG;
+ }
+ }
+}
+
+// Helper function for DoModifyShips(), called when the player presses
+// 'select' or 'cancel' after selling all the crew.
+static void
+DMS_ScrapEscortShip (MENU_STATE *pMS, HSHIPFRAG hStarShip)
+{
+ SHIP_FRAGMENT *StarShipPtr =
+ LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ BYTE slotNr;
+
+ SetFlashRect (NULL);
+ ShowCombatShip (pMS, pMS->CurState, StarShipPtr);
+
+ slotNr = StarShipPtr->index;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ RemoveQueue (&GLOBAL (built_ship_q), hStarShip);
+ FreeShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ // refresh SIS display
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+
+ SetContext (SpaceContext);
+ DMS_SetMode (pMS, DMS_Mode_navigate);
+}
+
+// Helper function for DoModifyShips(), called when the player presses
+// one of the motion keys when not in crew modification mode.
+static BYTE
+DMS_MoveCursor (BYTE curState, SBYTE dx, SBYTE dy)
+{
+ BYTE row = LONIBBLE(curState) / HANGAR_SHIPS_ROW;
+ BYTE col = LONIBBLE(curState) % HANGAR_SHIPS_ROW;
+ BOOLEAN isFlagShipSelected = (HINIBBLE(curState) != 0);
+
+ if (dy)
+ {
+ // Vertical motion.
+
+ // We consider the flagship an extra row (on the bottom),
+ // to ease operations.
+ if (isFlagShipSelected)
+ row = HANGAR_ROWS;
+
+ // Move up/down, wrapping around:
+ row = (row + (HANGAR_ROWS + 1) + dy) % (HANGAR_ROWS + 1);
+
+ // If we moved to the 'extra row', this means the flag ship.
+ isFlagShipSelected = (row == HANGAR_ROWS);
+ if (isFlagShipSelected)
+ row = 0;
+ }
+ else if (dx)
+ {
+ // Horizontal motion.
+ if (!isFlagShipSelected)
+ {
+ // Moving horizontally through the escort ship slots,
+ // wrapping around if necessary.
+ col = (col + HANGAR_SHIPS_ROW + dx) % HANGAR_SHIPS_ROW;
+ }
+ }
+
+ return MAKE_BYTE(row * HANGAR_SHIPS_ROW + col,
+ isFlagShipSelected ? 0xf : 0);
+}
+
+// Helper function for DoModifyShips(), called every time DoModifyShip() is
+// called when we are in crew editing mode.
+static void
+DMS_EditCrewMode (MENU_STATE *pMS, HSHIPFRAG hStarShip,
+ BOOLEAN select, BOOLEAN cancel, SBYTE dy)
+{
+ if (select || cancel)
+ {
+ // Leave crew editing mode.
+ if (hStarShip != 0)
+ {
+ // Exiting crew editing mode for an escort ship.
+ SHIP_FRAGMENT *StarShipPtr = LockShipFrag (
+ &GLOBAL (built_ship_q), hStarShip);
+ COUNT crew_level = StarShipPtr->crew_level;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ if (crew_level == 0)
+ {
+ // Scrapping the escort ship before exiting crew edit
+ // mode.
+ DMS_ScrapEscortShip (pMS, hStarShip);
+ }
+ }
+
+ pMS->delta_item &= ~MODIFY_CREW_FLAG;
+ DMS_SetMode (pMS, DMS_Mode_navigate);
+ }
+ else if (dy)
+ {
+ // Hire or dismiss crew for the flagship or an escort
+ // ship.
+ DMS_ModifyCrew (pMS, hStarShip, dy);
+ }
+}
+
+// Helper function for DoModifyShips(), called every time DoModifyShip() is
+// called when we are in the mode where you can select a ship or empty slot.
+static void
+DMS_NavigateShipSlots (MENU_STATE *pMS, BOOLEAN special, BOOLEAN select,
+ BOOLEAN cancel, SBYTE dx, SBYTE dy)
+{
+ HSHIPFRAG hStarShip = GetEscortByStarShipIndex (pMS->CurState);
+
+ if (dx || dy)
+ {
+ // Moving through the ship slots.
+ BYTE NewState = DMS_MoveCursor (pMS->CurState, dx, dy);
+ if (NewState != pMS->CurState)
+ {
+ pMS->CurState = NewState;
+ DMS_FlashActiveShip(pMS);
+ }
+ }
+
+#ifndef WANT_SHIP_SPINS
+ (void) special; // Satisfying compiler.
+#else
+ if (special)
+ {
+ if (DMS_SpinShip (pMS, hStarShip))
+ DMS_SetMode (pMS, DMS_Mode_navigate);
+ }
+ else
+#endif /* WANT_SHIP_SPINS */
+ if (select)
+ {
+ if (hStarShip == 0 && HINIBBLE (pMS->CurState) == 0)
+ {
+ // Select button was pressed over an empty escort
+ // ship slot. Switch to 'add escort ship' mode.
+ pMS->delta_item = MODIFY_CREW_FLAG;
+ DrawRaceStrings (pMS, 0);
+ DMS_SetMode (pMS, DMS_Mode_addEscort);
+ }
+ else
+ {
+ // Select button was pressed over an escort ship or
+ // the flagship. Entering crew editing mode
+ pMS->delta_item |= MODIFY_CREW_FLAG;
+ DMS_SetMode (pMS, DMS_Mode_editCrew);
+ }
+ }
+ else if (cancel)
+ {
+ // Leave escort ship editor.
+ pMS->InputFunc = DoShipyard;
+ pMS->CurState = SHIPYARD_CREW;
+ DrawMenuStateStrings (PM_CREW, pMS->CurState);
+ DMS_SetMode (pMS, DMS_Mode_exit);
+ }
+}
+
+/* In this routine, the least significant byte of pMS->CurState is used
+ * to store the current selected ship index
+ * a special case for the row is hi-nibble == -1 (0xf), which specifies
+ * SIS as the selected ship
+ * some bitwise math is still done to scroll through ships, for it to work
+ * ships per row number must divide 0xf0 without remainder
+ */
+static BOOLEAN
+DoModifyShips (MENU_STATE *pMS)
+{
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ pMS->InputFunc = DoShipyard;
+ return TRUE;
+ }
+
+ if (!pMS->Initialized)
+ {
+ pMS->InputFunc = DoModifyShips;
+ pMS->Initialized = TRUE;
+ pMS->CurState = MAKE_BYTE (0, 0xF);
+ pMS->delta_item = 0;
+
+ SetContext (SpaceContext);
+ DMS_SetMode (pMS, DMS_Mode_navigate);
+ }
+ else
+ {
+ BOOLEAN special = (PulsedInputState.menu[KEY_MENU_SPECIAL] != 0);
+ BOOLEAN select = (PulsedInputState.menu[KEY_MENU_SELECT] != 0);
+ BOOLEAN cancel = (PulsedInputState.menu[KEY_MENU_CANCEL] != 0);
+ SBYTE dx = 0;
+ SBYTE dy = 0;
+
+ if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ dx = 1;
+ if (PulsedInputState.menu[KEY_MENU_LEFT])
+ dx = -1;
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ dy = -1;
+ if (PulsedInputState.menu[KEY_MENU_DOWN])
+ dy = 1;
+
+
+ if (!(pMS->delta_item & MODIFY_CREW_FLAG))
+ {
+ // Navigating through the ship slots.
+ DMS_NavigateShipSlots (pMS, special, select, cancel, dx, dy);
+ }
+ else
+ {
+ // Add an escort ship or edit the crew of a ship.
+ HSHIPFRAG hStarShip = GetEscortByStarShipIndex (pMS->CurState);
+
+ if (hStarShip == 0 && HINIBBLE (pMS->CurState) == 0)
+ {
+ // Cursor is over an empty escort ship slot, while we're
+ // in 'add escort ship' mode.
+ DMS_AddEscortShip (pMS, special, select, cancel, dx, dy);
+ }
+ else
+ {
+ // Crew editing mode.
+ DMS_EditCrewMode (pMS, hStarShip, select, cancel, dy);
+ }
+ }
+
+ }
+
+ SleepThread (ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+static void
+DrawBluePrint (MENU_STATE *pMS)
+{
+ COUNT num_frames;
+ STAMP s;
+ FRAME ModuleFrame;
+
+ ModuleFrame = CaptureDrawable (LoadGraphic (SISBLU_MASK_ANIM));
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = DecFrameIndex (ModuleFrame);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x16), 0x01));
+ DrawFilledStamp (&s);
+
+ for (num_frames = 0; num_frames < NUM_DRIVE_SLOTS; ++num_frames)
+ {
+ DrawShipPiece (ModuleFrame, GLOBAL_SIS (DriveSlots[num_frames]),
+ num_frames, TRUE);
+ }
+ for (num_frames = 0; num_frames < NUM_JET_SLOTS; ++num_frames)
+ {
+ DrawShipPiece (ModuleFrame, GLOBAL_SIS (JetSlots[num_frames]),
+ num_frames, TRUE);
+ }
+ for (num_frames = 0; num_frames < NUM_MODULE_SLOTS; ++num_frames)
+ {
+ BYTE which_piece;
+
+ which_piece = GLOBAL_SIS (ModuleSlots[num_frames]);
+
+ if (!(pMS->CurState == SHIPYARD && which_piece == CREW_POD))
+ DrawShipPiece (ModuleFrame, which_piece, num_frames, TRUE);
+ }
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09));
+ for (num_frames = 0; num_frames < NUM_MODULE_SLOTS; ++num_frames)
+ {
+ BYTE which_piece;
+
+ which_piece = GLOBAL_SIS (ModuleSlots[num_frames]);
+ if (pMS->CurState == SHIPYARD && which_piece == CREW_POD)
+ DrawShipPiece (ModuleFrame, which_piece, num_frames, TRUE);
+ }
+
+ {
+ num_frames = GLOBAL_SIS (CrewEnlisted);
+ GLOBAL_SIS (CrewEnlisted) = 0;
+
+ while (num_frames--)
+ {
+ POINT pt;
+
+ GetCPodCapacity (&pt);
+ DrawPoint (&pt);
+
+ ++GLOBAL_SIS (CrewEnlisted);
+ }
+ }
+ {
+ RECT r;
+
+ num_frames = GLOBAL_SIS (TotalElementMass);
+ GLOBAL_SIS (TotalElementMass) = 0;
+
+ r.extent.width = 9;
+ r.extent.height = 1;
+ while (num_frames)
+ {
+ COUNT m;
+
+ m = num_frames < SBAY_MASS_PER_ROW ?
+ num_frames : SBAY_MASS_PER_ROW;
+ GLOBAL_SIS (TotalElementMass) += m;
+ GetSBayCapacity (&r.corner);
+ DrawFilledRectangle (&r);
+ num_frames -= m;
+ }
+ }
+ if (GLOBAL_SIS (FuelOnBoard) > FUEL_RESERVE)
+ {
+ DWORD FuelVolume;
+ RECT r;
+
+ FuelVolume = GLOBAL_SIS (FuelOnBoard) - FUEL_RESERVE;
+ GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE;
+
+ r.extent.width = 3;
+ r.extent.height = 1;
+ while (FuelVolume)
+ {
+ COUNT m;
+
+ GetFTankCapacity (&r.corner);
+ DrawPoint (&r.corner);
+ r.corner.x += r.extent.width + 1;
+ DrawPoint (&r.corner);
+ r.corner.x -= r.extent.width;
+ SetContextForeGroundColor (
+ SetContextBackGroundColor (BLACK_COLOR));
+ DrawFilledRectangle (&r);
+ m = FuelVolume < FUEL_VOLUME_PER_ROW ?
+ (COUNT)FuelVolume : FUEL_VOLUME_PER_ROW;
+ GLOBAL_SIS (FuelOnBoard) += m;
+ FuelVolume -= m;
+ }
+ }
+
+ DestroyDrawable (ReleaseDrawable (ModuleFrame));
+}
+
+BOOLEAN
+DoShipyard (MENU_STATE *pMS)
+{
+ BOOLEAN select, cancel;
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ goto ExitShipyard;
+
+ select = PulsedInputState.menu[KEY_MENU_SELECT];
+ cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ if (!pMS->Initialized)
+ {
+ pMS->InputFunc = DoShipyard;
+
+ {
+ STAMP s;
+ RECT r, old_r;
+
+ pMS->ModuleFrame = CaptureDrawable (
+ LoadGraphic (SHIPYARD_PMAP_ANIM));
+
+ pMS->CurString = CaptureColorMap (
+ LoadColorMap (HANGAR_COLOR_TAB));
+
+ pMS->hMusic = LoadMusic (SHIPYARD_MUSIC);
+
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+ DrawSISFrame ();
+ DrawSISMessage (GAME_STRING (STARBASE_STRING_BASE + 3));
+ DrawSISTitle (GAME_STRING (STARBASE_STRING_BASE));
+ SetContext (SpaceContext);
+ DrawBluePrint (pMS);
+
+ pMS->CurState = SHIPYARD_CREW;
+ DrawMenuStateStrings (PM_CREW, pMS->CurState);
+
+ SetContext (SpaceContext);
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pMS->ModuleFrame, 0);
+#ifdef USE_3DO_HANGAR
+ DrawStamp (&s);
+#else // PC hangar
+ // the PC ship dock needs to overwrite the border
+ // expand the clipping rect by 1 pixel
+ GetContextClipRect (&old_r);
+ r = old_r;
+ r.corner.x--;
+ r.extent.width += 2;
+ r.extent.height += 1;
+ SetContextClipRect (&r);
+ DrawStamp (&s);
+ SetContextClipRect (&old_r);
+ animatePowerLines (pMS);
+#endif // USE_3DO_HANGAR
+
+ SetContextFont (TinyFont);
+
+ ScreenTransition (3, NULL);
+ UnbatchGraphics ();
+
+ PlayMusic (pMS->hMusic, TRUE, 1);
+
+ ShowCombatShip (pMS, (COUNT)~0, NULL);
+
+ SetInputCallback (on_input_frame);
+
+ SetFlashRect (SFR_MENU_3DO);
+ }
+
+ pMS->Initialized = TRUE;
+ }
+ else if (cancel || (select && pMS->CurState == SHIPYARD_EXIT))
+ {
+ExitShipyard:
+ SetInputCallback (NULL);
+
+ DestroyDrawable (ReleaseDrawable (pMS->ModuleFrame));
+ pMS->ModuleFrame = 0;
+ DestroyColorMap (ReleaseColorMap (pMS->CurString));
+ pMS->CurString = 0;
+
+ return FALSE;
+ }
+ else if (select)
+ {
+ if (pMS->CurState != SHIPYARD_SAVELOAD)
+ {
+ pMS->Initialized = FALSE;
+ DoModifyShips (pMS);
+ }
+ else
+ {
+ // Clearing FlashRect is not necessary
+ if (!GameOptions ())
+ goto ExitShipyard;
+ DrawMenuStateStrings (PM_CREW, pMS->CurState);
+ SetFlashRect (SFR_MENU_3DO);
+ }
+ }
+ else
+ {
+ DoMenuChooser (pMS, PM_CREW);
+ }
+
+ return TRUE;
+}
+
diff --git a/src/uqm/sis.c b/src/uqm/sis.c
new file mode 100644
index 0000000..9f50d1d
--- /dev/null
+++ b/src/uqm/sis.c
@@ -0,0 +1,1741 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "sis.h"
+
+#include "colors.h"
+#include "races.h"
+#include "starmap.h"
+#include "units.h"
+#include "menustat.h"
+ // for DrawMenuStateStrings()
+#include "gamestr.h"
+#include "options.h"
+#include "battle.h"
+ // For BATTLE_FRAME_RATE
+#include "element.h"
+#include "setup.h"
+#include "state.h"
+#include "flash.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/tasklib.h"
+#include "libs/alarm.h"
+#include "libs/log.h"
+
+#include <stdio.h>
+
+static StatMsgMode curMsgMode = SMM_DEFAULT;
+
+static const UNICODE *describeWeapon (BYTE moduleType);
+
+void
+RepairSISBorder (void)
+{
+ RECT r;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (ScreenContext);
+
+ BatchGraphics ();
+
+ // Left border
+ r.corner.x = SIS_ORG_X - 1;
+ r.corner.y = SIS_ORG_Y - 1;
+ r.extent.width = 1;
+ r.extent.height = SIS_SCREEN_HEIGHT + 2;
+ SetContextForeGroundColor (SIS_LEFT_BORDER_COLOR);
+ DrawFilledRectangle (&r);
+
+ // Right border
+ SetContextForeGroundColor (SIS_BOTTOM_RIGHT_BORDER_COLOR);
+ r.corner.x += (SIS_SCREEN_WIDTH + 2) - 1;
+ DrawFilledRectangle (&r);
+
+ // Bottom border
+ r.corner.x = SIS_ORG_X - 1;
+ r.corner.y += (SIS_SCREEN_HEIGHT + 2) - 1;
+ r.extent.width = SIS_SCREEN_WIDTH + 2;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+
+ UnbatchGraphics ();
+
+ SetContext (OldContext);
+}
+
+void
+ClearSISRect (BYTE ClearFlags)
+{
+ RECT r;
+ Color OldColor;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+ OldColor = SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+
+ r.corner.x = 2;
+ r.extent.width = STATUS_WIDTH - 4;
+
+ BatchGraphics ();
+ if (ClearFlags & DRAW_SIS_DISPLAY)
+ {
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+ }
+
+ if (ClearFlags & CLEAR_SIS_RADAR)
+ {
+ DrawMenuStateStrings ((BYTE)~0, 1);
+#ifdef NEVER
+ r.corner.x = RADAR_X - 1;
+ r.corner.y = RADAR_Y - 1;
+ r.extent.width = RADAR_WIDTH + 2;
+ r.extent.height = RADAR_HEIGHT + 2;
+
+ DrawStarConBox (&r, 1,
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19),
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F),
+ TRUE, BUILD_COLOR (MAKE_RGB15 (0x00, 0x0E, 0x00), 0x6C));
+#endif /* NEVER */
+ }
+ UnbatchGraphics ();
+
+ SetContextForeGroundColor (OldColor);
+ SetContext (OldContext);
+}
+
+// Draw the SIS title. This is the field at the top of the screen, on the
+// right hand side, containing the coordinates in HyperSpace, or the planet
+// name in IP.
+void
+DrawSISTitle (UNICODE *pStr)
+{
+ TEXT t;
+ CONTEXT OldContext;
+ RECT r;
+
+ t.baseline.x = SIS_TITLE_WIDTH >> 1;
+ t.baseline.y = SIS_TITLE_HEIGHT - 2;
+ t.align = ALIGN_CENTER;
+ t.pStr = pStr;
+ t.CharCount = (COUNT)~0;
+
+ OldContext = SetContext (OffScreenContext);
+ r.corner.x = SIS_ORG_X + SIS_SCREEN_WIDTH - SIS_TITLE_BOX_WIDTH + 1;
+ r.corner.y = SIS_ORG_Y - SIS_TITLE_HEIGHT;
+ r.extent.width = SIS_TITLE_WIDTH;
+ r.extent.height = SIS_TITLE_HEIGHT - 1;
+ SetContextFGFrame (Screen);
+ SetContextClipRect (&r);
+ SetContextFont (TinyFont);
+
+ BatchGraphics ();
+
+ // Background color
+ SetContextBackGroundColor (SIS_TITLE_BACKGROUND_COLOR);
+ ClearDrawable ();
+
+ // Text color
+ SetContextForeGroundColor (SIS_TITLE_TEXT_COLOR);
+ font_DrawText (&t);
+
+ UnbatchGraphics ();
+
+ SetContextClipRect (NULL);
+
+ SetContext (OldContext);
+}
+
+void
+DrawHyperCoords (POINT universe)
+{
+ UNICODE buf[100];
+
+ snprintf (buf, sizeof buf, "%03u.%01u : %03u.%01u",
+ universe.x / 10, universe.x % 10,
+ universe.y / 10, universe.y % 10);
+
+ DrawSISTitle (buf);
+}
+
+void
+DrawSISMessage (const UNICODE *pStr)
+{
+ DrawSISMessageEx (pStr, -1, -1, DSME_NONE);
+}
+
+// See sis.h for the allowed flags.
+BOOLEAN
+DrawSISMessageEx (const UNICODE *pStr, SIZE CurPos, SIZE ExPos, COUNT flags)
+{
+ UNICODE buf[256];
+ CONTEXT OldContext;
+ TEXT t;
+ RECT r;
+
+ OldContext = SetContext (OffScreenContext);
+ // prepare the context
+ r.corner.x = SIS_ORG_X + 1;
+ r.corner.y = SIS_ORG_Y - SIS_MESSAGE_HEIGHT;
+ r.extent.width = SIS_MESSAGE_WIDTH;
+ r.extent.height = SIS_MESSAGE_HEIGHT - 1;
+ SetContextFGFrame (Screen);
+ SetContextClipRect (&r);
+
+ BatchGraphics ();
+ SetContextBackGroundColor (SIS_MESSAGE_BACKGROUND_COLOR);
+
+ if (pStr == 0)
+ {
+ switch (LOBYTE (GLOBAL (CurrentActivity)))
+ {
+ default:
+ case IN_ENCOUNTER:
+ pStr = "";
+ break;
+ case IN_LAST_BATTLE:
+ case IN_INTERPLANETARY:
+ GetClusterName (CurStarDescPtr, buf);
+ pStr = buf;
+ break;
+ case IN_HYPERSPACE:
+ if (inHyperSpace ())
+ {
+ pStr = GAME_STRING (NAVIGATION_STRING_BASE);
+ // "HyperSpace"
+ }
+ else
+ {
+ pStr = GAME_STRING (NAVIGATION_STRING_BASE + 1);
+ // "QuasiSpace"
+ }
+ break;
+ }
+
+ }
+
+ if (!(flags & DSME_MYCOLOR))
+ SetContextForeGroundColor (SIS_MESSAGE_TEXT_COLOR);
+
+ t.baseline.y = SIS_MESSAGE_HEIGHT - 2;
+ t.pStr = pStr;
+ t.CharCount = (COUNT)~0;
+ SetContextFont (TinyFont);
+
+ if (flags & DSME_CLEARFR)
+ SetFlashRect (NULL);
+
+ if (CurPos < 0 && ExPos < 0)
+ { // normal state
+ ClearDrawable ();
+ t.baseline.x = SIS_MESSAGE_WIDTH >> 1;
+ t.align = ALIGN_CENTER;
+ font_DrawText (&t);
+ }
+ else
+ { // editing state
+ int i;
+ RECT text_r;
+ // XXX: 128 is currently safe, but it would be better to specify
+ // the size to TextRect()
+ BYTE char_deltas[128];
+ BYTE *pchar_deltas;
+
+ t.baseline.x = 3;
+ t.align = ALIGN_LEFT;
+
+ TextRect (&t, &text_r, char_deltas);
+ if (text_r.extent.width + t.baseline.x + 2 >= r.extent.width)
+ { // the text does not fit the input box size and so
+ // will not fit when displayed later
+ // disallow the change
+ UnbatchGraphics ();
+ SetContextClipRect (NULL);
+ SetContext (OldContext);
+ return (FALSE);
+ }
+
+ ClearDrawable ();
+
+ if (CurPos >= 0 && CurPos <= t.CharCount)
+ { // calc and draw the cursor
+ RECT cur_r = text_r;
+
+ for (i = CurPos, pchar_deltas = char_deltas; i > 0; --i)
+ cur_r.corner.x += (SIZE)*pchar_deltas++;
+ if (CurPos < t.CharCount) /* end of line */
+ --cur_r.corner.x;
+
+ if (flags & DSME_BLOCKCUR)
+ { // Use block cursor for keyboardless systems
+ if (CurPos == t.CharCount)
+ { // cursor at end-line -- use insertion point
+ cur_r.extent.width = 1;
+ }
+ else if (CurPos + 1 == t.CharCount)
+ { // extra pixel for last char margin
+ cur_r.extent.width = (SIZE)*pchar_deltas + 2;
+ }
+ else
+ { // normal mid-line char
+ cur_r.extent.width = (SIZE)*pchar_deltas + 1;
+ }
+ }
+ else
+ { // Insertion point cursor
+ cur_r.extent.width = 1;
+ }
+
+ cur_r.corner.y = 0;
+ cur_r.extent.height = r.extent.height;
+ SetContextForeGroundColor (SIS_MESSAGE_CURSOR_COLOR);
+ DrawFilledRectangle (&cur_r);
+ }
+
+ SetContextForeGroundColor (SIS_MESSAGE_TEXT_COLOR);
+
+ if (ExPos >= 0 && ExPos < t.CharCount)
+ { // handle extra characters
+ t.CharCount = ExPos;
+ font_DrawText (&t);
+
+ // print extra chars
+ SetContextForeGroundColor (SIS_MESSAGE_EXTRA_TEXT_COLOR);
+ for (i = ExPos, pchar_deltas = char_deltas; i > 0; --i)
+ t.baseline.x += (SIZE)*pchar_deltas++;
+ t.pStr = skipUTF8Chars (t.pStr, ExPos);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ }
+ else
+ { // just print the text
+ font_DrawText (&t);
+ }
+ }
+
+ if (flags & DSME_SETFR)
+ {
+ r.corner.x = 0;
+ r.corner.y = 0;
+ SetFlashRect (&r);
+ }
+
+ UnbatchGraphics ();
+
+ SetContextClipRect (NULL);
+ SetContext (OldContext);
+
+ return (TRUE);
+}
+
+void
+DateToString (char *buf, size_t bufLen,
+ BYTE month_index, BYTE day_index, COUNT year_index)
+{
+ snprintf (buf, bufLen, "%s %02d" STR_MIDDLE_DOT "%04d",
+ GAME_STRING (MONTHS_STRING_BASE + month_index - 1),
+ day_index, year_index);
+}
+
+void
+GetStatusMessageRect (RECT *r)
+{
+ r->corner.x = 2;
+ r->corner.y = 130;
+ r->extent.width = STATUS_MESSAGE_WIDTH;
+ r->extent.height = STATUS_MESSAGE_HEIGHT;
+}
+
+void
+DrawStatusMessage (const UNICODE *pStr)
+{
+ RECT r;
+ RECT ctxRect;
+ TEXT t;
+ UNICODE buf[128];
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+ GetContextClipRect (&ctxRect);
+ // XXX: Technically, this does not need OffScreenContext. The only reason
+ // it is used is to avoid preserving StatusContext settings.
+ SetContext (OffScreenContext);
+ SetContextFGFrame (Screen);
+ GetStatusMessageRect (&r);
+ r.corner.x += ctxRect.corner.x;
+ r.corner.y += ctxRect.corner.y;
+ SetContextClipRect (&r);
+
+ BatchGraphics ();
+ SetContextBackGroundColor (STATUS_MESSAGE_BACKGROUND_COLOR);
+ ClearDrawable ();
+
+ if (!pStr)
+ {
+ if (curMsgMode == SMM_CREDITS)
+ {
+ snprintf (buf, sizeof buf, "%u %s", MAKE_WORD (
+ GET_GAME_STATE (MELNORME_CREDIT0),
+ GET_GAME_STATE (MELNORME_CREDIT1)
+ ), GAME_STRING (STATUS_STRING_BASE + 0)); // "Cr"
+ }
+ else if (curMsgMode == SMM_RES_UNITS)
+ {
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
+ {
+ snprintf (buf, sizeof buf, "%u %s", GLOBAL_SIS (ResUnits),
+ GAME_STRING (STATUS_STRING_BASE + 1)); // "RU"
+ }
+ else
+ {
+ snprintf (buf, sizeof buf, "%s %s",
+ (optWhichMenu == OPT_PC) ?
+ GAME_STRING (STATUS_STRING_BASE + 2)
+ : STR_INFINITY_SIGN, // "UNLIMITED"
+ GAME_STRING (STATUS_STRING_BASE + 1)); // "RU"
+ }
+ }
+ else
+ { // Just a date
+ DateToString (buf, sizeof buf,
+ GLOBAL (GameClock.month_index),
+ GLOBAL (GameClock.day_index),
+ GLOBAL (GameClock.year_index));
+ }
+ pStr = buf;
+ }
+
+ t.baseline.x = STATUS_MESSAGE_WIDTH >> 1;
+ t.baseline.y = STATUS_MESSAGE_HEIGHT - 1;
+ t.align = ALIGN_CENTER;
+ t.pStr = pStr;
+ t.CharCount = (COUNT)~0;
+
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (STATUS_MESSAGE_TEXT_COLOR);
+ font_DrawText (&t);
+ UnbatchGraphics ();
+
+ SetContextClipRect (NULL);
+
+ SetContext (OldContext);
+}
+
+StatMsgMode
+SetStatusMessageMode (StatMsgMode newMode)
+{
+ StatMsgMode oldMode = curMsgMode;
+ curMsgMode = newMode;
+ return oldMode;
+}
+
+void
+DrawCaptainsName (void)
+{
+ RECT r;
+ TEXT t;
+ CONTEXT OldContext;
+ FONT OldFont;
+ Color OldColor;
+
+ OldContext = SetContext (StatusContext);
+ OldFont = SetContextFont (TinyFont);
+ OldColor = SetContextForeGroundColor (CAPTAIN_NAME_BACKGROUND_COLOR);
+
+ r.corner.x = 2 + 1;
+ r.corner.y = 10;
+ r.extent.width = SHIP_NAME_WIDTH - 2;
+ r.extent.height = SHIP_NAME_HEIGHT;
+ DrawFilledRectangle (&r);
+
+ t.baseline.x = (STATUS_WIDTH >> 1) - 1;
+ t.baseline.y = r.corner.y + 6;
+ t.align = ALIGN_CENTER;
+ t.pStr = GLOBAL_SIS (CommanderName);
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (CAPTAIN_NAME_TEXT_COLOR);
+ font_DrawText (&t);
+
+ SetContextForeGroundColor (OldColor);
+ SetContextFont (OldFont);
+ SetContext (OldContext);
+}
+
+void
+DrawFlagshipName (BOOLEAN InStatusArea)
+{
+ RECT r;
+ TEXT t;
+ FONT OldFont;
+ Color OldColor;
+ CONTEXT OldContext;
+ FRAME OldFontEffect;
+ UNICODE buf[250];
+
+ if (InStatusArea)
+ {
+ OldContext = SetContext (StatusContext);
+ OldFont = SetContextFont (StarConFont);
+
+ r.corner.x = 2;
+ r.corner.y = 20;
+ r.extent.width = SHIP_NAME_WIDTH;
+ r.extent.height = SHIP_NAME_HEIGHT;
+
+ t.pStr = GLOBAL_SIS (ShipName);
+ }
+ else
+ {
+ OldContext = SetContext (SpaceContext);
+ OldFont = SetContextFont (MicroFont);
+
+ r.corner.x = 0;
+ r.corner.y = 1;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = SHIP_NAME_HEIGHT;
+
+ t.pStr = buf;
+ snprintf (buf, sizeof buf, "%s %s",
+ GAME_STRING (NAMING_STRING_BASE + 1), GLOBAL_SIS (ShipName));
+ // XXX: this will not work with UTF-8 strings
+ strupr (buf);
+ }
+ OldFontEffect = SetContextFontEffect (NULL);
+ OldColor = SetContextForeGroundColor (FLAGSHIP_NAME_BACKGROUND_COLOR);
+ DrawFilledRectangle (&r);
+
+ t.baseline.x = r.corner.x + (r.extent.width >> 1);
+ t.baseline.y = r.corner.y + (SHIP_NAME_HEIGHT - InStatusArea);
+ t.align = ALIGN_CENTER;
+ t.CharCount = (COUNT)~0;
+ if (optWhichFonts == OPT_PC)
+ SetContextFontEffect (SetAbsFrameIndex (FontGradFrame,
+ InStatusArea ? 0 : 3));
+ else
+ SetContextForeGroundColor (THREEDO_FLAGSHIP_NAME_TEXT_COLOR);
+
+ font_DrawText (&t);
+
+ SetContextFontEffect (OldFontEffect);
+ SetContextForeGroundColor (OldColor);
+ SetContextFont (OldFont);
+ SetContext (OldContext);
+}
+
+void
+DrawFlagshipStats (void)
+{
+ RECT r;
+ TEXT t;
+ FONT OldFont;
+ Color OldColor;
+ FRAME OldFontEffect;
+ CONTEXT OldContext;
+ UNICODE buf[128];
+ SIZE leading;
+ BYTE i;
+ BYTE energy_regeneration, energy_wait, turn_wait;
+ COUNT max_thrust;
+ DWORD fuel;
+
+ /* collect stats */
+#define ENERGY_REGENERATION 1
+#define ENERGY_WAIT 10
+#define MAX_THRUST 10
+#define TURN_WAIT 17
+ energy_regeneration = ENERGY_REGENERATION;
+ energy_wait = ENERGY_WAIT;
+ max_thrust = MAX_THRUST;
+ turn_wait = TURN_WAIT;
+ fuel = 10 * FUEL_TANK_SCALE;
+
+ for (i = 0; i < NUM_MODULE_SLOTS; i++)
+ {
+ switch (GLOBAL_SIS (ModuleSlots[i])) {
+ case FUEL_TANK:
+ fuel += FUEL_TANK_CAPACITY;
+ break;
+ case HIGHEFF_FUELSYS:
+ fuel += HEFUEL_TANK_CAPACITY;
+ break;
+ case DYNAMO_UNIT:
+ energy_wait -= 2;
+ if (energy_wait < 4)
+ energy_wait = 4;
+ break;
+ case SHIVA_FURNACE:
+ energy_regeneration++;
+ break;
+ }
+ }
+
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ if (GLOBAL_SIS (DriveSlots[i]) == FUSION_THRUSTER)
+ max_thrust += 2;
+
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ if (GLOBAL_SIS (JetSlots[i]) == TURNING_JETS)
+ turn_wait -= 2;
+ /* END collect stats */
+
+ OldContext = SetContext (SpaceContext);
+ OldFont = SetContextFont (StarConFont);
+ OldFontEffect = SetContextFontEffect (NULL);
+ GetContextFontLeading (&leading);
+
+ /* we need room to play. full screen width, 4 lines tall */
+ r.corner.x = 0;
+ r.corner.y = SIS_SCREEN_HEIGHT - (4 * leading);
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = (4 * leading);
+
+ OldColor = SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ /*
+ now that we've cleared out our playground, compensate for the
+ fact that the leading is way more than is generally needed.
+ */
+ leading -= 3;
+ t.baseline.x = SIS_SCREEN_WIDTH / 6; //wild-assed guess, but it worked
+ t.baseline.y = r.corner.y + leading + 3;
+ t.align = ALIGN_RIGHT;
+ t.CharCount = (COUNT)~0;
+
+ SetContextFontEffect (SetAbsFrameIndex (FontGradFrame, 4));
+
+ t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 0); // "nose:"
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 1); // "spread:"
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 2); // "side:"
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 3); // "tail:"
+ font_DrawText (&t);
+
+ t.baseline.x += 5;
+ t.baseline.y = r.corner.y + leading + 3;
+ t.align = ALIGN_LEFT;
+ t.pStr = buf;
+
+ snprintf (buf, sizeof buf, "%-7.7s",
+ describeWeapon (GLOBAL_SIS (ModuleSlots[15])));
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ snprintf (buf, sizeof buf,
+ "%-7.7s", describeWeapon (GLOBAL_SIS (ModuleSlots[14])));
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ snprintf (buf, sizeof buf,
+ "%-7.7s", describeWeapon (GLOBAL_SIS (ModuleSlots[13])));
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ snprintf (buf, sizeof buf,
+ "%-7.7s", describeWeapon (GLOBAL_SIS (ModuleSlots[0])));
+ font_DrawText (&t);
+
+ t.baseline.x = r.extent.width - 25;
+ t.baseline.y = r.corner.y + leading + 3;
+ t.align = ALIGN_RIGHT;
+
+ SetContextFontEffect (SetAbsFrameIndex (FontGradFrame, 5));
+
+ t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 4); // "maximum velocity:"
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 5); // "turning rate:"
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 6); // "combat energy:"
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 7); // "maximum fuel:"
+ font_DrawText (&t);
+
+ t.baseline.x = r.extent.width - 2;
+ t.baseline.y = r.corner.y + leading + 3;
+ t.pStr = buf;
+
+ snprintf (buf, sizeof buf, "%4u", max_thrust * 4);
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ snprintf (buf, sizeof buf, "%4u", 1 + TURN_WAIT - turn_wait);
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ {
+ unsigned int energy_per_10_sec =
+ (((100 * ONE_SECOND * energy_regeneration) /
+ ((1 + energy_wait) * BATTLE_FRAME_RATE)) + 5) / 10;
+ snprintf (buf, sizeof buf, "%2u.%1u",
+ energy_per_10_sec / 10, energy_per_10_sec % 10);
+ }
+ font_DrawText (&t);
+ t.baseline.y += leading;
+ snprintf (buf, sizeof buf, "%4u", (fuel / FUEL_TANK_SCALE));
+ font_DrawText (&t);
+
+ SetContextFontEffect (OldFontEffect);
+ SetContextForeGroundColor (OldColor);
+ SetContextFont (OldFont);
+ SetContext (OldContext);
+}
+
+static const UNICODE *
+describeWeapon (BYTE moduleType)
+{
+ switch (moduleType)
+ {
+ case GUN_WEAPON:
+ return GAME_STRING (FLAGSHIP_STRING_BASE + 8); // "gun"
+ case BLASTER_WEAPON:
+ return GAME_STRING (FLAGSHIP_STRING_BASE + 9); // "blaster"
+ case CANNON_WEAPON:
+ return GAME_STRING (FLAGSHIP_STRING_BASE + 10); // "cannon"
+ case BOMB_MODULE_0:
+ case BOMB_MODULE_1:
+ case BOMB_MODULE_2:
+ case BOMB_MODULE_3:
+ case BOMB_MODULE_4:
+ case BOMB_MODULE_5:
+ return GAME_STRING (FLAGSHIP_STRING_BASE + 11); // "n/a"
+ default:
+ return GAME_STRING (FLAGSHIP_STRING_BASE + 12); // "none"
+ }
+}
+
+void
+DrawLanders (void)
+{
+ BYTE i;
+ SIZE width;
+ RECT r;
+ STAMP s;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+
+ s.frame = IncFrameIndex (FlagStatFrame);
+ GetFrameRect (s.frame, &r);
+
+ i = GLOBAL_SIS (NumLanders);
+ r.corner.x = (STATUS_WIDTH >> 1) - r.corner.x;
+ s.origin.x = r.corner.x - (((r.extent.width * i) + (2 * (i - 1))) >> 1);
+ s.origin.y = 29;
+
+ width = r.extent.width + 2;
+ r.extent.width = (r.extent.width * MAX_LANDERS)
+ + (2 * (MAX_LANDERS - 1)) + 2;
+ r.corner.x -= r.extent.width >> 1;
+ r.corner.y += s.origin.y;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ while (i--)
+ {
+ DrawStamp (&s);
+ s.origin.x += width;
+ }
+
+ SetContext (OldContext);
+}
+
+// Draw the storage bays, below the picture of the flagship.
+void
+DrawStorageBays (BOOLEAN Refresh)
+{
+ BYTE i;
+ RECT r;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+
+ r.extent.width = 2;
+ r.extent.height = 4;
+ r.corner.y = 123;
+ if (Refresh)
+ {
+ r.extent.width = NUM_MODULE_SLOTS * (r.extent.width + 1);
+ r.corner.x = (STATUS_WIDTH >> 1) - (r.extent.width >> 1);
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ r.extent.width = 2;
+ }
+
+ i = (BYTE)CountSISPieces (STORAGE_BAY);
+ if (i)
+ {
+ COUNT j;
+
+ r.corner.x = (STATUS_WIDTH >> 1)
+ - ((i * (r.extent.width + 1)) >> 1);
+ SetContextForeGroundColor (STORAGE_BAY_FULL_COLOR);
+ for (j = GLOBAL_SIS (TotalElementMass);
+ j >= STORAGE_BAY_CAPACITY; j -= STORAGE_BAY_CAPACITY)
+ {
+ DrawFilledRectangle (&r);
+ r.corner.x += r.extent.width + 1;
+
+ --i;
+ }
+
+ r.extent.height = (4 * j + (STORAGE_BAY_CAPACITY - 1)) /
+ STORAGE_BAY_CAPACITY;
+ if (r.extent.height)
+ {
+ r.corner.y += 4 - r.extent.height;
+ DrawFilledRectangle (&r);
+ r.extent.height = 4 - r.extent.height;
+ if (r.extent.height)
+ {
+ r.corner.y = 123;
+ SetContextForeGroundColor (STORAGE_BAY_EMPTY_COLOR);
+ DrawFilledRectangle (&r);
+ }
+ r.corner.x += r.extent.width + 1;
+
+ --i;
+ }
+ r.extent.height = 4;
+
+ SetContextForeGroundColor (STORAGE_BAY_EMPTY_COLOR);
+ while (i--)
+ {
+ DrawFilledRectangle (&r);
+ r.corner.x += r.extent.width + 1;
+ }
+ }
+
+ SetContext (OldContext);
+}
+
+void
+GetGaugeRect (RECT *pRect, BOOLEAN IsCrewRect)
+{
+ pRect->extent.width = 24;
+ pRect->corner.x = (STATUS_WIDTH >> 1) - (pRect->extent.width >> 1);
+ pRect->extent.height = 5;
+ pRect->corner.y = IsCrewRect ? 117 : 38;
+}
+
+static void
+DrawPC_SIS (void)
+{
+ TEXT t;
+ RECT r;
+
+ GetGaugeRect (&r, FALSE);
+ t.baseline.x = STATUS_WIDTH >> 1;
+ t.baseline.y = r.corner.y - 1;
+ t.align = ALIGN_CENTER;
+ t.CharCount = (COUNT)~0;
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (BLACK_COLOR);
+
+ r.corner.y -= 6;
+ r.corner.x--;
+ r.extent.width += 2;
+ DrawFilledRectangle (&r);
+
+ SetContextFontEffect (SetAbsFrameIndex (FontGradFrame, 1));
+ t.pStr = GAME_STRING (STATUS_STRING_BASE + 3); // "FUEL"
+ font_DrawText (&t);
+
+ r.corner.y += 79;
+ t.baseline.y += 79;
+ DrawFilledRectangle (&r);
+
+ SetContextFontEffect (SetAbsFrameIndex (FontGradFrame, 2));
+ t.pStr = GAME_STRING (STATUS_STRING_BASE + 4); // "CREW"
+ font_DrawText (&t);
+ SetContextFontEffect (NULL);
+
+ // Background of text "CAPTAIN".
+ r.corner.x = 2 + 1;
+ r.corner.y = 3;
+ r.extent.width = 58;
+ r.extent.height = 7;
+ SetContextForeGroundColor (PC_CAPTAIN_STRING_BACKGROUND_COLOR);
+ DrawFilledRectangle (&r);
+
+ // Text "CAPTAIN".
+ SetContextForeGroundColor (PC_CAPTAIN_STRING_TEXT_COLOR);
+ t.baseline.y = r.corner.y + 6;
+ t.pStr = GAME_STRING (STATUS_STRING_BASE + 5); // "CAPTAIN"
+ font_DrawText (&t);
+}
+
+static void
+DrawThrusters (void)
+{
+ STAMP s;
+ COUNT i;
+
+ s.origin.x = 1;
+ s.origin.y = 0;
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ BYTE which_piece = GLOBAL_SIS (DriveSlots[i]);
+ if (which_piece < EMPTY_SLOT)
+ {
+ s.frame = SetAbsFrameIndex (FlagStatFrame, which_piece + 1 + 0);
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ DrawStamp (&s);
+ }
+
+ s.origin.y -= 3;
+ }
+}
+
+static void
+DrawTurningJets (void)
+{
+ STAMP s;
+ COUNT i;
+
+ s.origin.x = 1;
+ s.origin.y = 0;
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ BYTE which_piece = GLOBAL_SIS (JetSlots[i]);
+ if (which_piece < EMPTY_SLOT)
+ {
+ s.frame = SetAbsFrameIndex (FlagStatFrame, which_piece + 1 + 1);
+ DrawStamp (&s);
+ s.frame = IncFrameIndex (s.frame);
+ DrawStamp (&s);
+ }
+
+ s.origin.y -= 3;
+ }
+}
+
+static void
+DrawModules (void)
+{
+ STAMP s;
+ COUNT i;
+
+ s.origin.x = 1; // This properly centers the modules.
+ s.origin.y = 1;
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ BYTE which_piece = GLOBAL_SIS (ModuleSlots[i]);
+ if (which_piece < EMPTY_SLOT)
+ {
+ s.frame = SetAbsFrameIndex (FlagStatFrame, which_piece + 1 + 2);
+ DrawStamp (&s);
+ }
+
+ s.origin.y -= 3;
+ }
+}
+
+static void
+DrawSupportShips (void)
+{
+ HSHIPFRAG hStarShip;
+ HSHIPFRAG hNextShip;
+ const POINT *pship_pos;
+ const POINT ship_pos[MAX_BUILT_SHIPS] =
+ {
+ SUPPORT_SHIP_PTS
+ };
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)),
+ pship_pos = ship_pos;
+ hStarShip; hStarShip = hNextShip, ++pship_pos)
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+ STAMP s;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+
+ s.origin = *pship_pos;
+ s.frame = StarShipPtr->icons;
+ DrawStamp (&s);
+
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+}
+
+static void
+DeltaSISGauges_crewDelta (SIZE crew_delta)
+{
+ if (crew_delta == 0)
+ return;
+
+ if (crew_delta != UNDEFINED_DELTA)
+ {
+ COUNT CrewCapacity;
+
+ if (crew_delta < 0
+ && GLOBAL_SIS (CrewEnlisted) <= (COUNT)-crew_delta)
+ GLOBAL_SIS (CrewEnlisted) = 0;
+ else
+ {
+ GLOBAL_SIS (CrewEnlisted) += crew_delta;
+ CrewCapacity = GetCrewPodCapacity ();
+ if (GLOBAL_SIS (CrewEnlisted) > CrewCapacity)
+ GLOBAL_SIS (CrewEnlisted) = CrewCapacity;
+ }
+ }
+
+ {
+ TEXT t;
+ UNICODE buf[60];
+ RECT r;
+
+ snprintf (buf, sizeof buf, "%u", GLOBAL_SIS (CrewEnlisted));
+
+ GetGaugeRect (&r, TRUE);
+
+ t.baseline.x = STATUS_WIDTH >> 1;
+ t.baseline.y = r.corner.y + r.extent.height;
+ t.align = ALIGN_CENTER;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x0E, 0x00), 0x6C));
+ font_DrawText (&t);
+ }
+}
+
+static void
+DeltaSISGauges_fuelDelta (SIZE fuel_delta)
+{
+ COUNT old_coarse_fuel;
+ COUNT new_coarse_fuel;
+
+ if (fuel_delta == 0)
+ return;
+
+ if (fuel_delta == UNDEFINED_DELTA)
+ old_coarse_fuel = (COUNT)~0;
+ else
+ {
+
+ old_coarse_fuel = (COUNT)(
+ GLOBAL_SIS (FuelOnBoard) / FUEL_TANK_SCALE);
+ if (fuel_delta < 0
+ && GLOBAL_SIS (FuelOnBoard) <= (DWORD)-fuel_delta)
+ {
+ GLOBAL_SIS (FuelOnBoard) = 0;
+ }
+ else
+ {
+ DWORD FuelCapacity = GetFuelTankCapacity ();
+ GLOBAL_SIS (FuelOnBoard) += fuel_delta;
+ if (GLOBAL_SIS (FuelOnBoard) > FuelCapacity)
+ GLOBAL_SIS (FuelOnBoard) = FuelCapacity;
+ }
+ }
+
+ new_coarse_fuel = (COUNT)(
+ GLOBAL_SIS (FuelOnBoard) / FUEL_TANK_SCALE);
+ if (new_coarse_fuel != old_coarse_fuel)
+ {
+ TEXT t;
+ UNICODE buf[60];
+ RECT r;
+
+ snprintf (buf, sizeof buf, "%u", new_coarse_fuel);
+
+ GetGaugeRect (&r, FALSE);
+
+ t.baseline.x = STATUS_WIDTH >> 1;
+ t.baseline.y = r.corner.y + r.extent.height;
+ t.align = ALIGN_CENTER;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x13, 0x00, 0x00), 0x2C));
+ font_DrawText (&t);
+ }
+}
+
+static void
+DeltaSISGauges_resunitDelta (SIZE resunit_delta)
+{
+ if (resunit_delta == 0)
+ return;
+
+ if (resunit_delta != UNDEFINED_DELTA)
+ {
+ if (resunit_delta < 0
+ && GLOBAL_SIS (ResUnits) <= (DWORD)-resunit_delta)
+ GLOBAL_SIS (ResUnits) = 0;
+ else
+ GLOBAL_SIS (ResUnits) += resunit_delta;
+
+ assert (curMsgMode == SMM_RES_UNITS);
+ }
+ else
+ {
+ RECT r;
+
+ r.corner.x = 2;
+ r.corner.y = 130;
+ r.extent.width = STATUS_MESSAGE_WIDTH;
+ r.extent.height = STATUS_MESSAGE_HEIGHT;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x08, 0x00), 0x6E));
+ DrawFilledRectangle (&r);
+ }
+
+ DrawStatusMessage (NULL);
+}
+
+void
+DeltaSISGauges (SIZE crew_delta, SIZE fuel_delta, int resunit_delta)
+{
+ CONTEXT OldContext;
+
+ if (crew_delta == 0 && fuel_delta == 0 && resunit_delta == 0)
+ return;
+
+ OldContext = SetContext (StatusContext);
+
+ BatchGraphics ();
+ if (crew_delta == UNDEFINED_DELTA)
+ {
+ STAMP s;
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = FlagStatFrame;
+ DrawStamp (&s);
+
+ if (optWhichFonts == OPT_PC)
+ DrawPC_SIS();
+
+ DrawThrusters ();
+ DrawTurningJets ();
+ DrawModules ();
+
+ DrawSupportShips ();
+ }
+
+ SetContextFont (TinyFont);
+
+ DeltaSISGauges_crewDelta (crew_delta);
+ DeltaSISGauges_fuelDelta (fuel_delta);
+
+ if (crew_delta == UNDEFINED_DELTA)
+ {
+ DrawFlagshipName (TRUE);
+ DrawCaptainsName ();
+ DrawLanders ();
+ DrawStorageBays (FALSE);
+ }
+
+ DeltaSISGauges_resunitDelta (resunit_delta);
+
+ UnbatchGraphics ();
+
+ SetContext (OldContext);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// Crew
+////////////////////////////////////////////////////////////////////////////
+
+// Get the total amount of crew aboard the SIS.
+COUNT
+GetCrewCount (void)
+{
+ return GLOBAL_SIS (CrewEnlisted);
+}
+
+// Get the number of crew which fit in a module of a specified type.
+COUNT
+GetModuleCrewCapacity (BYTE moduleType)
+{
+ if (moduleType == CREW_POD)
+ return CREW_POD_CAPACITY;
+
+ return 0;
+}
+
+// Gets the amount of crew which currently fit in the ship's crew pods.
+COUNT
+GetCrewPodCapacity (void)
+{
+ COUNT capacity = 0;
+ COUNT slotI;
+
+ for (slotI = 0; slotI < NUM_MODULE_SLOTS; slotI++)
+ {
+ BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
+ capacity += GetModuleCrewCapacity (moduleType);
+ }
+
+ return capacity;
+}
+
+// Find the slot number of the crew pod and "seat" number in that crew pod,
+// where the Nth crew member would be located.
+// If the crew member does not fit, false is returned, and *slotNr and
+// *seatNr are unchanged.
+static bool
+GetCrewPodForCrewMember (COUNT crewNr, COUNT *slotNr, COUNT *seatNr)
+{
+ COUNT slotI;
+ COUNT capacity = 0;
+
+ slotI = NUM_MODULE_SLOTS;
+ while (slotI--) {
+ BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
+ COUNT moduleCapacity = GetModuleCrewCapacity (moduleType);
+
+ if (crewNr < capacity + moduleCapacity)
+ {
+ *slotNr = slotI;
+ *seatNr = crewNr - capacity;
+ return true;
+ }
+ capacity += moduleCapacity;
+ }
+
+ return false;
+}
+
+// Get the point where to draw the next crew member,
+// set the foreground color to the color for that crew member,
+// and return GetCrewPodCapacity ().
+// TODO: Split of the parts of this function into separate functions.
+COUNT
+GetCPodCapacity (POINT *ppt)
+{
+ COUNT crewCount;
+ COUNT slotNr;
+ COUNT seatNr;
+
+ COUNT rowNr;
+ COUNT colNr;
+
+ static const Color crewRows[] = PC_CREW_COLOR_TABLE;
+
+ crewCount = GetCrewCount ();
+ if (!GetCrewPodForCrewMember (crewCount, &slotNr, &seatNr))
+ {
+ // Crew does not fit. *ppt is unchanged.
+ return GetCrewPodCapacity ();
+ }
+
+ rowNr = seatNr / CREW_PER_ROW;
+ colNr = seatNr % CREW_PER_ROW;
+
+ if (optWhichFonts == OPT_PC)
+ SetContextForeGroundColor (crewRows[rowNr]);
+ else
+ SetContextForeGroundColor (THREEDO_CREW_COLOR);
+
+ ppt->x = 27 + (slotNr * SHIP_PIECE_OFFSET) - (colNr * 2);
+ ppt->y = 34 - (rowNr * 2);
+
+ return GetCrewPodCapacity ();
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// Storage bays
+////////////////////////////////////////////////////////////////////////////
+
+// Get the total amount of minerals aboard the SIS.
+static COUNT
+GetElementMass (void)
+{
+ return GLOBAL_SIS (TotalElementMass);
+}
+
+// Get the number of crew which fit in a module of a specified type.
+COUNT
+GetModuleStorageCapacity (BYTE moduleType)
+{
+ if (moduleType == STORAGE_BAY)
+ return STORAGE_BAY_CAPACITY;
+
+ return 0;
+}
+
+// Gets the amount of minerals which currently fit in the ship's storage.
+COUNT
+GetStorageBayCapacity (void)
+{
+ COUNT capacity = 0;
+ COUNT slotI;
+
+ for (slotI = 0; slotI < NUM_MODULE_SLOTS; slotI++)
+ {
+ BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
+ capacity += GetModuleStorageCapacity (moduleType);
+ }
+
+ return capacity;
+}
+
+// Find the slot number of the storage bay and "storage cell" number in that
+// storage bay, where the N-1th mineral unit would be located.
+// If the mineral unit does not fit, false is returned, and *slotNr and
+// *cellNr are unchanged.
+static bool
+GetStorageCellForMineralUnit (COUNT unitNr, COUNT *slotNr, COUNT *cellNr)
+{
+ COUNT slotI;
+ COUNT capacity = 0;
+
+ slotI = NUM_MODULE_SLOTS;
+ while (slotI--) {
+ BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
+ COUNT moduleCapacity = GetModuleStorageCapacity (moduleType);
+
+ if (unitNr <= capacity + moduleCapacity)
+ {
+ *slotNr = slotI;
+ *cellNr = unitNr - capacity;
+ return true;
+ }
+ capacity += moduleCapacity;
+ }
+
+ return false;
+}
+
+// Get the point where to draw the next mineral unit,
+// set the foreground color to the color for that mineral unit,
+// and return GetStorageBayCapacity ().
+// TODO: Split of the parts of this function into separate functions.
+COUNT
+GetSBayCapacity (POINT *ppt)
+{
+ COUNT massCount;
+ COUNT slotNr;
+ COUNT cellNr;
+
+ COUNT rowNr;
+ COUNT colNr;
+
+ static const Color colorBars[] = STORAGE_BAY_COLOR_TABLE;
+
+ massCount = GetElementMass ();
+ if (!GetStorageCellForMineralUnit (massCount, &slotNr, &cellNr))
+ {
+ // Crew does not fit. *ppt is unchanged.
+ return GetStorageBayCapacity ();
+ }
+
+ rowNr = cellNr / SBAY_MASS_PER_ROW;
+ colNr = cellNr % SBAY_MASS_PER_ROW;
+
+ if (rowNr == 0)
+ SetContextForeGroundColor (BLACK_COLOR);
+ else
+ {
+ rowNr--;
+ SetContextForeGroundColor (colorBars[rowNr]);
+ }
+
+ ppt->x = 19 + (slotNr * SHIP_PIECE_OFFSET);
+ ppt->y = 34 - (rowNr * 2);
+
+ return GetStorageBayCapacity ();
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// Fuel tanks
+////////////////////////////////////////////////////////////////////////////
+
+// Get the total amount of fuel aboard the SIS.
+static DWORD
+GetFuelTotal (void)
+{
+ return GLOBAL_SIS (FuelOnBoard);
+}
+
+// Get the amount of fuel which fits in a module of a specified type.
+DWORD
+GetModuleFuelCapacity (BYTE moduleType)
+{
+ if (moduleType == FUEL_TANK)
+ return FUEL_TANK_CAPACITY;
+
+ if (moduleType == HIGHEFF_FUELSYS)
+ return HEFUEL_TANK_CAPACITY;
+
+ return 0;
+}
+
+// Gets the amount of fuel which currently fits in the ship's fuel tanks.
+DWORD
+GetFuelTankCapacity (void)
+{
+ DWORD capacity = FUEL_RESERVE;
+ COUNT slotI;
+
+ for (slotI = 0; slotI < NUM_MODULE_SLOTS; slotI++)
+ {
+ BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
+ capacity += GetModuleFuelCapacity (moduleType);
+ }
+
+ return capacity;
+}
+
+// Find the slot number of the fuel cell and "compartment" number in that
+// crew pod, where the Nth unit of fuel would be located.
+// If the unit does not fit, false is returned, and *slotNr and
+// *compartmentNr are unchanged.
+// Pre: unitNr >= FUEL_RESERER
+static bool
+GetFuelTankForFuelUnit (DWORD unitNr, COUNT *slotNr, DWORD *compartmentNr)
+{
+ COUNT slotI;
+ DWORD capacity = FUEL_RESERVE;
+
+ assert (unitNr >= FUEL_RESERVE);
+
+ slotI = NUM_MODULE_SLOTS;
+ while (slotI--) {
+ BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
+
+ capacity += GetModuleFuelCapacity (moduleType);
+ if (unitNr < capacity)
+ {
+ *slotNr = slotI;
+ *compartmentNr = capacity - unitNr;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Get the point where to draw the next fuel unit,
+// set the foreground color to the color for that unit,
+// and return GetFuelTankCapacity ().
+// TODO: Split of the parts of this function into separate functions.
+DWORD
+GetFTankCapacity (POINT *ppt)
+{
+ DWORD capacity;
+ DWORD fuelAmount;
+ COUNT slotNr;
+ DWORD compartmentNr;
+ BYTE moduleType;
+ DWORD volume;
+
+ COUNT rowNr;
+
+ static const Color fuelColors[] = FUEL_COLOR_TABLE;
+
+ capacity = GetFuelTankCapacity ();
+ fuelAmount = GetFuelTotal ();
+ if (fuelAmount < FUEL_RESERVE)
+ {
+ // Fuel is in the SIS reserve, not in a fuel tank.
+ // *ppt is unchanged
+ return capacity;
+ }
+
+ if (!GetFuelTankForFuelUnit (fuelAmount, &slotNr, &compartmentNr))
+ {
+ // Fuel does not fit. *ppt is unchanged.
+ return capacity;
+ }
+
+ moduleType = GLOBAL_SIS (ModuleSlots[slotNr]);
+ volume = GetModuleFuelCapacity (moduleType);
+
+ rowNr = ((volume - compartmentNr) * MAX_FUEL_BARS / HEFUEL_TANK_CAPACITY);
+
+ ppt->x = 21 + (slotNr * SHIP_PIECE_OFFSET);
+ if (volume == FUEL_TANK_CAPACITY)
+ ppt->y = 27 - rowNr;
+ else
+ ppt->y = 30 - rowNr;
+
+ assert (rowNr + 1 < (COUNT) (sizeof fuelColors / sizeof fuelColors[0]));
+ SetContextForeGroundColor (fuelColors[rowNr]);
+ SetContextBackGroundColor (fuelColors[rowNr + 1]);
+
+ return capacity;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+COUNT
+CountSISPieces (BYTE piece_type)
+{
+ COUNT i, num_pieces;
+
+ num_pieces = 0;
+ if (piece_type == FUSION_THRUSTER)
+ {
+ for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (DriveSlots[i]) == piece_type)
+ ++num_pieces;
+ }
+ }
+ else if (piece_type == TURNING_JETS)
+ {
+ for (i = 0; i < NUM_JET_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (JetSlots[i]) == piece_type)
+ ++num_pieces;
+ }
+ }
+ else
+ {
+ for (i = 0; i < NUM_MODULE_SLOTS; ++i)
+ {
+ if (GLOBAL_SIS (ModuleSlots[i]) == piece_type)
+ ++num_pieces;
+ }
+ }
+
+ return num_pieces;
+}
+
+void
+DrawAutoPilotMessage (BOOLEAN Reset)
+{
+ static BOOLEAN LastPilot = FALSE;
+ static TimeCount NextTime = 0;
+ static DWORD cycle_index = 0;
+ BOOLEAN OnAutoPilot;
+
+ static const Color cycle_tab[] = AUTOPILOT_COLOR_CYCLE_TABLE;
+ const size_t cycleCount = sizeof cycle_tab / sizeof cycle_tab[0];
+#define BLINK_RATE (ONE_SECOND * 3 / 40) // 9 @ 120 ticks/second
+
+ if (Reset)
+ { // Just a reset, not drawing
+ LastPilot = FALSE;
+ return;
+ }
+
+ OnAutoPilot = (GLOBAL (autopilot.x) != ~0 && GLOBAL (autopilot.y) != ~0)
+ || GLOBAL_SIS (FuelOnBoard) == 0;
+
+ if (OnAutoPilot || LastPilot)
+ {
+ if (!OnAutoPilot)
+ { // AutoPilot aborted -- clear the AUTO-PILOT message
+ DrawSISMessage (NULL);
+ cycle_index = 0;
+ }
+ else if (GetTimeCounter () >= NextTime)
+ {
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT)
+ && GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
+ {
+ CONTEXT OldContext;
+
+ OldContext = SetContext (OffScreenContext);
+ SetContextForeGroundColor (cycle_tab[cycle_index]);
+ if (GLOBAL_SIS (FuelOnBoard) == 0)
+ {
+ DrawSISMessageEx (GAME_STRING (NAVIGATION_STRING_BASE + 2),
+ -1, -1, DSME_MYCOLOR); // "OUT OF FUEL"
+ }
+ else
+ {
+ DrawSISMessageEx (GAME_STRING (NAVIGATION_STRING_BASE + 3),
+ -1, -1, DSME_MYCOLOR); // "AUTO-PILOT"
+ }
+ SetContext (OldContext);
+ }
+
+ cycle_index = (cycle_index + 1) % cycleCount;
+ NextTime = GetTimeCounter () + BLINK_RATE;
+ }
+
+ LastPilot = OnAutoPilot;
+ }
+}
+
+
+static FlashContext *flashContext = NULL;
+static RECT flash_rect;
+static Alarm *flashAlarm = NULL;
+static BOOLEAN flashPaused = FALSE;
+
+static void scheduleFlashAlarm (void);
+
+static void
+updateFlashRect (void *arg)
+{
+ if (flashContext == NULL)
+ return;
+
+ Flash_process (flashContext);
+ scheduleFlashAlarm ();
+ (void) arg;
+}
+
+static void
+scheduleFlashAlarm (void)
+{
+ TimeCount nextTime = Flash_nextTime (flashContext);
+ DWORD nextTimeMs = (nextTime / ONE_SECOND) * 1000 +
+ ((nextTime % ONE_SECOND) * 1000 / ONE_SECOND);
+ // Overflow-safe conversion.
+ flashAlarm = Alarm_addAbsoluteMs (nextTimeMs, updateFlashRect, NULL);
+}
+
+void
+SetFlashRect (const RECT *pRect)
+{
+ RECT clip_r = {{0, 0}, {0, 0}};
+ RECT temp_r;
+
+ if (pRect != SFR_MENU_3DO && pRect != SFR_MENU_ANY)
+ {
+ // The caller specified their own flash area, or NULL (stop flashing).
+ GetContextClipRect (&clip_r);
+ }
+ else
+ {
+ if (optWhichMenu == OPT_PC && pRect != SFR_MENU_ANY)
+ {
+ // The player wants PC menus and this flash is not used
+ // for a PC menu.
+ // Don't flash.
+ pRect = 0;
+ }
+ else
+ {
+ // The player wants 3DO menus, or the flash is used in both
+ // 3DO and PC mode.
+ CONTEXT OldContext = SetContext (StatusContext);
+ GetContextClipRect (&clip_r);
+ pRect = &temp_r;
+ temp_r.corner.x = RADAR_X - clip_r.corner.x;
+ temp_r.corner.y = RADAR_Y - clip_r.corner.y;
+ temp_r.extent.width = RADAR_WIDTH;
+ temp_r.extent.height = RADAR_HEIGHT;
+ SetContext (OldContext);
+ }
+ }
+
+ if (pRect != 0 && pRect->extent.width != 0)
+ {
+ // Flash rectangle is not empty, start or continue flashing.
+ flash_rect = *pRect;
+ flash_rect.corner.x += clip_r.corner.x;
+ flash_rect.corner.y += clip_r.corner.y;
+
+ if (flashContext == NULL)
+ {
+ // Create a new flash context.
+ flashContext = Flash_createHighlight (ScreenContext, &flash_rect);
+ Flash_setMergeFactors(flashContext, 3, 2, 2);
+ Flash_setSpeed (flashContext, 0, ONE_SECOND / 16, 0, ONE_SECOND / 16);
+ Flash_setFrameTime (flashContext, ONE_SECOND / 16);
+ Flash_start (flashContext);
+ scheduleFlashAlarm ();
+ }
+ else
+ {
+ // Reuse an existing flash context
+ Flash_setRect (flashContext, &flash_rect);
+ }
+ }
+ else
+ {
+ // Flash rectangle is empty. Stop flashing.
+ if (flashContext != NULL)
+ {
+ Alarm_remove(flashAlarm);
+ flashAlarm = 0;
+
+ Flash_terminate (flashContext);
+ flashContext = NULL;
+ }
+ }
+}
+
+COUNT updateFlashRectRecursion = 0;
+// XXX This is necessary at least because DMS_AddEscortShip() calls
+// DrawRaceStrings() in an UpdateFlashRect block, which calls
+// ClearSISRect(), which calls DrawMenuStateStrings(), which starts its own
+// UpdateFlashRect block. This should probably be cleaned up.
+
+void
+PreUpdateFlashRect (void)
+{
+ if (flashAlarm)
+ {
+ updateFlashRectRecursion++;
+ if (updateFlashRectRecursion > 1)
+ return;
+ Flash_preUpdate (flashContext);
+ }
+}
+
+void
+PostUpdateFlashRect (void)
+{
+ if (flashAlarm)
+ {
+ updateFlashRectRecursion--;
+ if (updateFlashRectRecursion > 0)
+ return;
+
+ Flash_postUpdate (flashContext);
+ }
+}
+
+// Stop flashing if flashing is active.
+void
+PauseFlash (void)
+{
+ if (flashContext != NULL)
+ {
+ Alarm_remove(flashAlarm);
+ flashAlarm = 0;
+ flashPaused = TRUE;
+ }
+}
+
+// Continue flashing after PauseFlash (), if flashing was active.
+void
+ContinueFlash (void)
+{
+ if (flashPaused)
+ {
+ scheduleFlashAlarm ();
+ flashPaused = FALSE;
+ }
+}
+
+
diff --git a/src/uqm/sis.h b/src/uqm/sis.h
new file mode 100644
index 0000000..ee07a81
--- /dev/null
+++ b/src/uqm/sis.h
@@ -0,0 +1,241 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SIS_H_INCL__
+#define SIS_H_INCL__
+
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+#include "planets/elemdata.h"
+ // for NUM_ELEMENT_CATEGORIES
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define CLEAR_SIS_RADAR (1 << 2)
+#define DRAW_SIS_DISPLAY (1 << 3)
+
+#define UNDEFINED_DELTA 0x7FFF
+
+#define NUM_DRIVE_SLOTS 11
+#define NUM_JET_SLOTS 8
+#define NUM_MODULE_SLOTS 16
+
+#define CREW_POD_CAPACITY 50
+#define STORAGE_BAY_CAPACITY 500 /* km cubed */
+#define FUEL_TANK_SCALE 100
+#define FUEL_TANK_CAPACITY (50 * FUEL_TANK_SCALE)
+#define HEFUEL_TANK_CAPACITY (100 * FUEL_TANK_SCALE)
+#define MODULE_COST_SCALE 50
+
+#define CREW_EXPENSE_THRESHOLD 1000
+
+#define CREW_PER_ROW 5
+#define SBAY_MASS_PER_ROW 50
+
+#define MAX_FUEL_BARS 10
+#define FUEL_VOLUME_PER_ROW (HEFUEL_TANK_CAPACITY / MAX_FUEL_BARS)
+#define FUEL_RESERVE FUEL_VOLUME_PER_ROW
+
+#define IP_SHIP_THRUST_INCREMENT 8
+#define IP_SHIP_TURN_WAIT 17
+#define IP_SHIP_TURN_DECREMENT 2
+
+#define BIO_CREDIT_VALUE 2
+
+enum
+{
+ PLANET_LANDER = 0,
+ /* thruster types */
+ FUSION_THRUSTER,
+ /* jet types */
+ TURNING_JETS,
+ /* module types */
+ CREW_POD,
+ STORAGE_BAY,
+ FUEL_TANK,
+ HIGHEFF_FUELSYS,
+ DYNAMO_UNIT,
+ SHIVA_FURNACE,
+ GUN_WEAPON,
+ BLASTER_WEAPON,
+ CANNON_WEAPON,
+ TRACKING_SYSTEM,
+ ANTIMISSILE_DEFENSE,
+
+ NUM_PURCHASE_MODULES,
+
+ BOMB_MODULE_0 = NUM_PURCHASE_MODULES,
+ BOMB_MODULE_1,
+ BOMB_MODULE_2,
+ BOMB_MODULE_3,
+ BOMB_MODULE_4,
+ BOMB_MODULE_5,
+
+ NUM_MODULES /* must be last entry */
+};
+
+#define EMPTY_SLOT NUM_MODULES
+#define NUM_BOMB_MODULES 10
+
+#define DRIVE_SIDE_X 31
+#define DRIVE_SIDE_Y 56
+#define DRIVE_TOP_X 33
+#define DRIVE_TOP_Y (65 + 21)
+
+#define JET_SIDE_X 71
+#define JET_SIDE_Y 48
+#define JET_TOP_X 70
+#define JET_TOP_Y (73 + 21)
+
+#define MODULE_SIDE_X 17
+#define MODULE_SIDE_Y 14
+#define MODULE_TOP_X 17
+#define MODULE_TOP_Y (96 + 21)
+
+#define SHIP_PIECE_OFFSET 12
+
+#define MAX_BUILT_SHIPS 12
+ /* Maximum number of ships escorting the SIS */
+#define MAX_LANDERS 10
+
+#define SUPPORT_SHIP_PTS \
+ {3 + 0, 30 + (2 * 16)}, \
+ {3 + 42, 30 + (2 * 16)}, \
+ {3 + 0, 30 + (3 * 16)}, \
+ {3 + 42, 30 + (3 * 16)}, \
+ {3 + 0, 30 + (1 * 16)}, \
+ {3 + 42, 30 + (1 * 16)}, \
+ {3 + 0, 30 + (4 * 16)}, \
+ {3 + 42, 30 + (4 * 16)}, \
+ {3 + 0, 30 + (0 * 16)}, \
+ {3 + 42, 30 + (0 * 16)}, \
+ {3 + 0, 30 + (5 * 16)}, \
+ {3 + 42, 30 + (5 * 16)},
+
+#define SIS_NAME_SIZE 16
+
+typedef struct
+{
+ SDWORD log_x, log_y;
+
+ DWORD ResUnits;
+
+ DWORD FuelOnBoard;
+ COUNT CrewEnlisted;
+ // Number of crew on board, not counting the captain.
+ // Set to (COUNT) ~0 to indicate game over.
+ COUNT TotalElementMass, TotalBioMass;
+
+ BYTE ModuleSlots[NUM_MODULE_SLOTS];
+ BYTE DriveSlots[NUM_DRIVE_SLOTS];
+ BYTE JetSlots[NUM_JET_SLOTS];
+
+ BYTE NumLanders;
+
+ COUNT ElementAmounts[NUM_ELEMENT_CATEGORIES];
+
+ UNICODE ShipName[SIS_NAME_SIZE];
+ UNICODE CommanderName[SIS_NAME_SIZE];
+ UNICODE PlanetName[SIS_NAME_SIZE];
+} SIS_STATE;
+
+#define OVERRIDE_LANDER_FLAGS (1 << 7)
+#define AFTER_BOMB_INSTALLED (1 << 7)
+
+extern void RepairSISBorder (void);
+extern void InitSISContexts (void);
+extern void DrawSISFrame (void);
+extern void ClearSISRect (BYTE ClearFlags);
+extern void SetFlashRect (const RECT *pRect);
+extern void PreUpdateFlashRect (void);
+extern void PostUpdateFlashRect (void);
+extern void PauseFlash (void);
+extern void ContinueFlash (void);
+
+#define SFR_MENU_3DO ((RECT*)~0L)
+#define SFR_MENU_ANY ((RECT*)~1L)
+extern void DrawHyperCoords (POINT puniverse);
+extern void DrawSISTitle (UNICODE *pStr);
+
+// Flags for DrawSISMessageEx (may be OR'ed):
+#define DSME_NONE 0
+#define DSME_SETFR (1 << 0)
+ // Set the flash rectangle to the message area.
+#define DSME_CLEARFR (1 << 1)
+ // Disable the flash rectangle.
+#define DSME_BLOCKCUR (1 << 2)
+ // Use a block cursor instead of an insertion point cursor,
+ // when editing in the message field.
+#define DSME_MYCOLOR (1 << 3)
+ // Use the current foreground color, instead of the default.
+extern BOOLEAN DrawSISMessageEx (const UNICODE *pStr, SIZE CurPos,
+ SIZE ExPos, COUNT flags);
+
+extern void DrawSISMessage (const UNICODE *pStr);
+extern void DateToString (char *buf, size_t bufLen,
+ BYTE month_index, BYTE day_index, COUNT year_index);
+
+// Returned RECT is relative to the StatusContext
+extern void GetStatusMessageRect (RECT *r);
+extern void DrawStatusMessage (const UNICODE *pStr);
+typedef enum
+{
+ SMM_UNDEFINED = 0,
+ SMM_DATE,
+ SMM_RES_UNITS,
+ SMM_CREDITS,
+
+ SMM_DEFAULT = SMM_DATE,
+} StatMsgMode;
+// Sets the new mode and return the previous
+extern StatMsgMode SetStatusMessageMode (StatMsgMode);
+
+extern void DrawLanders (void);
+extern void DrawStorageBays (BOOLEAN Refresh);
+extern void GetGaugeRect (RECT *pRect, BOOLEAN IsCrewRect);
+extern void DrawFlagshipStats (void);
+void DrawAutoPilotMessage (BOOLEAN Reset);
+
+extern void DeltaSISGauges (SIZE crew_delta, SIZE fuel_delta, int
+ resunit_delta);
+
+extern COUNT GetCrewCount (void);
+extern COUNT GetModuleCrewCapacity (BYTE moduleType);
+
+extern COUNT GetCrewPodCapacity (void);
+extern COUNT GetCPodCapacity (POINT *ppt);
+
+extern COUNT GetModuleStorageCapacity (BYTE moduleType);
+extern COUNT GetStorageBayCapacity (void);
+extern COUNT GetSBayCapacity (POINT *ppt);
+
+extern DWORD GetModuleFuelCapacity (BYTE moduleType);
+extern DWORD GetFuelTankCapacity (void);
+extern DWORD GetFTankCapacity (POINT *ppt);
+
+extern COUNT CountSISPieces (BYTE piece_type);
+
+extern void DrawFlagshipName (BOOLEAN InStatusArea);
+extern void DrawCaptainsName (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SIS_H_INCL__ */
+
diff --git a/src/uqm/sounds.c b/src/uqm/sounds.c
new file mode 100644
index 0000000..be14c84
--- /dev/null
+++ b/src/uqm/sounds.c
@@ -0,0 +1,199 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "settings.h"
+#include "sounds.h"
+#include "units.h"
+
+
+SOUND MenuSounds;
+SOUND GameSounds;
+
+#define MAX_SOUNDS 8
+static BYTE num_sounds = 0;
+static SOUND sound_buf[MAX_SOUNDS];
+static ELEMENT *sound_posobj[MAX_SOUNDS];
+
+void
+PlaySound (SOUND S, SoundPosition Pos, ELEMENT *PositionalObject,
+ BYTE Priority)
+{
+ BYTE chan, c;
+ static BYTE lru_channel[NUM_FX_CHANNELS] = {0, 1, 2, 3};
+ static SOUND channel[NUM_FX_CHANNELS] = {0, 0, 0, 0};
+
+ if (S == 0)
+ return;
+
+ for (chan = 0; chan < NUM_FX_CHANNELS; ++chan)
+ {
+ if (S == channel[chan])
+ break;
+ }
+
+ if (chan == NUM_FX_CHANNELS)
+ {
+ for (chan = 0; chan < NUM_FX_CHANNELS; ++chan)
+ {
+ if (!ChannelPlaying (chan + MIN_FX_CHANNEL))
+ break;
+ }
+
+ if (chan == NUM_FX_CHANNELS)
+ chan = lru_channel[0];
+ }
+
+ channel[chan] = S;
+
+ for (c = 0; c < NUM_FX_CHANNELS - 1; ++c)
+ {
+ if (lru_channel[c] == chan)
+ {
+ memmove (&lru_channel[c], &lru_channel[c + 1],
+ (NUM_FX_CHANNELS - 1) - c);
+ break;
+ }
+ }
+ lru_channel[NUM_FX_CHANNELS - 1] = chan;
+
+ PlaySoundEffect (S, chan + MIN_FX_CHANNEL, Pos, PositionalObject, Priority);
+}
+
+void
+PlayMenuSound (MENU_SOUND_EFFECT S)
+{
+ PlaySoundEffect (SetAbsSoundIndex (MenuSounds, S),
+ 0, NotPositional (), NULL,
+ GAME_SOUND_PRIORITY);
+}
+
+void
+ProcessSound (SOUND Sound, ELEMENT *PositionalObject)
+{
+ if (Sound == (SOUND)~0)
+ {
+ memset (sound_buf, 0, sizeof (sound_buf));
+ memset (sound_posobj, 0, sizeof (sound_posobj));
+ num_sounds = MAX_SOUNDS;
+ }
+ else if (num_sounds < MAX_SOUNDS)
+ {
+ sound_buf[num_sounds] = Sound;
+ sound_posobj[num_sounds++] = PositionalObject;
+ }
+}
+
+SoundPosition
+CalcSoundPosition (ELEMENT *ElementPtr)
+{
+ SoundPosition pos;
+
+ if (ElementPtr == NULL)
+ {
+ pos.x = pos.y = 0;
+ pos.positional = FALSE;
+ }
+ else
+ {
+ GRAPHICS_PRIM objtype;
+
+ objtype = GetPrimType (&DisplayArray[ElementPtr->PrimIndex]);
+ if (objtype == LINE_PRIM)
+ {
+ pos.x = DisplayArray[ElementPtr->PrimIndex].Object.Line.first.x;
+ pos.y = DisplayArray[ElementPtr->PrimIndex].Object.Line.first.y;
+ }
+ else
+ {
+ pos.x = DisplayArray[ElementPtr->PrimIndex].Object.Point.x;
+ pos.y = DisplayArray[ElementPtr->PrimIndex].Object.Point.y;
+ }
+
+ pos.x -= (SPACE_WIDTH >> 1);
+ pos.y -= (SPACE_HEIGHT >> 1);
+ pos.positional = TRUE;
+ }
+
+ return pos;
+}
+
+SoundPosition
+NotPositional (void)
+{
+ return CalcSoundPosition (NULL);
+}
+
+/* Updates positional sound effects */
+void
+UpdateSoundPositions (void)
+{
+ COUNT i;
+
+ for (i = FIRST_SFX_CHANNEL; i <= LAST_SFX_CHANNEL; ++i)
+ {
+ ELEMENT *posobj;
+ if (!ChannelPlaying(i))
+ continue;
+
+ posobj = GetPositionalObject (i);
+ if (posobj != NULL)
+ {
+ SoundPosition pos;
+ pos = CalcSoundPosition (posobj);
+ if (pos.positional)
+ UpdateSoundPosition (i, pos);
+ }
+ }
+}
+
+void
+FlushSounds (void)
+{
+ if (num_sounds > 0)
+ {
+ SOUND *pSound;
+ ELEMENT **pSoundPosObj;
+
+ pSound = sound_buf;
+ pSoundPosObj = sound_posobj;
+ do
+ {
+ SoundPosition pos = CalcSoundPosition (*pSoundPosObj);
+ PlaySound (*pSound++, pos, *pSoundPosObj++,
+ GAME_SOUND_PRIORITY);
+ } while (--num_sounds);
+ }
+}
+
+void
+RemoveSoundsForObject (ELEMENT *PosObj)
+{
+ int i;
+
+ for (i = FIRST_SFX_CHANNEL; i <= LAST_SFX_CHANNEL; ++i)
+ {
+ if (GetPositionalObject (i) == PosObj)
+ SetPositionalObject (i, NULL);
+ }
+
+ for (i = 0; i < num_sounds; ++i)
+ {
+ if (sound_posobj[i] == PosObj)
+ sound_posobj[i] = NULL;
+ }
+}
+
+
diff --git a/src/uqm/sounds.h b/src/uqm/sounds.h
new file mode 100644
index 0000000..622b3ba
--- /dev/null
+++ b/src/uqm/sounds.h
@@ -0,0 +1,85 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_SOUNDS_H_
+#define UQM_SOUNDS_H_
+
+#include "element.h"
+#include "libs/compiler.h"
+#include "libs/sndlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef enum
+{
+ GRAB_CREW = 0,
+ SHIP_EXPLODES,
+ TARGET_DAMAGED_FOR_1_PT,
+ TARGET_DAMAGED_FOR_2_3_PT,
+ TARGET_DAMAGED_FOR_4_5_PT,
+ TARGET_DAMAGED_FOR_6_PLUS_PT
+} SOUND_EFFECTS;
+
+typedef enum
+{
+ MENU_SOUND_MOVE = 0,
+ MENU_SOUND_SUCCESS,
+ MENU_SOUND_FAILURE,
+ MENU_SOUND_INVOKED,
+} MENU_SOUND_EFFECT;
+
+extern SOUND MenuSounds;
+extern SOUND GameSounds;
+
+/* Constants for DoInput */
+typedef UWORD MENU_SOUND_FLAGS;
+#define MENU_SOUND_UP ((MENU_SOUND_FLAGS)(1 << 0))
+#define MENU_SOUND_DOWN ((MENU_SOUND_FLAGS)(1 << 1))
+#define MENU_SOUND_LEFT ((MENU_SOUND_FLAGS)(1 << 2))
+#define MENU_SOUND_RIGHT ((MENU_SOUND_FLAGS)(1 << 3))
+#define MENU_SOUND_SELECT ((MENU_SOUND_FLAGS)(1 << 4))
+#define MENU_SOUND_CANCEL ((MENU_SOUND_FLAGS)(1 << 5))
+#define MENU_SOUND_SPECIAL ((MENU_SOUND_FLAGS)(1 << 6))
+#define MENU_SOUND_PAGEUP ((MENU_SOUND_FLAGS)(1 << 7))
+#define MENU_SOUND_PAGEDOWN ((MENU_SOUND_FLAGS)(1 << 8))
+#define MENU_SOUND_DELETE ((MENU_SOUND_FLAGS)(1 << 9))
+#define MENU_SOUND_ARROWS (MENU_SOUND_UP | MENU_SOUND_DOWN | MENU_SOUND_LEFT | MENU_SOUND_RIGHT)
+#define MENU_SOUND_NONE ((MENU_SOUND_FLAGS)0)
+
+extern void SetMenuSounds (MENU_SOUND_FLAGS sound_0,
+ MENU_SOUND_FLAGS sound_1);
+extern void GetMenuSounds (MENU_SOUND_FLAGS *sound_0,
+ MENU_SOUND_FLAGS *sound_1);
+
+extern void PlaySound (SOUND S, SoundPosition Pos,
+ ELEMENT *PositionalObject, BYTE Priority);
+extern void PlayMenuSound (MENU_SOUND_EFFECT S);
+extern void ProcessSound (SOUND Sound, ELEMENT *PositionalObject);
+extern SoundPosition CalcSoundPosition (ELEMENT *ElementPtr);
+extern SoundPosition NotPositional (void);
+extern void UpdateSoundPositions (void);
+extern void FlushSounds (void);
+extern void RemoveSoundsForObject (ELEMENT *PosObj);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SOUNDS_H_ */
diff --git a/src/uqm/starbase.c b/src/uqm/starbase.c
new file mode 100644
index 0000000..f119208
--- /dev/null
+++ b/src/uqm/starbase.c
@@ -0,0 +1,602 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "build.h"
+#include "colors.h"
+#include "controls.h"
+#include "starmap.h"
+#include "comm.h"
+#include "gamestr.h"
+#include "save.h"
+#include "starbase.h"
+#include "sis.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/tasklib.h"
+
+
+static void CleanupAfterStarBase (void);
+
+static void
+DrawBaseStateStrings (STARBASE_STATE OldState, STARBASE_STATE NewState)
+{
+ TEXT t;
+ //STRING locString;
+
+ SetContext (ScreenContext);
+ SetContextFont (StarConFont);
+ SetContextForeGroundColor (BLACK_COLOR);
+
+ t.baseline.x = 73 - 4 + SAFE_X;
+ t.align = ALIGN_CENTER;
+
+ if (OldState == (STARBASE_STATE)~0)
+ {
+ t.baseline.y = 106 + 28 + (SAFE_Y + 4);
+ for (OldState = TALK_COMMANDER; OldState < DEPART_BASE; ++OldState)
+ {
+ if (OldState != NewState)
+ {
+ t.pStr = GAME_STRING (STARBASE_STRING_BASE + 1 + OldState);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ }
+ t.baseline.y += (23 - 4);
+ }
+ }
+
+ t.baseline.y = 106 + 28 + (SAFE_Y + 4) + ((23 - 4) * OldState);
+ t.pStr = GAME_STRING (STARBASE_STRING_BASE + 1 + OldState);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E));
+ t.baseline.y = 106 + 28 + (SAFE_Y + 4) + ((23 - 4) * NewState);
+ t.pStr = GAME_STRING (STARBASE_STRING_BASE + 1 + NewState);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+}
+
+void
+DrawShipPiece (FRAME ModuleFrame, COUNT which_piece, COUNT which_slot,
+ BOOLEAN DrawBluePrint)
+{
+ Color OldColor = UNDEFINED_COLOR;
+ // Initialisation is just to keep the compiler silent.
+ RECT r;
+ STAMP Side, Top;
+ SBYTE RepairSlot;
+
+ RepairSlot = 0;
+ switch (which_piece)
+ {
+ case FUSION_THRUSTER:
+ case EMPTY_SLOT + 0:
+ Side.origin.x = DRIVE_SIDE_X;
+ Side.origin.y = DRIVE_SIDE_Y;
+ Top.origin.x = DRIVE_TOP_X;
+ Top.origin.y = DRIVE_TOP_Y;
+ break;
+ case TURNING_JETS:
+ case EMPTY_SLOT + 1:
+ Side.origin.x = JET_SIDE_X;
+ Side.origin.y = JET_SIDE_Y;
+ Top.origin.x = JET_TOP_X;
+ Top.origin.y = JET_TOP_Y;
+ break;
+ default:
+ if (which_piece < EMPTY_SLOT + 2)
+ {
+ RepairSlot = 1;
+ if (which_piece < EMPTY_SLOT
+ && (which_slot == 0
+ || GLOBAL_SIS (ModuleSlots[
+ which_slot - 1
+ ]) < EMPTY_SLOT))
+ ++RepairSlot;
+ }
+ else if (!DrawBluePrint)
+ {
+ if (which_slot == 0 || which_slot >= NUM_MODULE_SLOTS - 3)
+ ++which_piece;
+
+ if (which_slot < NUM_MODULE_SLOTS - 1
+ && GLOBAL_SIS (ModuleSlots[
+ which_slot + 1
+ ]) < EMPTY_SLOT)
+ {
+ RepairSlot = -1;
+ if (which_piece == EMPTY_SLOT + 3
+ || which_slot + 1 == NUM_MODULE_SLOTS - 3)
+ --RepairSlot;
+ }
+ }
+ Side.origin.x = MODULE_SIDE_X;
+ Side.origin.y = MODULE_SIDE_Y;
+ Top.origin.x = MODULE_TOP_X;
+ Top.origin.y = MODULE_TOP_Y;
+ break;
+ }
+
+ Side.origin.x += which_slot * SHIP_PIECE_OFFSET;
+ Side.frame = NULL;
+ if (RepairSlot < 0)
+ {
+ Side.frame = SetAbsFrameIndex (ModuleFrame,
+ ((NUM_MODULES - 1) + (6 - 2)) + (NUM_MODULES + 6)
+ - (RepairSlot + 1));
+ DrawStamp (&Side);
+ }
+ else if (RepairSlot)
+ {
+ r.corner = Side.origin;
+ r.extent.width = SHIP_PIECE_OFFSET;
+ r.extent.height = 1;
+ OldColor = SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+ r.corner.y += 23 - 1;
+ DrawFilledRectangle (&r);
+
+ r.extent.width = 1;
+ r.extent.height = 8;
+ if (RepairSlot == 2)
+ {
+ r.corner = Side.origin;
+ DrawFilledRectangle (&r);
+ r.corner.y += 15;
+ DrawFilledRectangle (&r);
+ }
+ if (which_slot < (NUM_MODULE_SLOTS - 1))
+ {
+ r.corner = Side.origin;
+ r.corner.x += SHIP_PIECE_OFFSET;
+ DrawFilledRectangle (&r);
+ r.corner.y += 15;
+ DrawFilledRectangle (&r);
+ }
+ }
+
+ if (DrawBluePrint)
+ {
+ if (RepairSlot)
+ SetContextForeGroundColor (OldColor);
+ Side.frame = SetAbsFrameIndex (ModuleFrame, which_piece - 1);
+ DrawFilledStamp (&Side);
+ }
+ else
+ {
+ Top.origin.x += which_slot * SHIP_PIECE_OFFSET;
+ if (RepairSlot < 0)
+ {
+ Top.frame = SetRelFrameIndex (Side.frame, -((NUM_MODULES - 1) + 6));
+ DrawStamp (&Top);
+ }
+ else if (RepairSlot)
+ {
+ r.corner = Top.origin;
+ r.extent.width = SHIP_PIECE_OFFSET;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.y += 32 - 1;
+ DrawFilledRectangle (&r);
+
+ r.extent.width = 1;
+ r.extent.height = 12;
+ if (RepairSlot == 2)
+ {
+ r.corner = Top.origin;
+ DrawFilledRectangle (&r);
+ r.corner.y += 20;
+ DrawFilledRectangle (&r);
+ }
+ RepairSlot = (which_slot < NUM_MODULE_SLOTS - 1);
+ if (RepairSlot)
+ {
+ r.corner = Top.origin;
+ r.corner.x += SHIP_PIECE_OFFSET;
+ DrawFilledRectangle (&r);
+ r.corner.y += 20;
+ DrawFilledRectangle (&r);
+ }
+ }
+
+ Top.frame = SetAbsFrameIndex (ModuleFrame, which_piece);
+ DrawStamp (&Top);
+
+ Side.frame = SetRelFrameIndex (Top.frame, (NUM_MODULES - 1) + 6);
+ DrawStamp (&Side);
+
+ if (which_slot == 1 && which_piece == EMPTY_SLOT + 2)
+ {
+ STAMP s;
+
+ s.origin = Top.origin;
+ s.origin.x -= SHIP_PIECE_OFFSET;
+ s.frame = SetAbsFrameIndex (ModuleFrame, NUM_MODULES + 5);
+ DrawStamp (&s);
+ s.origin = Side.origin;
+ s.origin.x -= SHIP_PIECE_OFFSET;
+ s.frame = SetRelFrameIndex (s.frame, (NUM_MODULES - 1) + 6);
+ DrawStamp (&s);
+ }
+
+ if (RepairSlot)
+ {
+ Top.origin.x += SHIP_PIECE_OFFSET;
+ Side.origin.x += SHIP_PIECE_OFFSET;
+ which_piece = GLOBAL_SIS (ModuleSlots[++which_slot]);
+ if (which_piece == EMPTY_SLOT + 2
+ && which_slot >= NUM_MODULE_SLOTS - 3)
+ ++which_piece;
+
+ Top.frame = SetAbsFrameIndex (ModuleFrame, which_piece);
+ DrawStamp (&Top);
+
+ Side.frame = SetRelFrameIndex (Top.frame, (NUM_MODULES - 1) + 6);
+ DrawStamp (&Side);
+ }
+ }
+}
+
+static void
+rotateStarbase (MENU_STATE *pMS, FRAME AniFrame)
+{
+ static TimeCount NextTime = 0;
+ TimeCount Now = GetTimeCounter ();
+
+ if (AniFrame)
+ { // Setup the animation
+ pMS->flash_frame0 = AniFrame;
+ pMS->flash_rect0.corner.x = SAFE_X;
+ pMS->flash_rect0.corner.y = SAFE_Y + 4;
+ }
+
+ if (Now >= NextTime || AniFrame)
+ {
+ STAMP s;
+
+ NextTime = Now + (ONE_SECOND / 20);
+
+ s.origin = pMS->flash_rect0.corner;
+ s.frame = pMS->flash_frame0;
+ DrawStamp (&s);
+
+ s.frame = IncFrameIndex (s.frame);
+ if (GetFrameIndex (s.frame) == 0)
+ { // Do not redraw the base frame, animation loops to frame 1
+ s.frame = IncFrameIndex (s.frame);
+ }
+ pMS->flash_frame0 = s.frame;
+ }
+}
+
+BOOLEAN
+DoStarBase (MENU_STATE *pMS)
+{
+ // XXX: This function is full of hacks and otherwise strange code
+
+ if (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ {
+ pMS->CurState = DEPART_BASE;
+ goto ExitStarBase;
+ }
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ if (!pMS->Initialized)
+ {
+ LastActivity &= ~CHECK_LOAD;
+ pMS->InputFunc = DoStarBase;
+
+ SetFlashRect (NULL);
+
+ if (pMS->hMusic)
+ {
+ StopMusic ();
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+
+ pMS->Initialized = TRUE;
+
+ pMS->CurFrame = CaptureDrawable (LoadGraphic (STARBASE_ANIM));
+ pMS->hMusic = LoadMusic (STARBASE_MUSIC);
+
+ SetContext (ScreenContext);
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+ rotateStarbase (pMS, pMS->CurFrame);
+ DrawBaseStateStrings ((STARBASE_STATE)~0, pMS->CurState);
+ ScreenTransition (3, NULL);
+ PlayMusic (pMS->hMusic, TRUE, 1);
+ UnbatchGraphics ();
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ExitStarBase:
+ DestroyDrawable (ReleaseDrawable (pMS->CurFrame));
+ pMS->CurFrame = 0;
+ StopMusic ();
+ if (pMS->hMusic)
+ {
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+
+ if (pMS->CurState == DEPART_BASE)
+ {
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ SET_GAME_STATE (STARBASE_VISITED, 0);
+ }
+ return (FALSE);
+ }
+
+ pMS->Initialized = FALSE;
+ if (pMS->CurState == TALK_COMMANDER)
+ {
+ FlushInput ();
+ InitCommunication (COMMANDER_CONVERSATION);
+ // XXX: InitCommunication() clears these flags, and we need them
+ // This marks that we are in Starbase.
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, (BYTE)~0);
+ }
+ else
+ {
+ BYTE OldState;
+
+ switch (OldState = pMS->CurState)
+ {
+ case OUTFIT_STARSHIP:
+ pMS->InputFunc = DoOutfit;
+ break;
+ case SHIPYARD:
+ pMS->InputFunc = DoShipyard;
+ break;
+ }
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ DoInput (pMS, TRUE);
+
+ pMS->Initialized = FALSE;
+ pMS->CurState = OldState;
+ pMS->InputFunc = DoStarBase;
+ }
+ }
+ else
+ {
+ STARBASE_STATE NewState;
+
+ NewState = pMS->CurState;
+ if (PulsedInputState.menu[KEY_MENU_LEFT] || PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (NewState-- == TALK_COMMANDER)
+ NewState = DEPART_BASE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT] || PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (NewState++ == DEPART_BASE)
+ NewState = TALK_COMMANDER;
+ }
+
+ BatchGraphics ();
+ SetContext (ScreenContext);
+
+ if (NewState != pMS->CurState)
+ {
+ DrawBaseStateStrings (pMS->CurState, NewState);
+ pMS->CurState = NewState;
+ }
+
+ rotateStarbase (pMS, NULL);
+
+ UnbatchGraphics ();
+
+ SleepThread (ONE_SECOND / 30);
+ }
+
+ return (TRUE);
+}
+
+static void
+DoTimePassage (void)
+{
+#define LOST_DAYS 14
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND * 2));
+ MoveGameClockDays (LOST_DAYS);
+}
+
+void
+VisitStarBase (void)
+{
+ MENU_STATE MenuState;
+ CONTEXT OldContext;
+ StatMsgMode prevMsgMode = SMM_UNDEFINED;
+
+ // XXX: This should probably be moved out to Starcon2Main()
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
+ { // We were just transported by Chmmr to the Starbase
+ // Force a reload of the SolarSys
+ CurStarDescPtr = NULL;
+ // This marks that we are in Starbase.
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, (BYTE)~0);
+ }
+
+ if (!GET_GAME_STATE (STARBASE_AVAILABLE))
+ {
+ HSHIPFRAG hStarShip;
+ SHIP_FRAGMENT *FragPtr;
+
+ // Unallied Starbase conversation
+ SetCommIntroMode (CIM_CROSSFADE_SCREEN, 0);
+ InitCommunication (COMMANDER_CONVERSATION);
+ if (!GET_GAME_STATE (PROBE_ILWRATH_ENCOUNTER)
+ || (GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ CleanupAfterStarBase ();
+ return;
+ }
+
+ /* Create an Ilwrath ship responding to the Ur-Quan
+ * probe's broadcast */
+ hStarShip = CloneShipFragment (ILWRATH_SHIP,
+ &GLOBAL (npc_built_ship_q), 7);
+ FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+ /* Hack (sort of): Suppress the tally and salvage info
+ * after the battle */
+ FragPtr->race_id = (BYTE)~0;
+ UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
+
+ InitCommunication (ILWRATH_CONVERSATION);
+ if (GLOBAL_SIS (CrewEnlisted) == (COUNT)~0
+ || (GLOBAL (CurrentActivity) & CHECK_ABORT))
+ return; // Killed by Ilwrath
+
+ // After Ilwrath battle, about-to-ally Starbase conversation
+ SetCommIntroMode (CIM_CROSSFADE_SCREEN, 0);
+ InitCommunication (COMMANDER_CONVERSATION);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return;
+ // XXX: InitCommunication() clears these flags, and we need them
+ // This marks that we are in Starbase.
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, (BYTE)~0);
+ }
+
+ prevMsgMode = SetStatusMessageMode (SMM_RES_UNITS);
+
+ if (GET_GAME_STATE (MOONBASE_ON_SHIP)
+ || GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
+ { // Go immediately into a conversation with the Commander when the
+ // Starbase becomes available for the first time, or after Chmmr
+ // install the bomb.
+ DoTimePassage ();
+ if (GLOBAL_SIS (CrewEnlisted) == (COUNT)~0)
+ return; // You are now dead! Thank you! (killed by Kohr-Ah)
+
+ SetCommIntroMode (CIM_FADE_IN_SCREEN, ONE_SECOND * 2);
+ InitCommunication (COMMANDER_CONVERSATION);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return;
+ // XXX: InitCommunication() clears these flags, and we need them
+ // This marks that we are in Starbase.
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, (BYTE)~0);
+ }
+
+ memset (&MenuState, 0, sizeof (MenuState));
+ MenuState.InputFunc = DoStarBase;
+
+ OldContext = SetContext (ScreenContext);
+ DoInput (&MenuState, TRUE);
+ SetContext (OldContext);
+
+ SetStatusMessageMode (prevMsgMode);
+ CleanupAfterStarBase ();
+}
+
+static void
+CleanupAfterStarBase (void)
+{
+ if (!(GLOBAL (CurrentActivity) & (CHECK_LOAD | CHECK_ABORT)))
+ {
+ // Mark as not in Starbase anymore
+ SET_GAME_STATE (GLOBAL_FLAGS_AND_DATA, 0);
+ // Fake a load so Starcon2Main takes us to IP
+ GLOBAL (CurrentActivity) = CHECK_LOAD;
+ NextActivity = MAKE_WORD (IN_INTERPLANETARY, 0)
+ | START_INTERPLANETARY;
+ }
+}
+
+void
+InstallBombAtEarth (void)
+{
+ DoTimePassage ();
+
+ SetContext (ScreenContext);
+ SetTransitionSource (NULL);
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+
+ SleepThreadUntil (FadeScreen (FadeAllToColor, 0));
+
+ SET_GAME_STATE (CHMMR_BOMB_STATE, 3); /* bomb processed */
+ GLOBAL (CurrentActivity) = CHECK_LOAD; /* fake a load game */
+ NextActivity = MAKE_WORD (IN_INTERPLANETARY, 0) | START_INTERPLANETARY;
+ CurStarDescPtr = 0; /* force SolarSys reload */
+}
+
+// XXX: Doesn't really belong in this file.
+COUNT
+WrapText (const UNICODE *pStr, COUNT len, TEXT *tarray, SIZE field_width)
+{
+ COUNT num_lines;
+
+ num_lines = 0;
+ do
+ {
+ RECT r;
+ COUNT OldCount;
+
+ tarray->align = ALIGN_LEFT; /* set alignment to something */
+ tarray->pStr = pStr;
+ tarray->CharCount = 1;
+ ++num_lines;
+
+ do
+ {
+ OldCount = tarray->CharCount;
+ while (*++pStr != ' ' && (COUNT)(pStr - tarray->pStr) < len)
+ ;
+ tarray->CharCount = pStr - tarray->pStr;
+ TextRect (tarray, &r, NULL);
+ } while (tarray->CharCount < len && r.extent.width < field_width);
+
+ if (r.extent.width >= field_width)
+ {
+ if ((tarray->CharCount = OldCount) == 1)
+ {
+ do
+ {
+ ++tarray->CharCount;
+ TextRect (tarray, &r, NULL);
+ } while (r.extent.width < field_width);
+ --tarray->CharCount;
+ }
+ }
+
+ pStr = tarray->pStr + tarray->CharCount;
+ len -= tarray->CharCount;
+ ++tarray;
+
+ if (len && (r.extent.width < field_width || OldCount > 1))
+ {
+ ++pStr; /* skip white space */
+ --len;
+ }
+
+ } while (len);
+
+ return (num_lines);
+}
+
diff --git a/src/uqm/starbase.h b/src/uqm/starbase.h
new file mode 100644
index 0000000..0bf5558
--- /dev/null
+++ b/src/uqm/starbase.h
@@ -0,0 +1,55 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_STARBASE_H_
+#define UQM_STARBASE_H_
+
+#include "menustat.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum
+{
+ TALK_COMMANDER = 0,
+ OUTFIT_STARSHIP,
+ SHIPYARD,
+ DEPART_BASE
+};
+typedef BYTE STARBASE_STATE;
+
+
+extern void InstallBombAtEarth (void);
+extern void VisitStarBase (void);
+extern BOOLEAN DoStarBase (MENU_STATE *pMS);
+extern BOOLEAN DoOutfit (MENU_STATE *pMS);
+extern BOOLEAN DoShipyard (MENU_STATE *pMS);
+
+extern void DrawShipPiece (FRAME ModuleFrame, COUNT which_piece, COUNT
+ which_slot, BOOLEAN DrawBluePrint);
+
+extern COUNT WrapText (const UNICODE *pStr, COUNT len, TEXT *tarray, SIZE
+ field_width);
+ // XXX: Doesn't really belong in this file.
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_STARBASE_H_ */
diff --git a/src/uqm/starcon.c b/src/uqm/starcon.c
new file mode 100644
index 0000000..5903e68
--- /dev/null
+++ b/src/uqm/starcon.c
@@ -0,0 +1,323 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+
+#include "comm.h"
+#include "battle.h"
+#include "fmv.h"
+#include "gameev.h"
+#include "types.h"
+#include "globdata.h"
+#include "resinst.h"
+#include "restart.h"
+#include "starbase.h"
+#include "save.h"
+#include "setup.h"
+#include "master.h"
+#include "controls.h"
+#include "starcon.h"
+#include "clock.h"
+ // for GameClockTick()
+#include "hyper.h"
+ // for SeedUniverse()
+#include "planets/planets.h"
+ // for ExploreSolarSys()
+#include "uqmdebug.h"
+#include "libs/tasklib.h"
+#include "libs/log.h"
+#include "libs/gfxlib.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/tfb_draw.h"
+#include "libs/misc.h"
+
+#include "uqmversion.h"
+#include "options.h"
+
+volatile int MainExited = FALSE;
+#ifdef DEBUG_SLEEP
+uint32 mainThreadId;
+extern uint32 SDL_ThreadID(void);
+#endif
+
+// Open or close the periodically occuring QuasiSpace portal.
+// It changes the appearant portal size when necessary.
+static void
+checkArilouGate (void)
+{
+ BYTE counter;
+
+ counter = GET_GAME_STATE (ARILOU_SPACE_COUNTER);
+ if (GET_GAME_STATE (ARILOU_SPACE) == OPENING)
+ { // The portal is opening or fully open
+ if (counter < 9)
+ ++counter;
+ }
+ else
+ { // The portal is closing or fully closed
+ if (counter > 0)
+ --counter;
+ }
+ SET_GAME_STATE (ARILOU_SPACE_COUNTER, counter);
+}
+
+// Battle frame callback function.
+static void
+on_battle_frame (void)
+{
+ GameClockTick ();
+ checkArilouGate ();
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ SeedUniverse ();
+
+ DrawAutoPilotMessage (FALSE);
+}
+
+static void
+BackgroundInitKernel (DWORD TimeOut)
+{
+ LoadMasterShipList (TaskSwitch);
+ TaskSwitch ();
+ InitGameKernel ();
+
+ while ((GetTimeCounter () <= TimeOut) &&
+ !(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ UpdateInputState ();
+ TaskSwitch ();
+ }
+}
+
+// Executes on the main() thread
+void
+SignalStopMainThread (void)
+{
+ GamePaused = FALSE;
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ TaskSwitch ();
+}
+
+// Executes on the main() thread
+void
+ProcessUtilityKeys (void)
+{
+ if (ImmediateInputState.menu[KEY_ABORT])
+ {
+ log_showBox (false, false);
+ exit (EXIT_SUCCESS);
+ }
+
+ if (ImmediateInputState.menu[KEY_FULLSCREEN])
+ {
+ int flags = GfxFlags ^ TFB_GFXFLAGS_FULLSCREEN;
+ // clear ImmediateInputState so we don't repeat this next frame
+ FlushInput ();
+ TFB_DrawScreen_ReinitVideo (GraphicsDriver, flags, ScreenWidthActual,
+ ScreenHeightActual);
+ }
+
+#if defined(DEBUG) || defined(USE_DEBUG_KEY)
+ { // Only call the debug func on the rising edge of
+ // ImmediateInputState[KEY_DEBUG] so it does not execute repeatedly.
+ // This duplicates the PulsedInputState somewhat, but we cannot
+ // use PulsedInputState here because it is meant for another thread.
+ static int debugKeyState;
+
+ if (ImmediateInputState.menu[KEY_DEBUG] && debugKeyState == 0)
+ {
+ debugKeyPressed ();
+ }
+ debugKeyState = ImmediateInputState.menu[KEY_DEBUG];
+ }
+#endif /* DEBUG */
+}
+
+/* TODO: Remove these declarations once threading is gone. */
+extern int snddriver, soundflags;
+
+int
+Starcon2Main (void *threadArg)
+{
+#ifdef DEBUG_SLEEP
+ mainThreadId = SDL_ThreadID();
+#endif
+
+#if CREATE_JOURNAL
+{
+int ac = argc;
+char **av = argv;
+
+while (--ac > 0)
+{
+ ++av;
+ if ((*av)[0] == '-')
+ {
+ switch ((*av)[1])
+ {
+#if CREATE_JOURNAL
+ case 'j':
+ ++create_journal;
+ break;
+#endif //CREATE_JOURNAL
+ }
+ }
+}
+}
+#endif // CREATE_JOURNAL
+
+ {
+ /* TODO: Put initAudio back in main where it belongs once threading
+ * is gone.
+ */
+ extern sint32 initAudio (sint32 driver, sint32 flags);
+ initAudio (snddriver, soundflags);
+ }
+
+ if (!LoadKernel (0,0))
+ {
+ log_add (log_Fatal, "\n *** FATAL ERROR: Could not load basic content ***\n\nUQM requires at least the base content pack to run properly.");
+ log_add (log_Fatal, "This file is typically called uqm-%d.%d.0-content.uqm. UQM was expecting", UQM_MAJOR_VERSION, UQM_MINOR_VERSION);
+ log_add (log_Fatal, "it in the %s/packages directory.", baseContentPath);
+ log_add (log_Fatal, "Either your installation did not install the content pack at all, or it\ninstalled it in a different directory.\n\nFix your installation and rerun UQM.\n\n *******************\n");
+ log_showBox (true, true);
+
+ MainExited = TRUE;
+ return EXIT_FAILURE;
+ }
+ log_add (log_Info, "We've loaded the Kernel");
+
+ GLOBAL (CurrentActivity) = 0;
+ // show splash and init the kernel in the meantime
+ SplashScreen (BackgroundInitKernel);
+
+// OpenJournal ();
+ while (StartGame ())
+ {
+ // Initialise a new game
+ if (!SetPlayerInputAll ()) {
+ log_add (log_Fatal, "Could not set player input.");
+ explode (); // Does not return;
+ }
+ InitGameStructures ();
+ InitGameClock ();
+ AddInitialGameEvents();
+
+ do
+ {
+#ifdef DEBUG
+ if (debugHook != NULL)
+ {
+ void (*saveDebugHook) (void);
+ saveDebugHook = debugHook;
+ debugHook = NULL;
+ // No further debugHook calls unless the called
+ // function resets debugHook.
+ (*saveDebugHook) ();
+ continue;
+ }
+#endif
+ SetStatusMessageMode (SMM_DEFAULT);
+
+ if (!((GLOBAL (CurrentActivity) | NextActivity) & CHECK_LOAD))
+ ZeroVelocityComponents (&GLOBAL (velocity));
+ // not going into talking pet conversation
+ else if (GLOBAL (CurrentActivity) & CHECK_LOAD)
+ GLOBAL (CurrentActivity) = NextActivity;
+
+ if ((GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ || GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
+ {
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) == 2
+ && !GET_GAME_STATE (STARBASE_AVAILABLE))
+ { /* BGD mode */
+ InstallBombAtEarth ();
+ }
+ else if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) == (BYTE)~0
+ || GET_GAME_STATE (CHMMR_BOMB_STATE) == 2)
+ {
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ VisitStarBase ();
+ }
+ else
+ {
+ GLOBAL (CurrentActivity) |= START_ENCOUNTER;
+ RaceCommunication ();
+ }
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ GLOBAL (CurrentActivity) &= ~START_ENCOUNTER;
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY)
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ }
+ }
+ else if (GLOBAL (CurrentActivity) & START_INTERPLANETARY)
+ {
+ GLOBAL (CurrentActivity) = MAKE_WORD (IN_INTERPLANETARY, 0);
+
+ DrawAutoPilotMessage (TRUE);
+ SetGameClockRate (INTERPLANETARY_CLOCK_RATE);
+ ExploreSolarSys ();
+ }
+ else
+ {
+ // Entering HyperSpace or QuasiSpace.
+ GLOBAL (CurrentActivity) = MAKE_WORD (IN_HYPERSPACE, 0);
+
+ DrawAutoPilotMessage (TRUE);
+ SetGameClockRate (HYPERSPACE_CLOCK_RATE);
+ Battle (&on_battle_frame);
+ }
+
+ SetFlashRect (NULL);
+
+ LastActivity = GLOBAL (CurrentActivity);
+
+ if (!(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD))
+ && (LOBYTE (GLOBAL (CurrentActivity)) == WON_LAST_BATTLE
+ // if died for some reason
+ || GLOBAL_SIS (CrewEnlisted) == (COUNT)~0))
+ {
+ if (GET_GAME_STATE (KOHR_AH_KILLED_ALL))
+ InitCommunication (BLACKURQ_CONVERSATION);
+ // surrendered to Ur-Quan
+ else if (GLOBAL (CurrentActivity) & CHECK_RESTART)
+ GLOBAL (CurrentActivity) &= ~CHECK_RESTART;
+ break;
+ }
+ } while (!(GLOBAL (CurrentActivity) & CHECK_ABORT));
+
+ StopSound ();
+ UninitGameClock ();
+ UninitGameStructures ();
+ ClearPlayerInputAll ();
+ }
+// CloseJournal ();
+
+ UninitGameKernel ();
+ FreeMasterShipList ();
+ FreeKernel ();
+
+ log_showBox (false, false);
+ MainExited = TRUE;
+
+ (void) threadArg; /* Satisfying compiler (unused parameter) */
+ return 0;
+}
+
diff --git a/src/uqm/starcon.h b/src/uqm/starcon.h
new file mode 100644
index 0000000..a8f6528
--- /dev/null
+++ b/src/uqm/starcon.h
@@ -0,0 +1,35 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_STARCON_H_
+#define UQM_STARCON_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern volatile int MainExited;
+extern void SignalStopMainThread (void);
+extern void ProcessUtilityKeys (void);
+
+extern int Starcon2Main (void *threadArg);
+extern void FreeGameData (void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_STARCON_H_ */
diff --git a/src/uqm/starmap.c b/src/uqm/starmap.c
new file mode 100644
index 0000000..a209917
--- /dev/null
+++ b/src/uqm/starmap.c
@@ -0,0 +1,125 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "starmap.h"
+#include "gamestr.h"
+#include "globdata.h"
+#include "libs/gfxlib.h"
+
+
+STAR_DESC *star_array;
+STAR_DESC *CurStarDescPtr = 0;
+
+STAR_DESC*
+FindStar (STAR_DESC *LastSDPtr, POINT *puniverse, SIZE xbounds,
+ SIZE ybounds)
+{
+ COORD min_y, max_y;
+ SIZE lo, hi;
+ STAR_DESC *BaseSDPtr;
+
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ BaseSDPtr = star_array;
+ hi = NUM_SOLAR_SYSTEMS - 1;
+ }
+ else
+ {
+#define NUM_HYPER_VORTICES 15
+ BaseSDPtr = &star_array[NUM_SOLAR_SYSTEMS + 1];
+ hi = (NUM_HYPER_VORTICES + 1) - 1;
+ }
+
+ if (LastSDPtr == NULL)
+ lo = 0;
+ else if ((lo = LastSDPtr - BaseSDPtr + 1) > hi)
+ return (0);
+ else
+ hi = lo;
+
+ if (ybounds <= 0)
+ min_y = max_y = puniverse->y;
+ else
+ {
+ min_y = puniverse->y - ybounds;
+ max_y = puniverse->y + ybounds;
+ }
+
+ while (lo < hi)
+ {
+ SIZE mid;
+
+ mid = (lo + hi) >> 1;
+ if (BaseSDPtr[mid].star_pt.y >= min_y)
+ hi = mid - 1;
+ else
+ lo = mid + 1;
+ }
+
+ LastSDPtr = &BaseSDPtr[lo];
+ if (ybounds < 0 || LastSDPtr->star_pt.y <= max_y)
+ {
+ COORD min_x, max_x;
+
+ if (xbounds <= 0)
+ min_x = max_x = puniverse->x;
+ else
+ {
+ min_x = puniverse->x - xbounds;
+ max_x = puniverse->x + xbounds;
+ }
+
+ do
+ {
+ if ((ybounds < 0 || LastSDPtr->star_pt.y >= min_y)
+ && (xbounds < 0
+ || (LastSDPtr->star_pt.x >= min_x
+ && LastSDPtr->star_pt.x <= max_x))
+ )
+ return (LastSDPtr);
+ } while ((++LastSDPtr)->star_pt.y <= max_y);
+ }
+
+ return (0);
+}
+
+void
+GetClusterName (const STAR_DESC *pSD, UNICODE buf[])
+{
+ UNICODE *pBuf, *pStr;
+
+ pBuf = buf;
+ if (pSD->Prefix)
+ {
+ pStr = GAME_STRING (STAR_NUMBER_BASE + pSD->Prefix - 1);
+ if (pStr)
+ {
+ while ((*pBuf++ = *pStr++))
+ ;
+ pBuf[-1] = ' ';
+ }
+ }
+ if ((pStr = GAME_STRING (pSD->Postfix)) == 0)
+ *pBuf = '\0';
+ else
+ {
+ while ((*pBuf++ = *pStr++))
+ ;
+ }
+}
+
diff --git a/src/uqm/starmap.h b/src/uqm/starmap.h
new file mode 100644
index 0000000..0161830
--- /dev/null
+++ b/src/uqm/starmap.h
@@ -0,0 +1,42 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef STARMAP_H_INCL_
+#define STARMAP_H_INCL_
+
+#include "libs/compiler.h"
+#include "planets/planets.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern STAR_DESC *CurStarDescPtr;
+extern STAR_DESC *star_array;
+
+#define NUM_SOLAR_SYSTEMS 502
+
+extern STAR_DESC* FindStar (STAR_DESC *pLastStar, POINT *puniverse,
+ SIZE xbounds, SIZE ybounds);
+
+extern void GetClusterName (const STAR_DESC *pSD, UNICODE buf[]);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* STARMAP_H_INCL_ */
+
diff --git a/src/uqm/state.c b/src/uqm/state.c
new file mode 100644
index 0000000..3358b32
--- /dev/null
+++ b/src/uqm/state.c
@@ -0,0 +1,354 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "state.h"
+
+#include "starmap.h"
+#include "libs/memlib.h"
+#include "libs/log.h"
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <memory.h>
+
+// in-memory file i/o
+struct GAME_STATE_FILE
+{
+ const char *symname;
+ DWORD size_hint;
+ int open_count;
+ BYTE *data;
+ DWORD used;
+ DWORD size;
+ DWORD ptr;
+};
+#define STATE_FILE_ITRAILER 0, 0, 0, 0, 0
+
+#define NUM_STATE_FILES 3
+
+static GAME_STATE_FILE state_files[NUM_STATE_FILES] =
+{
+ {"STARINFO", STAR_BUFSIZE, STATE_FILE_ITRAILER},
+ {"RANDGRPINFO", RAND_BUFSIZE, STATE_FILE_ITRAILER},
+ {"DEFGRPINFO", DEF_BUFSIZE, STATE_FILE_ITRAILER}
+};
+
+
+GAME_STATE_FILE *
+OpenStateFile (int stateFile, const char *mode)
+{
+ GAME_STATE_FILE *fp;
+
+ if (stateFile < 0 || stateFile >= NUM_STATE_FILES)
+ return NULL;
+
+ fp = &state_files[stateFile];
+ fp->open_count++;
+ if (fp->open_count > 1)
+ log_add (log_Warning, "WARNING: "
+ "State file %s open count is %d after open()",
+ fp->symname, fp->open_count);
+
+ if (!fp->data)
+ {
+ fp->data = HMalloc (fp->size_hint);
+ if (!fp->data)
+ return NULL;
+ fp->size = fp->size_hint;
+ }
+
+ // we allow reading and writing for any open mode
+ // but the mode determines what happens to the file contents
+ if (mode[0] == 'w')
+ { // blow the file away
+ fp->used = 0;
+#ifdef DEBUG
+ // paint buffer for tracking writes
+ memset (fp->data, 0xCC, fp->size);
+#endif
+ }
+ else if (mode[0] == 'r')
+ { // nothing
+ }
+ else
+ {
+ log_add (log_Warning, "WARNING: "
+ "State file %s opened with unsupported mode '%s'",
+ fp->symname, mode);
+ }
+ fp->ptr = 0;
+
+ return fp;
+}
+
+void
+CloseStateFile (GAME_STATE_FILE *fp)
+{
+ fp->ptr = 0;
+ fp->open_count--;
+ if (fp->open_count < 0)
+ log_add (log_Warning, "WARNING: "
+ "State file %s open count is %d after close()",
+ fp->symname, fp->open_count);
+ // Erm, Ok, it's closed! Honest!
+}
+
+void
+DeleteStateFile (int stateFile)
+{
+ GAME_STATE_FILE *fp;
+
+ if (stateFile < 0 || stateFile >= NUM_STATE_FILES)
+ return;
+
+ fp = &state_files[stateFile];
+ if (fp->open_count != 0)
+ log_add (log_Warning, "WARNING: "
+ "State file %s open count is %d during delete()",
+ fp->symname, fp->open_count);
+
+ fp->used = 0;
+ fp->ptr = 0;
+ HFree (fp->data);
+ fp->data = 0;
+}
+
+DWORD
+LengthStateFile (GAME_STATE_FILE *fp)
+{
+ return fp->used;
+}
+
+int
+ReadStateFile (void *lpBuf, COUNT size, COUNT count, GAME_STATE_FILE *fp)
+{
+ DWORD bytes = size * count;
+
+ if (fp->ptr >= fp->size)
+ { // EOF
+ return 0;
+ }
+ else if (fp->ptr + bytes > fp->size)
+ { // dont have that much data
+ bytes = fp->size - fp->ptr;
+ bytes -= bytes % size;
+ }
+
+ if (bytes > 0)
+ {
+ memcpy (lpBuf, fp->data + fp->ptr, bytes);
+ fp->ptr += bytes;
+ }
+ return (bytes / size);
+}
+
+int
+WriteStateFile (const void *lpBuf, COUNT size, COUNT count, GAME_STATE_FILE *fp)
+{
+ DWORD bytes = size * count;
+
+ if (fp->ptr + bytes > fp->size)
+ { // dont have that much space available
+ DWORD newsize = fp->ptr + bytes;
+ // grab more space in advance
+ if (newsize < fp->size * 3 / 2)
+ newsize = fp->size * 3 / 2;
+
+ fp->data = HRealloc (fp->data, newsize);
+ if (!fp->data)
+ return 0;
+
+ fp->size = newsize;
+ if (newsize > fp->size_hint)
+ fp->size_hint = newsize;
+ }
+
+ if (bytes > 0)
+ {
+ memcpy (fp->data + fp->ptr, lpBuf, bytes);
+ fp->ptr += bytes;
+ if (fp->ptr > fp->used)
+ fp->used = fp->ptr;
+ }
+ return (bytes / size);
+}
+
+int
+SeekStateFile (GAME_STATE_FILE *fp, long offset, int whence)
+{
+ if (whence == SEEK_CUR)
+ offset += fp->ptr;
+ else if (whence == SEEK_END)
+ offset += fp->used;
+
+ if (offset < 0)
+ {
+ fp->ptr = 0;
+ return 0;
+ }
+ fp->ptr = offset;
+ return 1;
+}
+
+
+void
+InitPlanetInfo (void)
+{
+ GAME_STATE_FILE *fp;
+
+ fp = OpenStateFile (STARINFO_FILE, "wb");
+ if (fp)
+ {
+ STAR_DESC *pSD;
+
+ // Set record offsets for all stars to 0 (not present)
+ pSD = &star_array[0];
+ do
+ {
+ swrite_32 (fp, 0);
+ ++pSD;
+ } while (pSD->star_pt.x <= MAX_X_UNIVERSE
+ && pSD->star_pt.y <= MAX_Y_UNIVERSE);
+
+ CloseStateFile (fp);
+ }
+}
+
+void
+UninitPlanetInfo (void)
+{
+ DeleteStateFile (STARINFO_FILE);
+}
+
+#define OFFSET_SIZE (sizeof (DWORD))
+#define SCAN_RECORD_SIZE (sizeof (DWORD) * NUM_SCAN_TYPES)
+
+void
+GetPlanetInfo (void)
+{
+ GAME_STATE_FILE *fp;
+
+ pSolarSysState->SysInfo.PlanetInfo.ScanRetrieveMask[BIOLOGICAL_SCAN] = 0;
+ pSolarSysState->SysInfo.PlanetInfo.ScanRetrieveMask[MINERAL_SCAN] = 0;
+ pSolarSysState->SysInfo.PlanetInfo.ScanRetrieveMask[ENERGY_SCAN] = 0;
+
+ fp = OpenStateFile (STARINFO_FILE, "rb");
+ if (fp)
+ {
+ COUNT star_index, planet_index, moon_index;
+ DWORD offset;
+
+ star_index = (COUNT)(CurStarDescPtr - star_array);
+ planet_index = (COUNT)(pSolarSysState->pBaseDesc->pPrevDesc
+ - pSolarSysState->PlanetDesc);
+ if (pSolarSysState->pOrbitalDesc->pPrevDesc == pSolarSysState->SunDesc)
+ moon_index = 0;
+ else
+ moon_index = (COUNT)(pSolarSysState->pOrbitalDesc
+ - pSolarSysState->MoonDesc + 1);
+
+ SeekStateFile (fp, star_index * OFFSET_SIZE, SEEK_SET);
+ sread_32 (fp, &offset);
+
+ if (offset)
+ {
+ COUNT i;
+
+ // Skip scan records for all preceeding planets to the one we need
+ for (i = 0; i < planet_index; ++i)
+ offset += (pSolarSysState->PlanetDesc[i].NumPlanets + 1) *
+ SCAN_RECORD_SIZE;
+
+ // Skip scan records for all preceeding moons to the one we need
+ offset += moon_index * SCAN_RECORD_SIZE;
+
+ SeekStateFile (fp, offset, SEEK_SET);
+ sread_a32 (fp, pSolarSysState->SysInfo.PlanetInfo.ScanRetrieveMask,
+ NUM_SCAN_TYPES);
+ }
+
+ CloseStateFile (fp);
+ }
+}
+
+void
+PutPlanetInfo (void)
+{
+ GAME_STATE_FILE *fp;
+
+ fp = OpenStateFile (STARINFO_FILE, "r+b");
+ if (fp)
+ {
+ COUNT i;
+ COUNT star_index, planet_index, moon_index;
+ DWORD offset;
+
+ star_index = (COUNT)(CurStarDescPtr - star_array);
+ planet_index = (COUNT)(pSolarSysState->pBaseDesc->pPrevDesc
+ - pSolarSysState->PlanetDesc);
+ if (pSolarSysState->pOrbitalDesc->pPrevDesc == pSolarSysState->SunDesc)
+ moon_index = 0;
+ else
+ moon_index = (COUNT)(pSolarSysState->pOrbitalDesc
+ - pSolarSysState->MoonDesc + 1);
+
+ SeekStateFile (fp, star_index * OFFSET_SIZE, SEEK_SET);
+ sread_32 (fp, &offset);
+
+ if (offset == 0)
+ { // Scan record not present yet -- init it
+ DWORD ScanRetrieveMask[NUM_SCAN_TYPES] =
+ {
+ 0, 0, 0,
+ };
+
+ offset = LengthStateFile (fp);
+
+ // Write the record offset
+ SeekStateFile (fp, star_index * OFFSET_SIZE, SEEK_SET);
+ swrite_32 (fp, offset);
+
+ // Init scan records for all planets and moons in the system
+ SeekStateFile (fp, offset, SEEK_SET);
+ for (i = 0; i < pSolarSysState->SunDesc[0].NumPlanets; ++i)
+ {
+ COUNT j;
+
+ swrite_a32 (fp, ScanRetrieveMask, NUM_SCAN_TYPES);
+ // init moons
+ for (j = 0; j < pSolarSysState->PlanetDesc[i].NumPlanets; ++j)
+ swrite_a32 (fp, ScanRetrieveMask, NUM_SCAN_TYPES);
+ }
+ }
+
+ // Skip scan records for all preceeding planets to the one we need
+ for (i = 0; i < planet_index; ++i)
+ offset += (pSolarSysState->PlanetDesc[i].NumPlanets + 1) *
+ SCAN_RECORD_SIZE;
+
+ // Skip scan records for all preceeding moons to the one we need
+ offset += moon_index * SCAN_RECORD_SIZE;
+
+ SeekStateFile (fp, offset, SEEK_SET);
+ swrite_a32 (fp, pSolarSysState->SysInfo.PlanetInfo.ScanRetrieveMask,
+ NUM_SCAN_TYPES);
+
+ CloseStateFile (fp);
+ }
+}
+
diff --git a/src/uqm/state.h b/src/uqm/state.h
new file mode 100644
index 0000000..e469cba
--- /dev/null
+++ b/src/uqm/state.h
@@ -0,0 +1,166 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_STATE_H_
+#define UQM_STATE_H_
+
+#include "port.h"
+#include "libs/compiler.h"
+#include <assert.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern void InitPlanetInfo (void);
+extern void UninitPlanetInfo (void);
+extern void GetPlanetInfo (void);
+extern void PutPlanetInfo (void);
+
+extern void InitGroupInfo (BOOLEAN FirstTime);
+extern void UninitGroupInfo (void);
+extern BOOLEAN GetGroupInfo (DWORD offset, BYTE which_group);
+extern DWORD PutGroupInfo (DWORD offset, BYTE which_group);
+#define GROUPS_RANDOM ((DWORD)(0L))
+#define GROUPS_ADD_NEW ((DWORD)(~0L))
+#define GROUP_LIST ((BYTE)0)
+#define GROUP_INIT_IP ((BYTE)~0)
+ // Initialize IP group list (ip_group_q) from the actual groups
+ // (not GROUP_LIST) in one of the state files
+#define GROUP_LOAD_IP GROUP_LIST
+ // Read IP group list into ip_group_q from the list entry
+ // (GROUP_LIST) in one of the state files
+#define GROUP_SAVE_IP ((BYTE)~0)
+ // Write IP group list from ip_group_q to the list entry
+ // (GROUP_LIST) in one of the state files
+extern void BuildGroups (void);
+
+typedef struct GAME_STATE_FILE GAME_STATE_FILE;
+
+#define STARINFO_FILE 0
+ //"starinfo.dat"
+#define STAR_BUFSIZE (NUM_SOLAR_SYSTEMS * sizeof (DWORD) \
+ + 3800 * (3 * sizeof (DWORD)))
+#define RANDGRPINFO_FILE 1
+ //"randgrp.dat"
+#define RAND_BUFSIZE (4 * 1024)
+#define DEFGRPINFO_FILE 2
+ //"defgrp.dat"
+#define DEF_BUFSIZE (10 * 1024)
+
+typedef enum
+{
+ STARINFO,
+ RANDGRPINFO,
+ DEFGRPINFO
+} INFO_TYPE;
+
+GAME_STATE_FILE* OpenStateFile (int stateFile, const char *mode);
+void CloseStateFile (GAME_STATE_FILE *fp);
+void DeleteStateFile (int stateFile);
+DWORD LengthStateFile (GAME_STATE_FILE *fp);
+int ReadStateFile (void *lpBuf, COUNT size, COUNT count, GAME_STATE_FILE *fp);
+int WriteStateFile (const void *lpBuf, COUNT size, COUNT count, GAME_STATE_FILE *fp);
+int SeekStateFile (GAME_STATE_FILE *fp, long offset, int whence);
+
+static inline COUNT
+sread_8 (GAME_STATE_FILE *fp, BYTE *v)
+{
+ BYTE t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return ReadStateFile (v, 1, 1, fp);
+}
+
+static inline COUNT
+sread_16 (GAME_STATE_FILE *fp, UWORD *v)
+{
+ UWORD t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return ReadStateFile (v, 2, 1, fp);
+}
+
+static inline COUNT
+sread_16s (GAME_STATE_FILE *fp, SWORD *v)
+{
+ UWORD t;
+ COUNT ret;
+ ret = sread_16 (fp, &t);
+ // unsigned to signed conversion
+ if (v)
+ *v = t;
+ return ret;
+}
+
+static inline COUNT
+sread_32 (GAME_STATE_FILE *fp, DWORD *v)
+{
+ DWORD t;
+ if (!v) /* read value ignored */
+ v = &t;
+ return ReadStateFile (v, 4, 1, fp);
+}
+
+static inline COUNT
+sread_a32 (GAME_STATE_FILE *fp, DWORD *ar, COUNT count)
+{
+ assert (ar != NULL);
+
+ for ( ; count > 0; --count, ++ar)
+ {
+ if (sread_32 (fp, ar) != 1)
+ return 0;
+ }
+ return 1;
+}
+
+static inline COUNT
+swrite_8 (GAME_STATE_FILE *fp, BYTE v)
+{
+ return WriteStateFile (&v, 1, 1, fp);
+}
+
+static inline COUNT
+swrite_16 (GAME_STATE_FILE *fp, UWORD v)
+{
+ return WriteStateFile (&v, 2, 1, fp);
+}
+
+static inline COUNT
+swrite_32 (GAME_STATE_FILE *fp, DWORD v)
+{
+ return WriteStateFile (&v, 4, 1, fp);
+}
+
+static inline COUNT
+swrite_a32 (GAME_STATE_FILE *fp, const DWORD *ar, COUNT count)
+{
+ for ( ; count > 0; --count, ++ar)
+ {
+ if (swrite_32 (fp, *ar) != 1)
+ return 0;
+ }
+ return 1;
+}
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_STATE_H_ */
diff --git a/src/uqm/status.c b/src/uqm/status.c
new file mode 100644
index 0000000..6a588a8
--- /dev/null
+++ b/src/uqm/status.c
@@ -0,0 +1,582 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "status.h"
+#include "colors.h"
+#include "globdata.h"
+#include "races.h"
+#include "ship.h"
+#include "setup.h"
+#include "options.h"
+#include "init.h"
+ // for NUM_PLAYERS
+
+#include <stdio.h>
+#include <string.h>
+
+
+COORD status_y_offsets[NUM_PLAYERS];
+
+
+void
+InitStatusOffsets (void)
+{
+ // XXX: We have to jump through these hoops because GOOD_GUY_YOFFS is
+ // not a constant, contrary to what its name suggests.
+ status_y_offsets[0] = GOOD_GUY_YOFFS; // bottom player
+ status_y_offsets[1] = BAD_GUY_YOFFS; // top player
+}
+
+static void
+CaptainsWindow (CAPTAIN_STUFF *CSPtr, COORD y,
+ STATUS_FLAGS delta_status_flags, STATUS_FLAGS cur_status_flags,
+ COUNT Pass)
+{
+ STAMP Stamp;
+
+ Stamp.origin.x = CAPTAIN_XOFFS;
+ Stamp.origin.y = y + CAPTAIN_YOFFS;
+
+ if (delta_status_flags & LEFT)
+ {
+ Stamp.frame = CSPtr->turn;
+ if (!(delta_status_flags & RIGHT))
+ {
+ Stamp.frame = SetRelFrameIndex (Stamp.frame, 3);
+ if (Pass == 2)
+ {
+ if (cur_status_flags & LEFT)
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ else
+ Stamp.frame = DecFrameIndex (Stamp.frame);
+ }
+ }
+ else if (cur_status_flags & RIGHT)
+ {
+ if (Pass == 1)
+ Stamp.frame = SetRelFrameIndex (Stamp.frame, 3);
+ else
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ DrawStamp (&Stamp);
+ Stamp.frame = DecFrameIndex (Stamp.frame);
+ }
+ else
+ {
+ if (Pass == 1)
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ else
+ Stamp.frame = SetRelFrameIndex (Stamp.frame, 3);
+ DrawStamp (&Stamp);
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ }
+ DrawStamp (&Stamp);
+ }
+ else if (delta_status_flags & RIGHT)
+ {
+ Stamp.frame = CSPtr->turn;
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ if (Pass == 2)
+ {
+ if (cur_status_flags & RIGHT)
+ Stamp.frame = DecFrameIndex (Stamp.frame);
+ else
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ }
+ DrawStamp (&Stamp);
+ }
+
+ if (delta_status_flags & THRUST)
+ {
+ Stamp.frame = CSPtr->thrust;
+ if (Pass == 1)
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ else if (cur_status_flags & THRUST)
+ Stamp.frame = SetRelFrameIndex (Stamp.frame, 2);
+ DrawStamp (&Stamp);
+ }
+ if (delta_status_flags & WEAPON)
+ {
+ Stamp.frame = CSPtr->weapon;
+ if (Pass == 1)
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ else if (cur_status_flags & WEAPON)
+ Stamp.frame = SetRelFrameIndex (Stamp.frame, 2);
+ DrawStamp (&Stamp);
+ }
+ if (delta_status_flags & SPECIAL)
+ {
+ Stamp.frame = CSPtr->special;
+ if (Pass == 1)
+ Stamp.frame = IncFrameIndex (Stamp.frame);
+ else if (cur_status_flags & SPECIAL)
+ Stamp.frame = SetRelFrameIndex (Stamp.frame, 2);
+ DrawStamp (&Stamp);
+ }
+}
+
+void
+DrawBattleCrewAmount (SHIP_INFO *ShipInfoPtr, COORD y_offs)
+{
+#define MAX_CREW_DIGITS 3
+ RECT r;
+ TEXT t;
+ UNICODE buf[40];
+
+ t.baseline.x = BATTLE_CREW_X + 2;
+ if (optWhichMenu == OPT_PC)
+ t.baseline.x -= 8;
+ t.baseline.y = BATTLE_CREW_Y + y_offs;
+ t.align = ALIGN_LEFT;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+
+ r.corner.x = t.baseline.x;
+ r.corner.y = t.baseline.y - 5;
+ r.extent.width = 6 * MAX_CREW_DIGITS + 6;
+ r.extent.height = 5;
+
+ sprintf (buf, "%u", ShipInfoPtr->crew_level);
+ SetContextFont (StarConFont);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ DrawFilledRectangle (&r);
+ SetContextForeGroundColor (BLACK_COLOR);
+ font_DrawText (&t);
+}
+
+void
+DrawCaptainsWindow (STARSHIP *StarShipPtr)
+{
+ COORD y;
+ COORD y_offs;
+ RECT r;
+ STAMP s;
+ FRAME Frame;
+ RACE_DESC *RDPtr;
+
+ RDPtr = StarShipPtr->RaceDescPtr;
+ Frame = RDPtr->ship_data.captain_control.background;
+ if (Frame)
+ {
+ Frame = SetAbsFrameIndex (Frame, 0);
+ RDPtr->ship_data.captain_control.background = Frame;
+ Frame = SetRelFrameIndex (Frame, 1);
+ RDPtr->ship_data.captain_control.turn = Frame;
+ Frame = SetRelFrameIndex (Frame, 5);
+ RDPtr->ship_data.captain_control.thrust = Frame;
+ Frame = SetRelFrameIndex (Frame, 3);
+ RDPtr->ship_data.captain_control.weapon = Frame;
+ Frame = SetRelFrameIndex (Frame, 3);
+ RDPtr->ship_data.captain_control.special = Frame;
+ }
+
+ BatchGraphics ();
+
+ assert (StarShipPtr->playerNr >= 0);
+ y_offs = status_y_offsets[StarShipPtr->playerNr];
+
+ r.corner.x = CAPTAIN_XOFFS - 2;
+ r.corner.y = y_offs + SHIP_INFO_HEIGHT;
+ r.extent.width = STATUS_WIDTH - CAPTAIN_XOFFS;
+ r.extent.height = SHIP_STATUS_HEIGHT - CAPTAIN_YOFFS + 2;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F));
+ r.corner.x = 1;
+ r.corner.y = y_offs + SHIP_INFO_HEIGHT;
+ r.extent.width = 1;
+ r.extent.height = (SHIP_STATUS_HEIGHT - SHIP_INFO_HEIGHT - 2);
+ DrawFilledRectangle (&r);
+ r.corner.x = 0;
+ ++r.extent.height;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19));
+ r.corner.x = STATUS_WIDTH - 1;
+ r.corner.y = y_offs + SHIP_INFO_HEIGHT;
+ r.extent.width = 1;
+ r.extent.height = SHIP_STATUS_HEIGHT - SHIP_INFO_HEIGHT;
+ DrawFilledRectangle (&r);
+ r.corner.x = STATUS_WIDTH - 2;
+ DrawFilledRectangle (&r);
+ r.corner.x = 1;
+ r.extent.width = STATUS_WIDTH - 2;
+ r.corner.y = y_offs + (SHIP_STATUS_HEIGHT - 2);
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = 0;
+ ++r.extent.width;
+ ++r.corner.y;
+ DrawFilledRectangle (&r);
+
+ y = y_offs + CAPTAIN_YOFFS;
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F));
+ r.corner.x = 59;
+ r.corner.y = y;
+ r.extent.width = 1;
+ r.extent.height = 30;
+ DrawFilledRectangle (&r);
+ r.corner.x = 3;
+ r.corner.y += 30;
+ r.extent.width = 57;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19));
+ r.corner.x = 3;
+ r.extent.width = 57;
+ r.corner.y = y - 1;
+ r.extent.height = 1;
+ DrawFilledRectangle (&r);
+ r.corner.x = 3;
+ r.extent.width = 1;
+ r.corner.y = y;
+ r.extent.height = 30;
+ DrawFilledRectangle (&r);
+
+ s.frame = RDPtr->ship_data.captain_control.background;
+ s.origin.x = CAPTAIN_XOFFS;
+ s.origin.y = y;
+ DrawStamp (&s);
+
+ if (StarShipPtr->captains_name_index == 0
+ && StarShipPtr->playerNr == RPG_PLAYER_NUM)
+ { // This is SIS
+ TEXT t;
+
+ t.baseline.x = STATUS_WIDTH >> 1;
+ t.baseline.y = y + 6;
+ t.align = ALIGN_CENTER;
+ t.pStr = GLOBAL_SIS (CommanderName);
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02));
+ SetContextFont (TinyFont);
+ font_DrawText (&t);
+ }
+ if (RDPtr->ship_info.max_crew > MAX_CREW_SIZE ||
+ RDPtr->ship_info.ship_flags & PLAYER_CAPTAIN)
+ {
+ // All crew doesn't fit in the graphics; print a number.
+ // Always print a number for the SIS in the full game.
+ DrawBattleCrewAmount (&RDPtr->ship_info, y_offs);
+ }
+
+ UnbatchGraphics ();
+}
+
+BOOLEAN
+DeltaEnergy (ELEMENT *ElementPtr, SIZE energy_delta)
+{
+ BOOLEAN retval;
+ STARSHIP *StarShipPtr;
+ SHIP_INFO *ShipInfoPtr;
+
+ retval = TRUE;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ ShipInfoPtr = &StarShipPtr->RaceDescPtr->ship_info;
+ if (energy_delta >= 0)
+ {
+ if ((BYTE)(ShipInfoPtr->energy_level + (BYTE)energy_delta) >
+ ShipInfoPtr->max_energy)
+ energy_delta = ShipInfoPtr->max_energy
+ - ShipInfoPtr->energy_level;
+ }
+ else
+ {
+ if ((BYTE)-energy_delta > ShipInfoPtr->energy_level)
+ {
+ retval = FALSE;
+ }
+ }
+
+ if (!retval)
+ StarShipPtr->cur_status_flags |= LOW_ON_ENERGY;
+ else
+ {
+ StarShipPtr->cur_status_flags &= ~LOW_ON_ENERGY;
+ StarShipPtr->energy_counter =
+ StarShipPtr->RaceDescPtr->characteristics.energy_wait;
+
+ DeltaStatistics (ShipInfoPtr, status_y_offsets[StarShipPtr->playerNr],
+ 0, energy_delta);
+ }
+
+ return (retval);
+}
+
+BOOLEAN
+DeltaCrew (ELEMENT *ElementPtr, SIZE crew_delta)
+{
+ BOOLEAN retval;
+ STARSHIP *StarShipPtr;
+ SHIP_INFO *ShipInfoPtr;
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE
+ && ElementPtr->playerNr == NPC_PLAYER_NUM)
+ return (TRUE); /* Samatra can't be crew-modified */
+
+ retval = TRUE;
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ ShipInfoPtr = &StarShipPtr->RaceDescPtr->ship_info;
+ if (crew_delta > 0)
+ {
+ ElementPtr->crew_level += crew_delta;
+ if (ElementPtr->crew_level > ShipInfoPtr->max_crew)
+ {
+ crew_delta = ShipInfoPtr->max_crew - ShipInfoPtr->crew_level;
+ ElementPtr->crew_level = ShipInfoPtr->max_crew;
+ }
+ }
+ else if (crew_delta < 0)
+ {
+ if (ElementPtr->crew_level > (COUNT)-crew_delta)
+ ElementPtr->crew_level += crew_delta;
+ else
+ {
+ crew_delta = -(SIZE)ElementPtr->crew_level;
+ ElementPtr->crew_level = 0;
+ retval = FALSE;
+ }
+ }
+
+ DeltaStatistics (ShipInfoPtr, status_y_offsets[StarShipPtr->playerNr],
+ crew_delta, 0);
+
+ return (retval);
+}
+
+void
+PreProcessStatus (ELEMENT *ShipPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->captains_name_index
+ || StarShipPtr->playerNr == RPG_PLAYER_NUM)
+ { // All except Sa-Matra, no captain's window there
+ STATUS_FLAGS old_status_flags, cur_status_flags;
+ CAPTAIN_STUFF *CSPtr;
+
+ cur_status_flags = StarShipPtr->cur_status_flags;
+ old_status_flags = StarShipPtr->old_status_flags;
+ old_status_flags ^= cur_status_flags;
+
+ CSPtr = &StarShipPtr->RaceDescPtr->ship_data.captain_control;
+ old_status_flags &= (LEFT | RIGHT | THRUST | WEAPON | SPECIAL);
+ if (old_status_flags)
+ {
+ assert (StarShipPtr->playerNr >= 0);
+ CaptainsWindow (CSPtr, status_y_offsets[StarShipPtr->playerNr],
+ old_status_flags, cur_status_flags, 1);
+ }
+ }
+}
+
+void
+PostProcessStatus (ELEMENT *ShipPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+ if (StarShipPtr->captains_name_index
+ || StarShipPtr->playerNr == RPG_PLAYER_NUM)
+ { // All except Sa-Matra, no captain's window there
+ COORD y;
+ STATUS_FLAGS cur_status_flags, old_status_flags;
+
+ cur_status_flags = StarShipPtr->cur_status_flags;
+
+ assert (StarShipPtr->playerNr >= 0);
+ y = status_y_offsets[StarShipPtr->playerNr];
+
+ if (ShipPtr->crew_level == 0)
+ {
+ StarShipPtr->cur_status_flags &=
+ ~(LEFT | RIGHT | THRUST | WEAPON | SPECIAL);
+
+ if (StarShipPtr->RaceDescPtr->ship_info.crew_level == 0)
+ {
+ BYTE i;
+ Color c;
+ RECT r;
+
+ i = (BYTE)(NUM_EXPLOSION_FRAMES * 3 - 1) - ShipPtr->life_span;
+ if (i <= 4)
+ {
+ static const Color flash_tab0[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x19, 0x19), 0x24),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1C, 0x00), 0x78),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1F, 0x1F), 0x0F),
+ };
+
+ c = flash_tab0[i];
+ r.corner.x = CAPTAIN_XOFFS;
+ r.corner.y = y + CAPTAIN_YOFFS;
+ r.extent.width = CAPTAIN_WIDTH;
+ r.extent.height = CAPTAIN_HEIGHT;
+ }
+ else
+ {
+ SetContextForeGroundColor (BLACK_COLOR);
+ i -= 5;
+ if (i <= 14)
+ {
+ static const Color flash_tab1[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1E, 0x1F, 0x12), 0x70),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1F, 0x0A), 0x0E),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1F, 0x00), 0x71),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x1C, 0x00), 0x78),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x18, 0x00), 0x79),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7C),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7E),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7F),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2C),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D),
+ };
+
+ c = flash_tab1[i];
+ r.corner.x = CAPTAIN_XOFFS + i;
+ r.corner.y = y + CAPTAIN_YOFFS + i;
+ r.extent.width = CAPTAIN_WIDTH - (i << 1);
+ r.extent.height = CAPTAIN_HEIGHT - (i << 1);
+ if (r.extent.height == 2)
+ ++r.extent.height;
+ DrawRectangle (&r);
+ ++r.corner.x;
+ ++r.corner.y;
+ r.extent.width -= 2;
+ r.extent.height -= 2;
+ }
+ else if ((i -= 15) <= 4)
+ {
+ r.corner.y = y + (CAPTAIN_YOFFS + 15);
+ r.extent.width = i + 1;
+ r.extent.height = 1;
+ switch (i)
+ {
+ case 0:
+ r.corner.x = CAPTAIN_XOFFS + 15;
+ i = CAPTAIN_WIDTH - ((15 + 1) << 1);
+ c = BUILD_COLOR (MAKE_RGB15 (0x13, 0x00, 0x00), 0x2C);
+ break;
+ case 1:
+ r.corner.x = CAPTAIN_XOFFS + 16;
+ i = CAPTAIN_WIDTH - ((17 + 1) << 1);
+ c = BUILD_COLOR (MAKE_RGB15 (0x07, 0x00, 0x00), 0x2F);
+ break;
+ case 2:
+ r.corner.x = CAPTAIN_XOFFS + 18;
+ i = CAPTAIN_WIDTH - ((20 + 1) << 1);
+ c = BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x00), 0x2A);
+ break;
+ case 3:
+ r.corner.x = CAPTAIN_XOFFS + 21;
+ i = CAPTAIN_WIDTH - ((24 + 1) << 1);
+ c = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x29);
+ break;
+ case 4:
+ r.corner.x = CAPTAIN_XOFFS + 25;
+ i = 1;
+ r.extent.width = 2;
+ c = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x50, 0x05), 0x28);
+ break;
+ default:
+ // Should not happen.
+ c = UNDEFINED_COLOR; // Keeping compiler quiet.
+ break;
+ }
+ DrawFilledRectangle (&r);
+ r.corner.x += i + r.extent.width;
+ DrawFilledRectangle (&r);
+ r.corner.x -= i;
+ r.extent.width = i;
+ }
+ else
+ {
+ if ((i -= 5) > 2)
+ c = BLACK_COLOR;
+ else
+ {
+ static const Color flash_tab2[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0B, 0x00, 0x00), 0x2E),
+ };
+
+ c = flash_tab2[i];
+ }
+ r.corner.x = CAPTAIN_XOFFS
+ + (CAPTAIN_WIDTH >> 1);
+ r.corner.y = y + CAPTAIN_YOFFS
+ + ((CAPTAIN_HEIGHT + 1) >> 1);
+ r.extent.width = 1;
+ r.extent.height = 1;
+ }
+ }
+ SetContextForeGroundColor (c);
+ DrawFilledRectangle (&r);
+ }
+ }
+
+ old_status_flags = StarShipPtr->old_status_flags;
+ old_status_flags = (old_status_flags ^ cur_status_flags) &
+ (LEFT | RIGHT | THRUST | WEAPON | SPECIAL | LOW_ON_ENERGY);
+
+ if (old_status_flags)
+ {
+ if (old_status_flags & LOW_ON_ENERGY)
+ {
+ if (!(cur_status_flags & LOW_ON_ENERGY))
+ DrawCrewFuelString (y, 1);
+ else
+ DrawCrewFuelString (y, -1);
+ }
+
+ old_status_flags &= (LEFT | RIGHT | THRUST | WEAPON | SPECIAL);
+ if (old_status_flags)
+ {
+ CaptainsWindow (
+ &StarShipPtr->RaceDescPtr->ship_data.captain_control,
+ y, old_status_flags, cur_status_flags, 2);
+ }
+ }
+
+ StarShipPtr->old_status_flags = cur_status_flags;
+ }
+}
+
diff --git a/src/uqm/status.h b/src/uqm/status.h
new file mode 100644
index 0000000..f27965e
--- /dev/null
+++ b/src/uqm/status.h
@@ -0,0 +1,75 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_STATUS_H_INCL_
+#define UQM_STATUS_H_INCL_
+
+#include "races.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define CREW_XOFFS 4
+#define ENERGY_XOFFS 52
+#define GAUGE_YOFFS (SHIP_INFO_HEIGHT - 10)
+#define UNIT_WIDTH 2
+#define UNIT_HEIGHT 1
+#define STAT_WIDTH (1 + UNIT_WIDTH + 1 + UNIT_WIDTH + 1)
+
+#define SHIP_INFO_HEIGHT 65
+#define CAPTAIN_XOFFS 4
+#define CAPTAIN_YOFFS (SHIP_INFO_HEIGHT + 4)
+#define CAPTAIN_WIDTH 55
+#define CAPTAIN_HEIGHT 30
+#define SHIP_STATUS_HEIGHT (STATUS_HEIGHT >> 1)
+#define BAD_GUY_YOFFS 0
+#define GOOD_GUY_YOFFS SHIP_STATUS_HEIGHT
+#define STARCON_TEXT_HEIGHT 7
+#define TINY_TEXT_HEIGHT 9
+
+#define BATTLE_CREW_X 10
+#define BATTLE_CREW_Y (64 - SAFE_Y)
+
+extern COORD status_y_offsets[];
+
+extern void InitStatusOffsets (void);
+
+extern void DrawCrewFuelString (COORD y, SIZE state);
+extern void ClearShipStatus (COORD y);
+extern void OutlineShipStatus (COORD y);
+extern void InitShipStatus (SHIP_INFO *ShipInfoPtr, STARSHIP *StarShipPtr,
+ RECT *pClipRect);
+ // StarShipPtr or pClipRect can be NULL
+extern void DeltaStatistics (SHIP_INFO *ShipInfoPtr, COORD y_offs,
+ SIZE crew_delta, SIZE energy_delta);
+extern void DrawBattleCrewAmount (SHIP_INFO *ShipInfoPtr, COORD y_offs);
+
+extern void DrawCaptainsWindow (STARSHIP *StarShipPtr);
+extern BOOLEAN DeltaEnergy (ELEMENT *ElementPtr, SIZE energy_delta);
+extern BOOLEAN DeltaCrew (ELEMENT *ElementPtr, SIZE crew_delta);
+
+extern void PreProcessStatus (ELEMENT *ShipPtr);
+extern void PostProcessStatus (ELEMENT *ShipPtr);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_STATUS_H_INCL_ */
diff --git a/src/uqm/supermelee/Makeinfo b/src/uqm/supermelee/Makeinfo
new file mode 100644
index 0000000..897a1fe
--- /dev/null
+++ b/src/uqm/supermelee/Makeinfo
@@ -0,0 +1,5 @@
+uqm_CFILES="buildpick.c loadmele.c melee.c meleesetup.c pickmele.c"
+uqm_HFILES="buildpick.h loadmele.h melee.h meleesetup.h meleeship.h pickmele.h"
+if [ -n "$uqm_NETPLAY" ]; then
+ uqm_SUBDIRS="$uqm_SUBDIRS netplay"
+fi
diff --git a/src/uqm/supermelee/buildpick.c b/src/uqm/supermelee/buildpick.c
new file mode 100644
index 0000000..3f4731b
--- /dev/null
+++ b/src/uqm/supermelee/buildpick.c
@@ -0,0 +1,221 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "buildpick.h"
+
+#include "../controls.h"
+#include "../colors.h"
+#include "../fmv.h"
+#include "../master.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "libs/gfxlib.h"
+
+static FRAME BuildPickFrame;
+
+void
+BuildBuildPickFrame (void)
+{
+ STAMP s;
+ RECT r;
+ COUNT i;
+ CONTEXT OldContext = SetContext (OffScreenContext);
+
+ // create team building ship selection box
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (MeleeFrame, 27);
+ // 5x5 grid of ships to pick from
+ GetFrameRect (s.frame, &r);
+
+ BuildPickFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP, r.extent.width, r.extent.height, 1));
+ SetContextFGFrame (BuildPickFrame);
+ SetFrameHot (s.frame, MAKE_HOT_SPOT (0, 0));
+ DrawStamp (&s);
+
+ for (i = 0; i < NUM_PICK_COLS * NUM_PICK_ROWS; ++i)
+ DrawPickIcon (i, true);
+
+ SetContext (OldContext);
+}
+
+void
+DestroyBuildPickFrame (void)
+{
+ DestroyDrawable (ReleaseDrawable (BuildPickFrame));
+ BuildPickFrame = 0;
+}
+
+// Draw a ship icon in the ship selection popup.
+void
+DrawPickIcon (MeleeShip ship, bool DrawErase)
+{
+ STAMP s;
+ RECT r;
+
+ GetFrameRect (BuildPickFrame, &r);
+
+ s.origin.x = r.corner.x + 20 + (ship % NUM_PICK_COLS) * 18;
+ s.origin.y = r.corner.y + 5 + (ship / NUM_PICK_COLS) * 18;
+ s.frame = GetShipIconsFromIndex (ship);
+ if (DrawErase)
+ { // draw icon
+ DrawStamp (&s);
+ }
+ else
+ { // erase icon
+ Color OldColor;
+
+ OldColor = SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledStamp (&s);
+ SetContextForeGroundColor (OldColor);
+ }
+}
+
+void
+DrawPickFrame (MELEE_STATE *pMS)
+{
+ RECT r, r0, r1, ship_r;
+ STAMP s;
+
+ GetShipBox (&r0, 0, 0, 0),
+ GetShipBox (&r1, 1, NUM_MELEE_ROWS - 1, NUM_MELEE_COLUMNS - 1),
+ BoxUnion (&r0, &r1, &ship_r);
+
+ s.frame = SetAbsFrameIndex (BuildPickFrame, 0);
+ GetFrameRect (s.frame, &r);
+ r.corner.x = -(ship_r.corner.x
+ + ((ship_r.extent.width - r.extent.width) >> 1));
+ if (pMS->side)
+ r.corner.y = -ship_r.corner.y;
+ else
+ r.corner.y = -(ship_r.corner.y
+ + (ship_r.extent.height - r.extent.height));
+ SetFrameHot (s.frame, MAKE_HOT_SPOT (r.corner.x, r.corner.y));
+ s.origin.x = 0;
+ s.origin.y = 0;
+ DrawStamp (&s);
+ DrawMeleeShipStrings (pMS, pMS->currentShip);
+}
+
+void
+GetBuildPickFrameRect (RECT *r)
+{
+ GetFrameRect (BuildPickFrame, r);
+}
+
+static BOOLEAN
+DoPickShip (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ pMS->buildPickConfirmed = false;
+ return FALSE;
+ }
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ // Confirm selection or cancel.
+ pMS->buildPickConfirmed = !PulsedInputState.menu[KEY_MENU_CANCEL];
+ return FALSE;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SPECIAL]
+ && (pMS->currentShip != MELEE_NONE))
+ {
+ // Show ship spin video.
+ DoShipSpin (pMS->currentShip, (MUSIC_REF) 0);
+ return TRUE;
+ }
+
+ {
+ MeleeShip newSelectedShip;
+
+ newSelectedShip = pMS->currentShip;
+
+ if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ if (newSelectedShip % NUM_PICK_COLS == 0)
+ newSelectedShip += NUM_PICK_COLS;
+ --newSelectedShip;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ ++newSelectedShip;
+ if (newSelectedShip % NUM_PICK_COLS == 0)
+ newSelectedShip -= NUM_PICK_COLS;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (newSelectedShip >= NUM_PICK_COLS)
+ newSelectedShip -= NUM_PICK_COLS;
+ else
+ newSelectedShip += NUM_PICK_COLS * (NUM_PICK_ROWS - 1);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (newSelectedShip < NUM_PICK_COLS * (NUM_PICK_ROWS - 1))
+ newSelectedShip += NUM_PICK_COLS;
+ else
+ newSelectedShip -= NUM_PICK_COLS * (NUM_PICK_ROWS - 1);
+ }
+
+ if (newSelectedShip != pMS->currentShip)
+ {
+ // A new ship has been selected.
+ DrawPickIcon (pMS->currentShip, true);
+ pMS->currentShip = newSelectedShip;
+ DrawMeleeShipStrings (pMS, newSelectedShip);
+ }
+ }
+
+ Melee_flashSelection (pMS);
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+// Returns true if a ship has been selected, or false if the operation has
+// been cancelled or if the general abort key was pressed (in which case
+// 'GLOBAL (CurrentActivity) & CHECK_ABORT' is true as usual.
+// If a ship was selected, pMS->currentShip is set to the selected ship.
+bool
+BuildPickShip (MELEE_STATE *pMS)
+{
+ FlushInput ();
+
+ if (pMS->currentShip == MELEE_NONE)
+ pMS->currentShip = 0;
+
+ DrawPickFrame (pMS);
+
+ pMS->InputFunc = DoPickShip;
+ DoInput (pMS, FALSE);
+
+ return pMS->buildPickConfirmed;
+}
+
diff --git a/src/uqm/supermelee/buildpick.h b/src/uqm/supermelee/buildpick.h
new file mode 100644
index 0000000..43608e7
--- /dev/null
+++ b/src/uqm/supermelee/buildpick.h
@@ -0,0 +1,25 @@
+#ifndef BUILDPICK_H
+#define BUILDPICK_H
+
+#include "types.h"
+#include "melee.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void BuildBuildPickFrame (void);
+void DestroyBuildPickFrame (void);
+bool BuildPickShip (MELEE_STATE *pMS);
+void GetBuildPickFrameRect (RECT *r);
+
+void DrawPickFrame (MELEE_STATE *pMS);
+void DrawPickIcon (MeleeShip ship, bool DrawErase);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* BUILDPICK_H */
+
diff --git a/src/uqm/supermelee/loadmele.c b/src/uqm/supermelee/loadmele.c
new file mode 100644
index 0000000..d5917c3
--- /dev/null
+++ b/src/uqm/supermelee/loadmele.c
@@ -0,0 +1,826 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+// This file handles loading of teams, but the UI and the actual loading.
+
+#define MELEESETUP_INTERNAL
+#include "melee.h"
+
+#include "../controls.h"
+#include "../gameopt.h"
+#include "../gamestr.h"
+#include "../globdata.h"
+#include "../master.h"
+#include "meleesetup.h"
+#include "../save.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "options.h"
+#include "libs/log.h"
+#include "libs/memlib.h"
+
+
+#define LOAD_TEAM_NAME_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0F, 0x10, 0x1B), 0x00)
+#define LOAD_TEAM_NAME_TEXT_COLOR_HILITE \
+ BUILD_COLOR (MAKE_RGB15 (0x17, 0x18, 0x1D), 0x00)
+
+
+#define LOAD_MELEE_BOX_WIDTH 34
+#define LOAD_MELEE_BOX_HEIGHT 34
+#define LOAD_MELEE_BOX_SPACE 1
+
+
+static void DrawFileStrings (MELEE_STATE *pMS);
+static bool FillFileView (MELEE_STATE *pMS);
+
+
+static bool
+LoadTeamImage (DIRENTRY DirEntry, MeleeTeam *team)
+{
+ const char *fileName;
+ uio_Stream *stream;
+
+ fileName = GetDirEntryAddress (DirEntry);
+
+ stream = uio_fopen (meleeDir, fileName, "rb");
+ if (stream == NULL)
+ return false;
+
+ if (MeleeTeam_deserialize (team, stream) == -1)
+ return false;
+
+ uio_fclose (stream);
+
+ return true;
+}
+
+#if 0 /* Not used */
+static void
+UnindexFleet (MELEE_STATE *pMS, COUNT index)
+{
+ assert (index < pMS->load.numIndices);
+ pMS->load.numIndices--;
+ memmove (&pMS->load.entryIndices[index],
+ &pMS->load.entryIndices[index + 1],
+ (pMS->load.numIndices - index) * sizeof pMS->load.entryIndices[0]);
+}
+#endif
+
+static void
+UnindexFleets (MELEE_STATE *pMS, COUNT index, COUNT count)
+{
+ assert (index + count <= pMS->load.numIndices);
+
+ pMS->load.numIndices -= count;
+ memmove (&pMS->load.entryIndices[index],
+ &pMS->load.entryIndices[index + count],
+ (pMS->load.numIndices - index) * sizeof pMS->load.entryIndices[0]);
+}
+
+static bool
+GetFleetByIndex (MELEE_STATE *pMS, COUNT index, MeleeTeam *result)
+{
+ COUNT firstIndex;
+
+ if (index < pMS->load.preBuiltCount)
+ {
+ MeleeTeam_copy (result, pMS->load.preBuiltList[index]);
+ return true;
+ }
+
+ index -= pMS->load.preBuiltCount;
+ firstIndex = index;
+
+ for ( ; index < pMS->load.numIndices; index++)
+ {
+ DIRENTRY entry = SetAbsDirEntryTableIndex (pMS->load.dirEntries,
+ pMS->load.entryIndices[index]);
+ if (LoadTeamImage (entry, result))
+ break; // Success
+
+ {
+ const char *fileName;
+ fileName = GetDirEntryAddress (entry);
+ log_add (log_Warning, "Warning: File '%s' is not a valid "
+ "SuperMelee team.", fileName);
+ }
+ }
+
+ if (index != firstIndex)
+ UnindexFleets (pMS, firstIndex, index - firstIndex);
+
+ return index < pMS->load.numIndices;
+}
+
+// returns (COUNT) -1 if not found
+static COUNT
+GetFleetIndexByFileName (MELEE_STATE *pMS, const char *fileName)
+{
+ COUNT index;
+
+ for (index = 0; index < pMS->load.numIndices; index++)
+ {
+ DIRENTRY entry = SetAbsDirEntryTableIndex (pMS->load.dirEntries,
+ pMS->load.entryIndices[index]);
+ const char *entryName = GetDirEntryAddress (entry);
+
+ if (strcasecmp ((const char *) entryName, fileName) == 0)
+ return pMS->load.preBuiltCount + index;
+ }
+
+ return (COUNT) -1;
+}
+
+// Auxiliary function for DrawFileStrings
+// If drawShips is set the ships themselves are drawn, in addition to the
+// fleet name and value; if not, only the fleet name and value are drawn.
+// If highlite is set the text is drawn in the color used for highlighting.
+static void
+DrawFileString (const MeleeTeam *team, const POINT *origin,
+ BOOLEAN drawShips, BOOLEAN highlite)
+{
+ SetContextForeGroundColor (highlite ?
+ LOAD_TEAM_NAME_TEXT_COLOR_HILITE : LOAD_TEAM_NAME_TEXT_COLOR);
+
+ // Print the name of the fleet
+ {
+ TEXT Text;
+
+ Text.baseline = *origin;
+ Text.align = ALIGN_LEFT;
+ Text.pStr = MeleeTeam_getTeamName(team);
+ Text.CharCount = (COUNT)~0;
+ font_DrawText (&Text);
+ }
+
+ // Print the value of the fleet
+ {
+ TEXT Text;
+ UNICODE buf[60];
+
+ sprintf (buf, "%u", MeleeTeam_getValue (team));
+ Text.baseline = *origin;
+ Text.baseline.x += NUM_MELEE_COLUMNS *
+ (LOAD_MELEE_BOX_WIDTH + LOAD_MELEE_BOX_SPACE) - 1;
+ Text.align = ALIGN_RIGHT;
+ Text.pStr = buf;
+ Text.CharCount = (COUNT)~0;
+ font_DrawText (&Text);
+ }
+
+ // Draw the ships for the fleet
+ if (drawShips)
+ {
+ STAMP s;
+ FleetShipIndex slotI;
+
+ s.origin.x = origin->x + 1;
+ s.origin.y = origin->y + 4;
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ BYTE StarShip;
+
+ StarShip = team->ships[slotI];
+ if (StarShip != MELEE_NONE)
+ {
+ s.frame = GetShipIconsFromIndex (StarShip);
+ DrawStamp (&s);
+ s.origin.x += 17;
+ }
+ }
+ }
+}
+
+// returns true if there are any entries in the view, in which case
+// pMS->load.bot gets set to the index just past the bottom entry in the view.
+// returns false if not, in which case, the entire view remains unchanged.
+static bool
+FillFileView (MELEE_STATE *pMS)
+{
+ COUNT viewI;
+
+ for (viewI = 0; viewI < LOAD_TEAM_VIEW_SIZE; viewI++)
+ {
+ bool success = GetFleetByIndex (pMS, pMS->load.top + viewI,
+ pMS->load.view[viewI]);
+ if (!success)
+ break;
+ }
+
+ if (viewI == 0)
+ return false;
+
+ pMS->load.bot = pMS->load.top + viewI;
+ return true;
+}
+
+#define FILE_STRING_ORIGIN_X 5
+#define FILE_STRING_ORIGIN_Y 34
+#define ENTRY_HEIGHT 32
+
+static void
+SelectFileString (MELEE_STATE *pMS, bool hilite)
+{
+ CONTEXT OldContext;
+ POINT origin;
+ COUNT viewI;
+
+ viewI = pMS->load.cur - pMS->load.top;
+
+ OldContext = SetContext (SpaceContext);
+ SetContextFont (MicroFont);
+ BatchGraphics ();
+
+ origin.x = FILE_STRING_ORIGIN_X;
+ origin.y = FILE_STRING_ORIGIN_Y + viewI * ENTRY_HEIGHT;
+ DrawFileString (pMS->load.view[viewI], &origin, FALSE, hilite);
+
+ UnbatchGraphics ();
+ SetContext (OldContext);
+}
+
+static void
+DrawFileStrings (MELEE_STATE *pMS)
+{
+ POINT origin;
+ CONTEXT OldContext;
+
+ origin.x = FILE_STRING_ORIGIN_X;
+ origin.y = FILE_STRING_ORIGIN_Y;
+
+ OldContext = SetContext (SpaceContext);
+ SetContextFont (MicroFont);
+ BatchGraphics ();
+
+ DrawMeleeIcon (28); /* The load team frame */
+
+ if (FillFileView (pMS))
+ {
+ COUNT i;
+ for (i = pMS->load.top; i < pMS->load.bot; i++) {
+ DrawFileString (pMS->load.view[i - pMS->load.top], &origin,
+ TRUE, FALSE);
+ origin.y += ENTRY_HEIGHT;
+ }
+ }
+
+ UnbatchGraphics ();
+ SetContext (OldContext);
+}
+
+static void
+RefocusView (MELEE_STATE *pMS, COUNT index)
+{
+ assert (index < pMS->load.preBuiltCount + pMS->load.numIndices);
+
+ pMS->load.cur = index;
+ if (index <= LOAD_TEAM_VIEW_SIZE / 2)
+ pMS->load.top = 0;
+ else
+ pMS->load.top = index - LOAD_TEAM_VIEW_SIZE / 2;
+}
+
+static void
+flashSelectedTeam (MELEE_STATE *pMS)
+{
+#define FLASH_RATE (ONE_SECOND / 9)
+ static TimeCount NextTime = 0;
+ static int hilite = 0;
+ TimeCount Now = GetTimeCounter ();
+
+ if (Now >= NextTime)
+ {
+ CONTEXT OldContext;
+
+ NextTime = Now + FLASH_RATE;
+ hilite ^= 1;
+
+ OldContext = SetContext (SpaceContext);
+ SelectFileString (pMS, hilite);
+ SetContext (OldContext);
+ }
+}
+
+BOOLEAN
+DoLoadTeam (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN | MENU_SOUND_PAGEUP |
+ MENU_SOUND_PAGEDOWN, MENU_SOUND_SELECT);
+
+ if (!pMS->Initialized)
+ {
+ DrawFileStrings (pMS);
+ SelectFileString (pMS, true);
+ pMS->Initialized = TRUE;
+ pMS->InputFunc = DoLoadTeam;
+ return TRUE;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ // Copy the selected fleet to the player.
+ Melee_LocalChange_team (pMS, pMS->side,
+ pMS->load.view[pMS->load.cur - pMS->load.top]);
+ }
+
+ pMS->InputFunc = DoMelee;
+ pMS->LastInputTime = GetTimeCounter ();
+ {
+ RECT r;
+
+ GetFrameRect (SetAbsFrameIndex (MeleeFrame, 28), &r);
+ RepairMeleeFrame (&r);
+ }
+ return TRUE;
+ }
+
+ {
+ COUNT newTop = pMS->load.top;
+ COUNT newIndex = pMS->load.cur;
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (newIndex > 0)
+ {
+ newIndex--;
+ if (newIndex < newTop)
+ newTop = (newTop < LOAD_TEAM_VIEW_SIZE) ?
+ 0 : newTop - LOAD_TEAM_VIEW_SIZE;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ COUNT numEntries = pMS->load.numIndices + pMS->load.preBuiltCount;
+ if (newIndex + 1 < numEntries)
+ {
+ newIndex++;
+ if (newIndex >= pMS->load.bot)
+ newTop = pMS->load.bot;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_PAGE_UP])
+ {
+ newIndex = (newIndex < LOAD_TEAM_VIEW_SIZE) ?
+ 0 : newIndex - LOAD_TEAM_VIEW_SIZE;
+ newTop = (newTop < LOAD_TEAM_VIEW_SIZE) ?
+ 0 : newTop - LOAD_TEAM_VIEW_SIZE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_PAGE_DOWN])
+ {
+ COUNT numEntries = pMS->load.numIndices + pMS->load.preBuiltCount;
+ if (newIndex + LOAD_TEAM_VIEW_SIZE < numEntries)
+ {
+ newIndex += LOAD_TEAM_VIEW_SIZE;
+ newTop += LOAD_TEAM_VIEW_SIZE;
+ }
+ else
+ {
+ newIndex = numEntries - 1;
+ if (newTop + LOAD_TEAM_VIEW_SIZE < numEntries &&
+ numEntries > LOAD_TEAM_VIEW_SIZE)
+ newTop = numEntries - LOAD_TEAM_VIEW_SIZE;
+ }
+ }
+
+ if (newIndex != pMS->load.cur)
+ {
+ // The cursor has been moved.
+ if (newTop == pMS->load.top)
+ {
+ // The view itself hasn't changed.
+ SelectFileString (pMS, false);
+ }
+ else
+ {
+ // The view is changed.
+ pMS->load.top = newTop;
+ DrawFileStrings (pMS);
+ }
+ pMS->load.cur = newIndex;
+ }
+ }
+
+ flashSelectedTeam (pMS);
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+static void
+SelectTeamByFileName (MELEE_STATE *pMS, const char *fileName)
+{
+ COUNT index = GetFleetIndexByFileName (pMS, fileName);
+ if (index == (COUNT) -1)
+ return;
+
+ RefocusView (pMS, index);
+}
+
+void
+LoadTeamList (MELEE_STATE *pMS)
+{
+ COUNT i;
+
+ DestroyDirEntryTable (ReleaseDirEntryTable (pMS->load.dirEntries));
+ pMS->load.dirEntries = CaptureDirEntryTable (
+ LoadDirEntryTable (meleeDir, "", ".mle", match_MATCH_SUFFIX));
+
+ if (pMS->load.entryIndices != NULL)
+ HFree (pMS->load.entryIndices);
+ pMS->load.numIndices = GetDirEntryTableCount (pMS->load.dirEntries);
+ pMS->load.entryIndices = HMalloc (pMS->load.numIndices *
+ sizeof pMS->load.entryIndices[0]);
+ for (i = 0; i < pMS->load.numIndices; i++)
+ pMS->load.entryIndices[i] = i;
+}
+
+BOOLEAN
+DoSaveTeam (MELEE_STATE *pMS)
+{
+ STAMP MsgStamp;
+ char file[NAME_MAX];
+ uio_Stream *stream;
+ CONTEXT OldContext;
+ bool saveOk = false;
+
+ snprintf (file, sizeof file, "%s.mle",
+ MeleeSetup_getTeamName (pMS->meleeSetup, pMS->side));
+
+ OldContext = SetContext (ScreenContext);
+ ConfirmSaveLoad (&MsgStamp);
+ // Show the "Saving . . ." message.
+
+ stream = uio_fopen (meleeDir, file, "wb");
+ if (stream != NULL)
+ {
+ saveOk = (MeleeTeam_serialize (&pMS->meleeSetup->teams[pMS->side],
+ stream) == 0);
+ uio_fclose (stream);
+
+ if (!saveOk)
+ uio_unlink (meleeDir, file);
+ }
+
+ pMS->load.top = 0;
+ pMS->load.cur = 0;
+
+ // Undo the screen damage done by the "Saving . . ." message.
+ DrawStamp (&MsgStamp);
+ DestroyDrawable (ReleaseDrawable (MsgStamp.frame));
+ SetContext (OldContext);
+
+ if (!saveOk)
+ SaveProblem ();
+
+ // Update the team list; a previously existing team may have been
+ // deleted when save failed.
+ LoadTeamList (pMS);
+ SelectTeamByFileName (pMS, file);
+
+ return (stream != 0);
+}
+
+static void
+InitPreBuilt (MELEE_STATE *pMS)
+{
+ MeleeTeam **list;
+
+#define PREBUILT_COUNT 15
+ pMS->load.preBuiltList =
+ HMalloc (PREBUILT_COUNT * sizeof (MeleeTeam *));
+ pMS->load.preBuiltCount = PREBUILT_COUNT;
+#undef PREBUILT_COUNT
+
+ {
+ size_t fleetI;
+
+ for (fleetI = 0; fleetI < pMS->load.preBuiltCount; fleetI++)
+ pMS->load.preBuiltList[fleetI] = MeleeTeam_new ();
+ }
+
+ list = pMS->load.preBuiltList;
+
+ {
+ /* "Balanced Team 1" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 4));
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ list++;
+ }
+
+ {
+ /* "Balanced Team 2" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 5));
+ MeleeTeam_setShip (*list, i++, MELEE_ARILOU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_THRADDASH);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ list++;
+ }
+
+ {
+ /* "200 points" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 6));
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_ILWRATH);
+ MeleeTeam_setShip (*list, i++, MELEE_VUX);
+ list++;
+ }
+
+ {
+ /* "Behemoth Zenith" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 7));
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ list++;
+ }
+
+ {
+ /* "The Peeled Eyes" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 8));
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Ford's Fighters");
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ MeleeTeam_setShip (*list, i++, MELEE_UMGAH);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Leyland's Lashers");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "The Gregorizers 200");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "300 point Armada!");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Little Dudes with Attitudes");
+ MeleeTeam_setShip (*list, i++, MELEE_UMGAH);
+ MeleeTeam_setShip (*list, i++, MELEE_THRADDASH);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_VUX);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "New Alliance Ships");
+ MeleeTeam_setShip (*list, i++, MELEE_ARILOU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_THRADDASH);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Old Alliance Ships");
+ MeleeTeam_setShip (*list, i++, MELEE_ARILOU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_MMRNMHRM);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Old Hierarchy Ships");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_ILWRATH);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_UMGAH);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_VUX);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Star Control 1");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_ARILOU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_ILWRATH);
+ MeleeTeam_setShip (*list, i++, MELEE_MMRNMHRM);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_UMGAH);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_VUX);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Star Control 2");
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SLYLANDRO);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_THRADDASH);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ list++;
+ }
+
+ assert (list == pMS->load.preBuiltList + pMS->load.preBuiltCount);
+}
+
+static void
+UninitPreBuilt (MELEE_STATE *pMS)
+{
+ size_t fleetI;
+ for (fleetI = 0; fleetI < pMS->load.preBuiltCount; fleetI++)
+ MeleeTeam_delete (pMS->load.preBuiltList[fleetI]);
+ HFree (pMS->load.preBuiltList);
+ pMS->load.preBuiltCount = 0;
+}
+
+static void
+InitLoadView (MELEE_STATE *pMS)
+{
+ size_t viewI;
+ MeleeTeam **view = pMS->load.view;
+
+ for (viewI = 0; viewI < LOAD_TEAM_VIEW_SIZE; viewI++)
+ view[viewI] = MeleeTeam_new ();
+}
+
+static void
+UninitLoadView (MELEE_STATE *pMS)
+{
+ size_t viewI;
+ MeleeTeam **view = pMS->load.view;
+
+ for (viewI = 0; viewI < LOAD_TEAM_VIEW_SIZE; viewI++)
+ MeleeTeam_delete(view[viewI]);
+}
+
+void
+InitMeleeLoadState (MELEE_STATE *pMS)
+{
+ pMS->load.entryIndices = NULL;
+ InitPreBuilt (pMS);
+ InitLoadView (pMS);
+}
+
+void
+UninitMeleeLoadState (MELEE_STATE *pMS)
+{
+ UninitLoadView (pMS);
+ UninitPreBuilt (pMS);
+ if (pMS->load.entryIndices != NULL)
+ HFree (pMS->load.entryIndices);
+}
+
+
diff --git a/src/uqm/supermelee/loadmele.h b/src/uqm/supermelee/loadmele.h
new file mode 100644
index 0000000..529ef66
--- /dev/null
+++ b/src/uqm/supermelee/loadmele.h
@@ -0,0 +1,67 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_SUPERMELEE_LOADMELE_H_
+#define UQM_SUPERMELEE_LOADMELE_H_
+
+#define LOAD_TEAM_VIEW_SIZE 5
+
+struct melee_load_state;
+
+#include "melee.h"
+#include "meleesetup.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct melee_load_state
+{
+ MeleeTeam **preBuiltList;
+ COUNT preBuiltCount;
+
+ DIRENTRY dirEntries;
+ COUNT *entryIndices;
+ COUNT numIndices;
+
+ MeleeTeam *view[LOAD_TEAM_VIEW_SIZE];
+ COUNT top;
+ // Index of the first entry for the view.
+ COUNT bot;
+ // Index of the first entry past the end of the view.
+
+ COUNT cur;
+ // Index of the current position in the view.
+ COUNT viewSize;
+ // Number of entries in the view.
+};
+
+void InitMeleeLoadState (MELEE_STATE *pMS);
+void UninitMeleeLoadState (MELEE_STATE *pMS);
+
+BOOLEAN DoLoadTeam (MELEE_STATE *pMS);
+BOOLEAN DoSaveTeam (MELEE_STATE *pMS);
+bool ReadTeamImage (MeleeTeam *pTI, uio_Stream *load_fp);
+int WriteTeamImage (const MeleeTeam *pTI, uio_Stream *save_fp);
+void LoadTeamList (MELEE_STATE *pMS);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_LOADMELE_H_ */
diff --git a/src/uqm/supermelee/melee.c b/src/uqm/supermelee/melee.c
new file mode 100644
index 0000000..70f3acb
--- /dev/null
+++ b/src/uqm/supermelee/melee.c
@@ -0,0 +1,2640 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "melee.h"
+
+#include "options.h"
+#include "buildpick.h"
+#include "meleeship.h"
+#include "../battle.h"
+#include "../build.h"
+#include "../status.h"
+#include "../colors.h"
+#include "../comm.h"
+ // for getLineWithinWidth()
+#include "../cons_res.h"
+ // for load_gravity_well() and free_gravity_well()
+#include "../controls.h"
+#include "../gamestr.h"
+#include "../globdata.h"
+#include "../intel.h"
+#include "../master.h"
+#include "../nameref.h"
+#ifdef NETPLAY
+# include "netplay/netconnection.h"
+# include "netplay/netmelee.h"
+# include "netplay/notify.h"
+# include "netplay/notifyall.h"
+# include "libs/graphics/widgets.h"
+ // for DrawShadowedBox()
+# include "../cnctdlg.h"
+ // for MeleeConnectDialog()
+#endif /* defined (NETPLAY) */
+#include "../resinst.h"
+#include "../settings.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "../util.h"
+ // for DrawStarConBox()
+#include "../planets/planets.h"
+ // for NUMBER_OF_PLANET_TYPES
+#include "libs/gfxlib.h"
+#include "libs/mathlib.h"
+ // for TFB_Random()
+#include "libs/reslib.h"
+#include "libs/log.h"
+#include "libs/uio.h"
+
+
+#include <assert.h>
+#include <string.h>
+
+
+static void StartMelee (MELEE_STATE *pMS);
+#ifdef NETPLAY
+static ssize_t numPlayersReady (void);
+#endif /* NETPLAY */
+
+enum
+{
+#ifdef NETPLAY
+ NET_TOP,
+#endif
+ CONTROLS_TOP,
+ SAVE_TOP,
+ LOAD_TOP,
+ START_MELEE,
+ LOAD_BOT,
+ SAVE_BOT,
+ CONTROLS_BOT,
+#ifdef NETPLAY
+ NET_BOT,
+#endif
+ QUIT_BOT,
+ EDIT_MELEE, // Editing a fleet or the team name
+ BUILD_PICK // Selecting a ship to add to a fleet
+};
+
+#ifdef NETPLAY
+#define TOP_ENTRY NET_TOP
+#else
+#define TOP_ENTRY CONTROLS_TOP
+#endif
+
+#define MELEE_X_OFFS 2
+#define MELEE_Y_OFFS 21
+#define MELEE_BOX_WIDTH 34
+#define MELEE_BOX_HEIGHT 34
+#define MELEE_BOX_SPACE 1
+
+#define MENU_X_OFFS 29
+
+#define INFO_ORIGIN_X 4
+#define INFO_WIDTH 58
+#define TEAM_INFO_ORIGIN_Y 3
+#define TEAM_INFO_HEIGHT (SHIP_INFO_HEIGHT + 75)
+#define MODE_INFO_ORIGIN_Y (TEAM_INFO_HEIGHT + 6)
+#define MODE_INFO_HEIGHT ((STATUS_HEIGHT - 3) - MODE_INFO_ORIGIN_Y)
+#define RACE_INFO_ORIGIN_Y (SHIP_INFO_HEIGHT + 6)
+#define RACE_INFO_HEIGHT ((STATUS_HEIGHT - 3) - RACE_INFO_ORIGIN_Y)
+
+#define MELEE_STATUS_X_OFFS 1
+#define MELEE_STATUS_Y_OFFS 201
+#define MELEE_STATUS_WIDTH (NUM_MELEE_COLUMNS * \
+ (MELEE_BOX_WIDTH + MELEE_BOX_SPACE))
+#define MELEE_STATUS_HEIGHT 38
+
+#define MELEE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
+#define MELEE_TITLE_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)
+#define MELEE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)
+#define MELEE_TEAM_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)
+
+#define STATE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+#define STATE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)
+#define ACTIVE_STATE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+#define UNAVAILABLE_STATE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+#define HI_STATE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+#define HI_STATE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+
+// XXX: The following entries are unused:
+#define LIST_INFO_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05)
+#define LIST_INFO_TITLE_COLOR \
+ WHITE_COLOR
+#define LIST_INFO_TEXT_COLOR \
+ LT_GRAY_COLOR
+#define LIST_INFO_CURENTRY_TEXT_COLOR \
+ WHITE_COLOR
+#define HI_LIST_INFO_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
+#define HI_LIST_INFO_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x1F), 0x0D)
+
+#define TEAM_NAME_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0F, 0x10, 0x1B), 0x00)
+#define TEAM_NAME_EDIT_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x17, 0x18, 0x1D), 0x00)
+#define TEAM_NAME_EDIT_RECT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05)
+#define TEAM_NAME_EDIT_CURS_COLOR \
+ WHITE_COLOR
+
+#define SHIPBOX_TOPLEFT_COLOR_NORMAL \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x09), 0x56)
+#define SHIPBOX_BOTTOMRIGHT_COLOR_NORMAL \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0E), 0x54)
+#define SHIPBOX_INTERIOR_COLOR_NORMAL \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0C), 0x55)
+
+#define SHIPBOX_TOPLEFT_COLOR_HILITE \
+ BUILD_COLOR (MAKE_RGB15 (0x07, 0x00, 0x0C), 0x3E)
+#define SHIPBOX_BOTTOMRIGHT_COLOR_HILITE \
+ BUILD_COLOR (MAKE_RGB15 (0x0C, 0x00, 0x14), 0x3C)
+#define SHIPBOX_INTERIOR_COLOR_HILITE \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x00, 0x11), 0x3D)
+
+#define MELEE_STATUS_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02)
+
+
+FRAME MeleeFrame;
+ // Loaded from melee/melebkgd.ani
+MELEE_STATE *pMeleeState;
+
+BOOLEAN DoMelee (MELEE_STATE *pMS);
+static BOOLEAN DoEdit (MELEE_STATE *pMS);
+static BOOLEAN DoConfirmSettings (MELEE_STATE *pMS);
+
+#define DTSHS_NORMAL 0
+#define DTSHS_EDIT 1
+#define DTSHS_SELECTED 2
+#define DTSHS_REPAIR 4
+#define DTSHS_BLOCKCUR 8
+static BOOLEAN DrawTeamString (MELEE_STATE *pMS, COUNT side,
+ COUNT HiLiteState, const char *str);
+static void DrawFleetValue (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState);
+
+static void Melee_UpdateView_fleetValue (MELEE_STATE *pMS, COUNT side);
+static void Melee_UpdateView_ship (MELEE_STATE *pMS, COUNT side,
+ FleetShipIndex index);
+static void Melee_UpdateView_teamName (MELEE_STATE *pMS, COUNT side);
+
+
+// These icons come from melee/melebkgd.ani
+void
+DrawMeleeIcon (COUNT which_icon)
+{
+ STAMP s;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (MeleeFrame, which_icon);
+ DrawStamp (&s);
+}
+
+static FleetShipIndex
+GetShipIndex (BYTE row, BYTE col)
+{
+ return row * NUM_MELEE_COLUMNS + col;
+}
+
+static BYTE
+GetShipRow (FleetShipIndex index)
+{
+ return index / NUM_MELEE_COLUMNS;
+}
+
+static BYTE
+GetShipColumn (int index)
+{
+ return index % NUM_MELEE_COLUMNS;
+}
+
+// Get the rectangle containing the ship slot for the specified side, row,
+// and column.
+void
+GetShipBox (RECT *pRect, COUNT side, COUNT row, COUNT col)
+{
+ pRect->corner.x = MELEE_X_OFFS
+ + (col * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE));
+ pRect->corner.y = MELEE_Y_OFFS
+ + (side * (MELEE_Y_OFFS + MELEE_BOX_SPACE
+ + (NUM_MELEE_ROWS * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE))))
+ + (row * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE));
+ pRect->extent.width = MELEE_BOX_WIDTH;
+ pRect->extent.height = MELEE_BOX_HEIGHT;
+}
+
+static void
+DrawShipBox (COUNT side, FleetShipIndex index, MeleeShip ship, BOOLEAN HiLite)
+{
+ RECT r;
+ BYTE row = GetShipRow (index);
+ BYTE col = GetShipColumn (index);
+
+ GetShipBox (&r, side, row, col);
+
+ BatchGraphics ();
+ if (HiLite)
+ DrawStarConBox (&r, 1,
+ SHIPBOX_TOPLEFT_COLOR_HILITE,
+ SHIPBOX_BOTTOMRIGHT_COLOR_HILITE,
+ (BOOLEAN)(ship != MELEE_NONE),
+ SHIPBOX_INTERIOR_COLOR_HILITE);
+ else
+ DrawStarConBox (&r, 1,
+ SHIPBOX_TOPLEFT_COLOR_NORMAL,
+ SHIPBOX_BOTTOMRIGHT_COLOR_NORMAL,
+ (BOOLEAN)(ship != MELEE_NONE),
+ SHIPBOX_INTERIOR_COLOR_NORMAL);
+
+ if (ship != MELEE_NONE)
+ {
+ STAMP s;
+ s.origin.x = r.corner.x + (r.extent.width >> 1);
+ s.origin.y = r.corner.y + (r.extent.height >> 1);
+ s.frame = GetShipMeleeIconsFromIndex (ship);
+
+ DrawStamp (&s);
+ }
+ UnbatchGraphics ();
+}
+
+static void
+ClearShipBox (COUNT side, FleetShipIndex index)
+{
+ RECT rect;
+ BYTE row = GetShipRow (index);
+ BYTE col = GetShipColumn (index);
+
+ GetShipBox (&rect, side, row, col);
+ RepairMeleeFrame (&rect);
+}
+
+static void
+DrawShipBoxCurrent (MELEE_STATE *pMS, BOOLEAN HiLite)
+{
+ FleetShipIndex slotI = GetShipIndex (pMS->row, pMS->col);
+ MeleeShip ship = MeleeSetup_getShip (pMS->meleeSetup, pMS->side, slotI);
+ DrawShipBox (pMS->side, slotI, ship, HiLite);
+}
+
+// Draw an image for one of the control method selection buttons.
+static void
+DrawControls (COUNT which_side, BOOLEAN HiLite)
+{
+ COUNT which_icon;
+
+ if (PlayerControl[which_side] & NETWORK_CONTROL)
+ {
+ DrawMeleeIcon (31 + (HiLite ? 1 : 0) + 2 * (1 - which_side));
+ /* "Network Control" */
+ return;
+ }
+
+ if (PlayerControl[which_side] & HUMAN_CONTROL)
+ which_icon = 0;
+ else
+ {
+ switch (PlayerControl[which_side]
+ & (STANDARD_RATING | GOOD_RATING | AWESOME_RATING))
+ {
+ case STANDARD_RATING:
+ which_icon = 1;
+ break;
+ case GOOD_RATING:
+ which_icon = 2;
+ break;
+ case AWESOME_RATING:
+ which_icon = 3;
+ break;
+ default:
+ // Should not happen. Satisfying compiler.
+ which_icon = 0;
+ break;
+ }
+ }
+
+ DrawMeleeIcon (1 + (8 * (1 - which_side)) + (HiLite ? 4 : 0) + which_icon);
+}
+
+static void
+DrawTeams (void)
+{
+ COUNT side;
+
+ for (side = 0; side < NUM_SIDES; side++)
+ {
+ FleetShipIndex index;
+
+ DrawControls (side, FALSE);
+
+ for (index = 0; index < MELEE_FLEET_SIZE; index++)
+ {
+ MeleeShip ship = MeleeSetup_getShip(pMeleeState->meleeSetup,
+ side, index);
+ DrawShipBox (side, index, ship, FALSE);
+ }
+
+ DrawTeamString (pMeleeState, side, DTSHS_NORMAL, NULL);
+ DrawFleetValue (pMeleeState, side, DTSHS_NORMAL);
+ }
+}
+
+void
+RepairMeleeFrame (const RECT *pRect)
+{
+ RECT r;
+ CONTEXT OldContext;
+ RECT OldRect;
+ POINT oldOrigin;
+
+ r.corner.x = pRect->corner.x + SAFE_X;
+ r.corner.y = pRect->corner.y + SAFE_Y;
+ r.extent = pRect->extent;
+ if (r.corner.y & 1)
+ {
+ --r.corner.y;
+ ++r.extent.height;
+ }
+
+ OldContext = SetContext (SpaceContext);
+ GetContextClipRect (&OldRect);
+ SetContextClipRect (&r);
+ // Offset the origin so that we draw the correct gfx in the cliprect
+ oldOrigin = SetContextOrigin (MAKE_POINT (-r.corner.x + SAFE_X,
+ -r.corner.y + SAFE_Y));
+ BatchGraphics ();
+
+ DrawMeleeIcon (0); /* Entire melee screen */
+#ifdef NETPLAY
+ DrawMeleeIcon (35); /* "Net..." (top, not highlighted) */
+ DrawMeleeIcon (37); /* "Net..." (bottom, not highlighted) */
+#endif
+ DrawMeleeIcon (26); /* "Battle!" (highlighted) */
+
+ DrawTeams ();
+
+ if (pMeleeState->MeleeOption == BUILD_PICK)
+ DrawPickFrame (pMeleeState);
+
+ UnbatchGraphics ();
+ SetContextOrigin (oldOrigin);
+ SetContextClipRect (&OldRect);
+ SetContext (OldContext);
+}
+
+static void
+RedrawMeleeFrame (void)
+{
+ RECT r;
+
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = SCREEN_WIDTH;
+ r.extent.height = SCREEN_HEIGHT;
+
+ RepairMeleeFrame (&r);
+}
+
+static void
+GetTeamStringRect (COUNT side, RECT *r)
+{
+ r->corner.x = MELEE_X_OFFS - 1;
+ r->corner.y = (side + 1) * (MELEE_Y_OFFS
+ + ((MELEE_BOX_HEIGHT + MELEE_BOX_SPACE) * NUM_MELEE_ROWS + 2));
+ r->extent.width = NUM_MELEE_COLUMNS * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE)
+ - 29;
+ r->extent.height = 13;
+}
+
+static void
+GetFleetValueRect (COUNT side, RECT *r)
+{
+ r->corner.x = MELEE_X_OFFS
+ + NUM_MELEE_COLUMNS * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE) - 30;
+ r->corner.y = (side + 1) * (MELEE_Y_OFFS
+ + ((MELEE_BOX_HEIGHT + MELEE_BOX_SPACE) * NUM_MELEE_ROWS + 2));
+ r->extent.width = 29;
+ r->extent.height = 13;
+}
+
+static void
+DrawFleetValue (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState)
+{
+ RECT r;
+ TEXT rtText;
+ UNICODE buf[30];
+ COUNT fleetValue;
+
+ GetFleetValueRect (side ,&r);
+
+ if (HiLiteState == DTSHS_REPAIR)
+ {
+ RepairMeleeFrame (&r);
+ return;
+ }
+
+ SetContextFont (MicroFont);
+
+ fleetValue = MeleeSetup_getFleetValue (pMS->meleeSetup, side);
+ sprintf (buf, "%u", fleetValue);
+ rtText.pStr = buf;
+ rtText.align = ALIGN_RIGHT;
+ rtText.CharCount = (COUNT)~0;
+ rtText.baseline.y = r.corner.y + r.extent.height - 3;
+ rtText.baseline.x = r.corner.x + r.extent.width;
+
+ SetContextForeGroundColor (!(HiLiteState & DTSHS_SELECTED)
+ ? TEAM_NAME_TEXT_COLOR : TEAM_NAME_EDIT_TEXT_COLOR);
+ font_DrawText (&rtText);
+}
+
+// If teamName == NULL, the team name is taken from pMS->meleeSetup
+static BOOLEAN
+DrawTeamString (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState,
+ const char *teamName)
+{
+ RECT r;
+ TEXT lfText;
+
+ GetTeamStringRect (side, &r);
+ if (HiLiteState == DTSHS_REPAIR)
+ {
+ RepairMeleeFrame (&r);
+ return TRUE;
+ }
+
+ SetContextFont (MicroFont);
+
+ lfText.pStr = (teamName != NULL) ? teamName :
+ MeleeSetup_getTeamName (pMS->meleeSetup, side);
+ lfText.baseline.y = r.corner.y + r.extent.height - 3;
+ lfText.baseline.x = r.corner.x + 1;
+ lfText.align = ALIGN_LEFT;
+ lfText.CharCount = strlen (lfText.pStr);
+
+ BatchGraphics ();
+ if (!(HiLiteState & DTSHS_EDIT))
+ { // normal or selected state
+ SetContextForeGroundColor (!(HiLiteState & DTSHS_SELECTED)
+ ? TEAM_NAME_TEXT_COLOR : TEAM_NAME_EDIT_TEXT_COLOR);
+ font_DrawText (&lfText);
+ }
+ else
+ { // editing state
+ COUNT i;
+ RECT text_r;
+ BYTE char_deltas[MAX_TEAM_CHARS];
+ BYTE *pchar_deltas;
+
+ TextRect (&lfText, &text_r, char_deltas);
+ if ((text_r.extent.width + 2) >= r.extent.width)
+ { // the text does not fit the input box size and so
+ // will not fit when displayed later
+ UnbatchGraphics ();
+ // disallow the change
+ return FALSE;
+ }
+
+ text_r = r;
+ SetContextForeGroundColor (TEAM_NAME_EDIT_RECT_COLOR);
+ DrawFilledRectangle (&text_r);
+
+ // calculate the cursor position and draw it
+ pchar_deltas = char_deltas;
+ for (i = pMS->CurIndex; i > 0; --i)
+ text_r.corner.x += (SIZE)*pchar_deltas++;
+ if (pMS->CurIndex < lfText.CharCount) /* cursor mid-line */
+ --text_r.corner.x;
+ if (HiLiteState & DTSHS_BLOCKCUR)
+ { // Use block cursor for keyboardless systems
+ if (pMS->CurIndex == lfText.CharCount)
+ { // cursor at end-line -- use insertion point
+ text_r.extent.width = 1;
+ }
+ else if (pMS->CurIndex + 1 == lfText.CharCount)
+ { // extra pixel for last char margin
+ text_r.extent.width = (SIZE)*pchar_deltas + 2;
+ }
+ else
+ { // normal mid-line char
+ text_r.extent.width = (SIZE)*pchar_deltas + 1;
+ }
+ }
+ else
+ { // Insertion point cursor
+ text_r.extent.width = 1;
+ }
+ // position cursor within input field rect
+ ++text_r.corner.x;
+ ++text_r.corner.y;
+ text_r.extent.height -= 2;
+ SetContextForeGroundColor (TEAM_NAME_EDIT_CURS_COLOR);
+ DrawFilledRectangle (&text_r);
+
+ SetContextForeGroundColor (BLACK_COLOR); // TEAM_NAME_EDIT_TEXT_COLOR);
+ font_DrawText (&lfText);
+ }
+ UnbatchGraphics ();
+
+ return TRUE;
+}
+
+#ifdef NETPLAY
+// This function is generic. It should probably be moved to elsewhere.
+static void
+multiLineDrawText (TEXT *textIn, RECT *clipRect) {
+ RECT oldRect;
+
+ SIZE leading;
+ TEXT text;
+ SIZE lineWidth;
+
+ GetContextClipRect (&oldRect);
+
+ SetContextClipRect (clipRect);
+ GetContextFontLeading (&leading);
+
+ text = *textIn;
+ text.baseline.x = 1;
+ text.baseline.y = 0;
+
+ if (clipRect->extent.width <= text.baseline.x)
+ goto out;
+
+ lineWidth = clipRect->extent.width - text.baseline.x;
+
+ while (*text.pStr != '\0') {
+ const char *nextLine;
+
+ text.baseline.y += leading;
+ text.CharCount = (COUNT) ~0;
+ getLineWithinWidth (&text, &nextLine, lineWidth, text.CharCount);
+ // This will also fill in text->CharCount.
+
+ font_DrawText (&text);
+
+ text.pStr = nextLine;
+ }
+
+out:
+ SetContextClipRect (&oldRect);
+}
+
+// Use an empty string to clear the status area.
+static void
+DrawMeleeStatusMessage (const char *message)
+{
+ CONTEXT oldContext;
+ RECT r;
+
+ oldContext = SetContext (SpaceContext);
+
+ r.corner.x = MELEE_STATUS_X_OFFS;
+ r.corner.y = MELEE_STATUS_Y_OFFS;
+ r.extent.width = MELEE_STATUS_WIDTH;
+ r.extent.height = MELEE_STATUS_HEIGHT;
+
+ RepairMeleeFrame (&r);
+
+ if (message[0] != '\0')
+ {
+ TEXT lfText;
+ lfText.pStr = message;
+ lfText.align = ALIGN_LEFT;
+ lfText.CharCount = (COUNT)~0;
+
+ SetContextFont (MicroFont);
+ SetContextForeGroundColor (MELEE_STATUS_COLOR);
+
+ BatchGraphics ();
+ multiLineDrawText (&lfText, &r);
+ UnbatchGraphics ();
+ }
+
+ SetContext (oldContext);
+}
+
+static void
+UpdateMeleeStatusMessage (ssize_t player)
+{
+ NetConnection *conn;
+
+ assert (player == -1 || (player >= 0 && player < NUM_PLAYERS));
+
+ if (player == -1)
+ {
+ DrawMeleeStatusMessage ("");
+ return;
+ }
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ {
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 0));
+ // "Unconnected. Press LEFT to connect."
+ return;
+ }
+
+ switch (NetConnection_getState (conn)) {
+ case NetState_unconnected:
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 0));
+ // "Unconnected. Press LEFT to connect."
+ break;
+ case NetState_connecting:
+ if (NetConnection_getPeerOptions (conn)->isServer)
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 1));
+ // "Awaiting incoming connection...\n"
+ // "Press RIGHT to cancel."
+ else
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 2));
+ // "Attempting outgoing connection...\n"
+ // "Press RIGHT to cancel."
+ break;
+ case NetState_init:
+ case NetState_inSetup:
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 3));
+ // "Connected. Press RIGHT to disconnect."
+ break;
+ default:
+ DrawMeleeStatusMessage ("");
+ break;
+ }
+}
+#endif /* NETPLAY */
+
+// XXX: this function is called when the current selection is blinking off.
+static void
+Deselect (BYTE opt)
+{
+ switch (opt)
+ {
+ case START_MELEE:
+ DrawMeleeIcon (25); /* "Battle!" (not highlighted) */
+ break;
+ case LOAD_TOP:
+ DrawMeleeIcon (17); /* "Load" (top, not highlighted) */
+ break;
+ case LOAD_BOT:
+ DrawMeleeIcon (22); /* "Load" (bottom, not highlighted) */
+ break;
+ case SAVE_TOP:
+ DrawMeleeIcon (18); /* "Save" (top, not highlighted) */
+ break;
+ case SAVE_BOT:
+ DrawMeleeIcon (21); /* "Save" (bottom, not highlighted) */
+ break;
+#ifdef NETPLAY
+ case NET_TOP:
+ DrawMeleeIcon (35); /* "Net..." (top, not highlighted) */
+ break;
+ case NET_BOT:
+ DrawMeleeIcon (37); /* "Net..." (bottom, not highlighted) */
+ break;
+#endif
+ case QUIT_BOT:
+ DrawMeleeIcon (29); /* "Quit" (not highlighted) */
+ break;
+ case CONTROLS_TOP:
+ case CONTROLS_BOT:
+ {
+ COUNT which_side;
+
+ which_side = opt == CONTROLS_TOP ? 1 : 0;
+ DrawControls (which_side, FALSE);
+ break;
+ }
+ case EDIT_MELEE:
+ if (pMeleeState->InputFunc == DoEdit)
+ {
+ if (pMeleeState->row < NUM_MELEE_ROWS)
+ DrawShipBoxCurrent (pMeleeState, FALSE);
+ else if (pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
+ {
+ // Not currently editing the team name.
+ DrawTeamString (pMeleeState, pMeleeState->side,
+ DTSHS_NORMAL, NULL);
+ DrawFleetValue (pMeleeState, pMeleeState->side,
+ DTSHS_NORMAL);
+ }
+ }
+ break;
+ case BUILD_PICK:
+ DrawPickIcon (pMeleeState->currentShip, true);
+ break;
+ }
+}
+
+// XXX: this function is called when the current selection is blinking off.
+static void
+Select (BYTE opt)
+{
+ switch (opt)
+ {
+ case START_MELEE:
+ DrawMeleeIcon (26); /* "Battle!" (highlighted) */
+ break;
+ case LOAD_TOP:
+ DrawMeleeIcon (19); /* "Load" (top, highlighted) */
+ break;
+ case LOAD_BOT:
+ DrawMeleeIcon (24); /* "Load" (bottom, highlighted) */
+ break;
+ case SAVE_TOP:
+ DrawMeleeIcon (20); /* "Save" (top; highlighted) */
+ break;
+ case SAVE_BOT:
+ DrawMeleeIcon (23); /* "Save" (bottom; highlighted) */
+ break;
+#ifdef NETPLAY
+ case NET_TOP:
+ DrawMeleeIcon (36); /* "Net..." (top; highlighted) */
+ break;
+ case NET_BOT:
+ DrawMeleeIcon (38); /* "Net..." (bottom; highlighted) */
+ break;
+#endif
+ case QUIT_BOT:
+ DrawMeleeIcon (30); /* "Quit" (highlighted) */
+ break;
+ case CONTROLS_TOP:
+ case CONTROLS_BOT:
+ {
+ COUNT which_side;
+
+ which_side = (opt == CONTROLS_TOP) ? 1 : 0;
+ DrawControls (which_side, TRUE);
+ break;
+ }
+ case EDIT_MELEE:
+ if (pMeleeState->InputFunc == DoEdit)
+ {
+ if (pMeleeState->row < NUM_MELEE_ROWS)
+ DrawShipBoxCurrent (pMeleeState, TRUE);
+ else if (pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
+ {
+ // Not currently editing the team name.
+ DrawTeamString (pMeleeState, pMeleeState->side,
+ DTSHS_SELECTED, NULL);
+ DrawFleetValue (pMeleeState, pMeleeState->side,
+ DTSHS_SELECTED);
+ }
+ }
+ break;
+ case BUILD_PICK:
+ DrawPickIcon (pMeleeState->currentShip, false);
+ break;
+ }
+}
+
+void
+Melee_flashSelection (MELEE_STATE *pMS)
+{
+#define FLASH_RATE (ONE_SECOND / 9)
+ static TimeCount NextTime = 0;
+ static bool select = false;
+ TimeCount Now = GetTimeCounter ();
+
+ if (Now >= NextTime)
+ {
+ CONTEXT OldContext;
+
+ NextTime = Now + FLASH_RATE;
+ select = !select;
+
+ OldContext = SetContext (SpaceContext);
+ if (select)
+ Select (pMS->MeleeOption);
+ else
+ Deselect (pMS->MeleeOption);
+ SetContext (OldContext);
+ }
+}
+
+static void
+InitMelee (MELEE_STATE *pMS)
+{
+ RECT r;
+
+ SetContext (SpaceContext);
+ SetContextFGFrame (Screen);
+ SetContextClipRect (NULL);
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+ r.corner.x = SAFE_X;
+ r.corner.y = SAFE_Y;
+ r.extent.width = SCREEN_WIDTH - (SAFE_X * 2);
+ r.extent.height = SCREEN_HEIGHT - (SAFE_Y * 2);
+ SetContextClipRect (&r);
+
+ r.corner.x = r.corner.y = 0;
+ RedrawMeleeFrame ();
+
+ (void) pMS;
+}
+
+void
+DrawMeleeShipStrings (MELEE_STATE *pMS, MeleeShip NewStarShip)
+{
+ RECT r, OldRect;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+ GetContextClipRect (&OldRect);
+ r = OldRect;
+ r.corner.x += ((SAFE_X << 1) - 32) + MENU_X_OFFS;
+ r.corner.y += 76;
+ r.extent.height = SHIP_INFO_HEIGHT;
+ SetContextClipRect (&r);
+ BatchGraphics ();
+
+ if (NewStarShip == MELEE_NONE)
+ {
+ RECT r;
+ TEXT t;
+
+ ClearShipStatus (0);
+ SetContextFont (StarConFont);
+ r.corner.x = 3;
+ r.corner.y = 4;
+ r.extent.width = 57;
+ r.extent.height = 60;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawRectangle (&r);
+ t.baseline.x = STATUS_WIDTH >> 1;
+ t.baseline.y = 32;
+ t.align = ALIGN_CENTER;
+ if (pMS->row < NUM_MELEE_ROWS)
+ {
+ // A ship is selected (or an empty fleet position).
+ t.pStr = GAME_STRING (MELEE_STRING_BASE + 0); // "Empty"
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.pStr = GAME_STRING (MELEE_STRING_BASE + 1); // "Slot"
+ }
+ else
+ {
+ // The team name is selected.
+ t.pStr = GAME_STRING (MELEE_STRING_BASE + 2); // "Team"
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.pStr = GAME_STRING (MELEE_STRING_BASE + 3); // "Name"
+ }
+ t.baseline.y += TINY_TEXT_HEIGHT;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ }
+ else
+ {
+ HMASTERSHIP hMasterShip;
+ MASTER_SHIP_INFO *MasterPtr;
+
+ hMasterShip = GetStarShipFromIndex (&master_q, NewStarShip);
+ MasterPtr = LockMasterShip (&master_q, hMasterShip);
+
+ InitShipStatus (&MasterPtr->ShipInfo, NULL, NULL);
+
+ UnlockMasterShip (&master_q, hMasterShip);
+ }
+
+ UnbatchGraphics ();
+ SetContextClipRect (&OldRect);
+ SetContext (OldContext);
+}
+
+// Set the currently displayed ship to the ship for the slot indicated by
+// pMS->row and pMS->col.
+static void
+UpdateCurrentShip (MELEE_STATE *pMS)
+{
+ if (pMS->row == NUM_MELEE_ROWS)
+ {
+ // The team name is selected.
+ pMS->currentShip = MELEE_NONE;
+ }
+ else
+ {
+ FleetShipIndex slotNr = GetShipIndex (pMS->row, pMS->col);
+ pMS->currentShip =
+ MeleeSetup_getShip (pMS->meleeSetup, pMS->side, slotNr);
+ }
+
+ DrawMeleeShipStrings (pMS, pMS->currentShip);
+}
+
+// returns (COUNT) ~0 for an invalid ship.
+COUNT
+GetShipValue (MeleeShip StarShip)
+{
+ COUNT val;
+
+ if (StarShip == MELEE_NONE)
+ return 0;
+
+ val = GetShipCostFromIndex (StarShip);
+ if (val == 0)
+ val = (COUNT)~0;
+
+ return val;
+}
+
+static void
+DeleteCurrentShip (MELEE_STATE *pMS)
+{
+ FleetShipIndex slotI = GetShipIndex (pMS->row, pMS->col);
+ Melee_LocalChange_ship (pMS, pMS->side, slotI, MELEE_NONE);
+}
+
+static bool
+isShipSlotSelected (MELEE_STATE *pMS, COUNT side, FleetShipIndex index)
+{
+ if (pMS->MeleeOption != EDIT_MELEE)
+ return false;
+
+ if (pMS->side != side)
+ return false;
+
+ return (index == GetShipIndex (pMS->row, pMS->col));
+}
+
+static void
+AdvanceCursor (MELEE_STATE *pMS)
+{
+ ++pMS->col;
+ if (pMS->col == NUM_MELEE_COLUMNS)
+ {
+ ++pMS->row;
+ if (pMS->row < NUM_MELEE_ROWS)
+ pMS->col = 0;
+ else
+ {
+ pMS->col = NUM_MELEE_COLUMNS - 1;
+ pMS->row = NUM_MELEE_ROWS - 1;
+ }
+ }
+}
+
+static BOOLEAN
+OnTeamNameChange (TEXTENTRY_STATE *pTES)
+{
+ MELEE_STATE *pMS = (MELEE_STATE*) pTES->CbParam;
+ BOOLEAN ret;
+ COUNT hl = DTSHS_EDIT;
+
+ pMS->CurIndex = pTES->CursorPos;
+ if (pTES->JoystickMode)
+ hl |= DTSHS_BLOCKCUR;
+
+ ret = DrawTeamString (pMS, pMS->side, hl, pTES->BaseStr);
+
+ return ret;
+}
+
+static BOOLEAN
+TeamNameFrameCallback (TEXTENTRY_STATE *pTES)
+{
+#ifdef NETPLAY
+ // Process incoming packets, so that remote changes are displayed
+ // while we are editing the team name.
+ // The team name itself isn't modified visually due to remote changes
+ // while it is being edited.
+ netInput ();
+#endif
+
+ (void) pTES;
+
+ return TRUE;
+ // Keep editing
+}
+
+static void
+BuildPickShipPopup (MELEE_STATE *pMS)
+{
+ bool buildOk;
+
+ pMS->MeleeOption = BUILD_PICK;
+
+ buildOk = BuildPickShip (pMS);
+ if (buildOk)
+ {
+ // A ship has been selected.
+ // Add the currently selected ship to the fleet.
+ FleetShipIndex index = GetShipIndex (pMS->row, pMS->col);
+ Melee_LocalChange_ship (pMS, pMS->side, index, pMS->currentShip);
+ AdvanceCursor (pMS);
+ }
+
+ pMS->MeleeOption = EDIT_MELEE;
+ // Must set this before the call to RepairMeleeFrame(), so that
+ // it will not redraw the BuildPickFrame.
+
+ {
+ RECT r;
+
+ GetBuildPickFrameRect (&r);
+ RepairMeleeFrame (&r);
+ }
+
+ UpdateCurrentShip (pMS);
+ pMS->InputFunc = DoEdit;
+}
+
+static BOOLEAN
+DoEdit (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT | MENU_SOUND_DELETE);
+ if (!pMS->Initialized)
+ {
+ UpdateCurrentShip (pMS);
+ pMS->Initialized = TRUE;
+ pMS->InputFunc = DoEdit;
+ return TRUE;
+ }
+
+#ifdef NETPLAY
+ netInput ();
+#endif
+ if ((pMS->row < NUM_MELEE_ROWS || pMS->currentShip == MELEE_NONE)
+ && (PulsedInputState.menu[KEY_MENU_CANCEL]
+ || (PulsedInputState.menu[KEY_MENU_RIGHT]
+ && (pMS->col == NUM_MELEE_COLUMNS - 1
+ || pMS->row == NUM_MELEE_ROWS))))
+ {
+ // Done editing the teams.
+ Deselect (EDIT_MELEE);
+ pMS->currentShip = MELEE_NONE;
+ pMS->MeleeOption = START_MELEE;
+ pMS->InputFunc = DoMelee;
+ pMS->LastInputTime = GetTimeCounter ();
+ }
+ else if (pMS->row < NUM_MELEE_ROWS
+ && PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ // Show a popup to add a new ship to the current team.
+ Deselect (EDIT_MELEE);
+ BuildPickShipPopup (pMS);
+ }
+ else if (pMS->row < NUM_MELEE_ROWS
+ && PulsedInputState.menu[KEY_MENU_SPECIAL])
+ {
+ // TODO: this is a stub; Should we display a ship spin?
+ Deselect (EDIT_MELEE);
+ if (pMS->currentShip != MELEE_NONE)
+ {
+ // Do something with pMS->currentShip here
+ }
+ }
+ else if (pMS->row < NUM_MELEE_ROWS &&
+ PulsedInputState.menu[KEY_MENU_DELETE])
+ {
+ // Remove the currently selected ship from the current team.
+ Deselect (EDIT_MELEE);
+ DeleteCurrentShip (pMS);
+ AdvanceCursor (pMS);
+ UpdateCurrentShip (pMS);
+ }
+ else
+ {
+ COUNT side = pMS->side;
+ COUNT row = pMS->row;
+ COUNT col = pMS->col;
+
+ if (row == NUM_MELEE_ROWS)
+ {
+ // Edit the name of the current team.
+ if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ TEXTENTRY_STATE tes;
+ char buf[MAX_TEAM_CHARS + 1];
+
+ // going to enter text
+ pMS->CurIndex = 0;
+ DrawTeamString (pMS, pMS->side, DTSHS_EDIT, NULL);
+
+ strncpy (buf, MeleeSetup_getTeamName (
+ pMS->meleeSetup, pMS->side), MAX_TEAM_CHARS);
+ buf[MAX_TEAM_CHARS] = '\0';
+
+ tes.Initialized = FALSE;
+ tes.BaseStr = buf;
+ tes.CursorPos = 0;
+ tes.MaxSize = MAX_TEAM_CHARS + 1;
+ tes.CbParam = pMS;
+ tes.ChangeCallback = OnTeamNameChange;
+ tes.FrameCallback = TeamNameFrameCallback;
+ DoTextEntry (&tes);
+
+ // done entering
+ pMS->CurIndex = MELEE_STATE_INDEX_DONE;
+ if (!tes.Success ||
+ !Melee_LocalChange_teamName (pMS, pMS->side, buf)) {
+ // The team name was not changed, so it was not redrawn.
+ // However, because we now leave edit mode, we still
+ // need to redraw.
+ Melee_UpdateView_teamName (pMS, pMS->side);
+ }
+
+ return TRUE;
+ }
+ }
+
+ {
+ if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ if (col > 0)
+ --col;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ if (col < NUM_MELEE_COLUMNS - 1)
+ ++col;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (row-- == 0)
+ {
+ if (side == 0)
+ row = 0;
+ else
+ {
+ row = NUM_MELEE_ROWS;
+ side = !side;
+ }
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (row++ == NUM_MELEE_ROWS)
+ {
+ if (side == 1)
+ row = NUM_MELEE_ROWS;
+ else
+ {
+ row = 0;
+ side = !side;
+ }
+ }
+ }
+ }
+
+ if (col != pMS->col || row != pMS->row || side != pMS->side)
+ {
+ Deselect (EDIT_MELEE);
+ pMS->side = side;
+ pMS->row = row;
+ pMS->col = col;
+
+ UpdateCurrentShip (pMS);
+ }
+ }
+
+#ifdef NETPLAY
+ flushPacketQueues ();
+#endif
+
+ Melee_flashSelection (pMS);
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+#ifdef NETPLAY
+// Returns -1 if a connection has been aborted.
+static ssize_t
+numPlayersReady (void)
+{
+ size_t player;
+ size_t numDone;
+
+ numDone = 0;
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ if (!(PlayerControl[player] & NETWORK_CONTROL))
+ {
+ numDone++;
+ continue;
+ }
+
+ {
+ NetConnection *conn;
+
+ conn = netConnections[player];
+
+ if (conn == NULL || !NetConnection_isConnected (conn))
+ return -1;
+
+ if (NetConnection_getState (conn) > NetState_inSetup)
+ numDone++;
+ }
+ }
+
+ return numDone;
+}
+#endif /* NETPLAY */
+
+// The player has pressed "Start Game", and all Network players are
+// connected. We're now just waiting for the confirmation of the other
+// party.
+// When the other party changes something in the settings, the confirmation
+// is cancelled.
+static BOOLEAN
+DoConfirmSettings (MELEE_STATE *pMS)
+{
+#ifdef NETPLAY
+ ssize_t numDone;
+#endif
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ // The connection is explicitely cancelled, locally.
+ pMS->InputFunc = DoMelee;
+#ifdef NETPLAY
+ cancelConfirmations ();
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 4));
+ // "Confirmation cancelled. Press FIRE to reconfirm."
+#endif
+ return TRUE;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_LEFT] ||
+ PulsedInputState.menu[KEY_MENU_UP] ||
+ PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ // The player moves the cursor; cancel the confirmation.
+ pMS->InputFunc = DoMelee;
+#ifdef NETPLAY
+ cancelConfirmations ();
+ DrawMeleeStatusMessage ("");
+#endif
+ return DoMelee (pMS);
+ // Let the pressed keys take effect immediately.
+ }
+
+#ifndef NETPLAY
+ pMS->InputFunc = DoMelee;
+ SeedRandomNumbers ();
+ pMS->meleeStarted = TRUE;
+ StartMelee (pMS);
+ pMS->meleeStarted = FALSE;
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+ return TRUE;
+#else
+ closeDisconnectedConnections ();
+ netInput ();
+ SleepThread (ONE_SECOND / 120);
+
+ numDone = numPlayersReady ();
+ if (numDone == -1)
+ {
+ // Connection aborted
+ cancelConfirmations ();
+ flushPacketQueues ();
+ pMS->InputFunc = DoMelee;
+ return TRUE;
+ }
+ else if (numDone != NUM_SIDES)
+ {
+ // Still waiting for some confirmation.
+ return TRUE;
+ }
+
+ // All sides have confirmed.
+
+ // Send our own prefered frame delay.
+ Netplay_NotifyAll_inputDelay (netplayOptions.inputDelay);
+
+ // Synchronise the RNGs:
+ {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+
+ if (!(PlayerControl[player] & NETWORK_CONTROL))
+ continue;
+
+ conn = netConnections[player];
+ assert (conn != NULL);
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ if (NetConnection_getDiscriminant (conn))
+ Netplay_Notify_seedRandom (conn, SeedRandomNumbers ());
+ }
+ flushPacketQueues ();
+ }
+
+ {
+ // One side will send the seed followed by 'Done' and wait
+ // for the other side to report 'Done'.
+ // The other side will report 'Done' and will wait for the other
+ // side to report 'Done', but before the reception of 'Done'
+ // it will have received the seed.
+ bool allOk = negotiateReadyConnections (true, NetState_interBattle);
+ if (!allOk)
+ return FALSE;
+ }
+
+ // The maximum value for all connections is used.
+ {
+ bool ok = setupInputDelay (netplayOptions.inputDelay);
+ if (!ok)
+ return FALSE;
+ }
+
+ pMS->InputFunc = DoMelee;
+
+ StartMelee (pMS);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ return TRUE;
+#endif /* defined (NETPLAY) */
+}
+
+static void
+LoadMeleeInfo (MELEE_STATE *pMS)
+{
+ BuildPickMeleeFrame ();
+ MeleeFrame = CaptureDrawable (LoadGraphic (MELEE_SCREEN_PMAP_ANIM));
+ BuildBuildPickFrame ();
+
+ InitSpace ();
+
+ LoadTeamList (pMS);
+}
+
+static void
+FreeMeleeInfo (MELEE_STATE *pMS)
+{
+ DestroyDirEntryTable (ReleaseDirEntryTable (pMS->load.dirEntries));
+ pMS->load.dirEntries = 0;
+
+ if (pMS->hMusic)
+ {
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+
+ UninitSpace ();
+
+ DestroyPickMeleeFrame ();
+ DestroyDrawable (ReleaseDrawable (MeleeFrame));
+ MeleeFrame = 0;
+ DestroyBuildPickFrame ();
+
+#ifdef NETPLAY
+ closeAllConnections ();
+ // Clear the input delay in case we will go into the full game later.
+ // Must be done after the net connections are closed.
+ setupInputDelay (0);
+#endif
+}
+
+static void
+BuildAndDrawShipList (MELEE_STATE *pMS)
+{
+ FillPickMeleeFrame (pMS->meleeSetup);
+ // XXX TODO: This also builds the race_q for each player.
+ // This should be split off.
+}
+
+static void
+StartMelee (MELEE_STATE *pMS)
+{
+ {
+ FadeMusic (0, ONE_SECOND / 2);
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2)
+ + ONE_SECOND / 60);
+ FlushColorXForms ();
+ StopMusic ();
+ }
+ FadeMusic (NORMAL_VOLUME, 0);
+ if (pMS->hMusic)
+ {
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+
+ do
+ {
+ if (!SetPlayerInputAll ())
+ break;
+ BuildAndDrawShipList (pMS);
+
+ WaitForSoundEnd (TFBSOUND_WAIT_ALL);
+
+ load_gravity_well ((BYTE)((COUNT)TFB_Random () %
+ NUMBER_OF_PLANET_TYPES));
+ Battle (NULL);
+ free_gravity_well ();
+ ClearPlayerInputAll ();
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return;
+
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2)
+ + ONE_SECOND / 60);
+ FlushColorXForms ();
+
+ } while (0 /* !(GLOBAL (CurrentActivity) & CHECK_ABORT) */);
+ GLOBAL (CurrentActivity) = SUPER_MELEE;
+
+ pMS->Initialized = FALSE;
+}
+
+static void
+StartMeleeButtonPressed (MELEE_STATE *pMS)
+{
+ // Either fleet must at least have one ship.
+ if (MeleeSetup_getFleetValue (pMS->meleeSetup, 0) == 0 ||
+ MeleeSetup_getFleetValue (pMS->meleeSetup, 1) == 0)
+ {
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return;
+ }
+
+#ifdef NETPLAY
+ if ((PlayerControl[0] & NETWORK_CONTROL) &&
+ (PlayerControl[1] & NETWORK_CONTROL))
+ {
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 32));
+ // "Only one side at a time can be network controlled."
+ return;
+ }
+
+ if (((PlayerControl[0] & NETWORK_CONTROL) &&
+ (PlayerControl[1] & COMPUTER_CONTROL)) ||
+ ((PlayerControl[0] & COMPUTER_CONTROL) &&
+ (PlayerControl[1] & NETWORK_CONTROL)))
+ {
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 33));
+ // "Netplay with a computer-controlled side is currently
+ // not possible."
+ return;
+ }
+
+ // Check whether all network parties are ready;
+ {
+ COUNT player;
+ bool netReady = true;
+
+ // We collect all error conditions, instead of only reporting
+ // the first one.
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+
+ if (!(PlayerControl[player] & NETWORK_CONTROL))
+ continue;
+
+ conn = netConnections[player];
+ if (conn == NULL || !NetConnection_isConnected (conn))
+ {
+ // Connection for player not established.
+ netReady = false;
+ if (player == 0)
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 5));
+ // "Connection for bottom player not "
+ // "established."
+ else
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 6));
+ // "Connection for top player not "
+ // "established."
+ }
+ else if (NetConnection_getState (conn) != NetState_inSetup)
+ {
+ // This side may be in the setup, but the network connection
+ // is not in a state that setup information can be sent.
+ netReady = false;
+ if (player == 0)
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 14));
+ // "Connection for bottom player not ready."
+ else
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 15));
+ // "Connection for top player not ready."
+
+ }
+ }
+ if (!netReady)
+ {
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return;
+ }
+
+ if (numPlayersReady () != NUM_PLAYERS)
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 7));
+ // "Waiting for remote confirmation."
+ confirmConnections ();
+ }
+#endif
+
+ pMS->InputFunc = DoConfirmSettings;
+}
+
+#ifdef NETPLAY
+
+static BOOLEAN
+DoConnectingDialog (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+ COUNT which_side = (pMS->MeleeOption == NET_TOP) ? 1 : 0;
+ NetConnection *conn;
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ if (!pMS->Initialized)
+ {
+ RECT r;
+ FONT oldfont;
+ Color oldcolor;
+ TEXT t;
+
+ // Build a network connection.
+ if (netConnections[which_side] != NULL)
+ closePlayerNetworkConnection (which_side);
+
+ pMS->Initialized = TRUE;
+ conn = openPlayerNetworkConnection (which_side, pMS);
+ pMS->InputFunc = DoConnectingDialog;
+
+ /* Draw the dialog box here */
+ oldfont = SetContextFont (StarConFont);
+ oldcolor = SetContextForeGroundColor (BLACK_COLOR);
+ BatchGraphics ();
+ r.extent.width = 200;
+ r.extent.height = 30;
+ r.corner.x = (SCREEN_WIDTH - r.extent.width) >> 1;
+ r.corner.y = (SCREEN_HEIGHT - r.extent.height) >> 1;
+ DrawShadowedBox (&r, SHADOWBOX_BACKGROUND_COLOR,
+ SHADOWBOX_DARK_COLOR, SHADOWBOX_MEDIUM_COLOR);
+
+ if (NetConnection_getPeerOptions (conn)->isServer)
+ {
+ t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 1);
+ /* "Awaiting incoming connection */
+ }
+ else
+ {
+ t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 2);
+ /* "Awaiting outgoing connection */
+ }
+ t.baseline.y = r.corner.y + 10;
+ t.baseline.x = SCREEN_WIDTH >> 1;
+ t.align = ALIGN_CENTER;
+ t.CharCount = ~0;
+ font_DrawText (&t);
+
+ t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 18);
+ /* "Press SPACE to cancel" */
+ t.baseline.y += 16;
+ font_DrawText (&t);
+
+ // Restore original graphics
+ SetContextFont (oldfont);
+ SetContextForeGroundColor (oldcolor);
+ UnbatchGraphics ();
+ }
+
+ netInput ();
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ // Terminate a network connection.
+ if (netConnections[which_side] != NULL) {
+ closePlayerNetworkConnection (which_side);
+ UpdateMeleeStatusMessage (which_side);
+ }
+ RedrawMeleeFrame ();
+ pMS->InputFunc = DoMelee;
+ pMS->LastInputTime = GetTimeCounter ();
+
+ flushPacketQueues ();
+
+ return TRUE;
+ }
+
+ conn = netConnections[which_side];
+ if (conn != NULL)
+ {
+ NetState status = NetConnection_getState (conn);
+ if ((status == NetState_init) ||
+ (status == NetState_inSetup))
+ {
+ /* Connection complete! */
+ PlayerControl[which_side] = NETWORK_CONTROL | STANDARD_RATING;
+ DrawControls (which_side, TRUE);
+
+ RedrawMeleeFrame ();
+
+ UpdateMeleeStatusMessage (which_side);
+ pMS->InputFunc = DoMelee;
+ pMS->LastInputTime = GetTimeCounter ();
+ Deselect (pMS->MeleeOption);
+ pMS->MeleeOption = START_MELEE;
+ }
+ }
+
+ flushPacketQueues ();
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+/* Check for disconnects, and revert to human control if there is one */
+static void
+check_for_disconnects (MELEE_STATE *pMS)
+{
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+
+ if (!(PlayerControl[player] & NETWORK_CONTROL))
+ continue;
+
+ conn = netConnections[player];
+ if (conn == NULL || !NetConnection_isConnected (conn))
+ {
+ PlayerControl[player] = HUMAN_CONTROL | STANDARD_RATING;
+ DrawControls (player, FALSE);
+ log_add (log_User, "Player %d has disconnected; shifting "
+ "controls\n", player);
+ }
+ }
+
+ (void) pMS;
+}
+
+#endif
+
+static void
+nextControlType (COUNT which_side)
+{
+ switch (PlayerControl[which_side])
+ {
+ case HUMAN_CONTROL | STANDARD_RATING:
+ PlayerControl[which_side] = COMPUTER_CONTROL | STANDARD_RATING;
+ break;
+ case COMPUTER_CONTROL | STANDARD_RATING:
+ PlayerControl[which_side] = COMPUTER_CONTROL | GOOD_RATING;
+ break;
+ case COMPUTER_CONTROL | GOOD_RATING:
+ PlayerControl[which_side] = COMPUTER_CONTROL | AWESOME_RATING;
+ break;
+ case COMPUTER_CONTROL | AWESOME_RATING:
+ PlayerControl[which_side] = HUMAN_CONTROL | STANDARD_RATING;
+ break;
+
+#ifdef NETPLAY
+ case NETWORK_CONTROL | STANDARD_RATING:
+ if (netConnections[which_side] != NULL)
+ closePlayerNetworkConnection (which_side);
+ UpdateMeleeStatusMessage (-1);
+ PlayerControl[which_side] = HUMAN_CONTROL | STANDARD_RATING;
+ break;
+#endif /* NETPLAY */
+ default:
+ log_add (log_Error, "Error: Bad control type (%d) in "
+ "nextControlType().\n", PlayerControl[which_side]);
+ PlayerControl[which_side] = HUMAN_CONTROL | STANDARD_RATING;
+ break;
+ }
+
+ DrawControls (which_side, TRUE);
+}
+
+static MELEE_OPTIONS
+MeleeOptionDown (MELEE_OPTIONS current) {
+ if (current == QUIT_BOT)
+ return QUIT_BOT;
+ return current + 1;
+}
+
+static MELEE_OPTIONS
+MeleeOptionUp (MELEE_OPTIONS current)
+{
+ if (current == TOP_ENTRY)
+ return TOP_ENTRY;
+ return current - 1;
+}
+
+static void
+MeleeOptionSelect (MELEE_STATE *pMS)
+{
+ switch (pMS->MeleeOption)
+ {
+ case START_MELEE:
+ StartMeleeButtonPressed (pMS);
+ break;
+ case LOAD_TOP:
+ case LOAD_BOT:
+ pMS->Initialized = FALSE;
+ pMS->side = pMS->MeleeOption == LOAD_TOP ? 0 : 1;
+ DoLoadTeam (pMS);
+ break;
+ case SAVE_TOP:
+ case SAVE_BOT:
+ pMS->side = pMS->MeleeOption == SAVE_TOP ? 0 : 1;
+ if (MeleeSetup_getFleetValue (pMS->meleeSetup, pMS->side) > 0)
+ DoSaveTeam (pMS);
+ else
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ break;
+ case QUIT_BOT:
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ break;
+#ifdef NETPLAY
+ case NET_TOP:
+ case NET_BOT:
+ {
+ COUNT which_side;
+ BOOLEAN confirmed;
+
+ which_side = pMS->MeleeOption == NET_TOP ? 1 : 0;
+ confirmed = MeleeConnectDialog (which_side);
+ RedrawMeleeFrame ();
+ pMS->LastInputTime = GetTimeCounter ();
+ if (confirmed)
+ {
+ pMS->Initialized = FALSE;
+ pMS->InputFunc = DoConnectingDialog;
+ }
+ break;
+ }
+#endif /* NETPLAY */
+ case CONTROLS_TOP:
+ case CONTROLS_BOT:
+ {
+ COUNT which_side = (pMS->MeleeOption == CONTROLS_TOP) ? 1 : 0;
+ nextControlType (which_side);
+ break;
+ }
+ }
+}
+
+BOOLEAN
+DoMelee (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+ BOOLEAN force_select = FALSE;
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ if (!pMS->Initialized)
+ {
+ if (pMS->hMusic)
+ {
+ StopMusic ();
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+ pMS->hMusic = LoadMusic (MELEE_MUSIC);
+ pMS->Initialized = TRUE;
+
+ pMS->MeleeOption = START_MELEE;
+ PlayMusic (pMS->hMusic, TRUE, 1);
+ InitMelee (pMS);
+
+ FadeScreen (FadeAllToColor, ONE_SECOND / 2);
+ pMS->LastInputTime = GetTimeCounter ();
+ return TRUE;
+ }
+
+#ifdef NETPLAY
+ netInput ();
+#endif
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL] ||
+ PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ // Start editing the teams.
+ pMS->LastInputTime = GetTimeCounter ();
+ Deselect (pMS->MeleeOption);
+ pMS->MeleeOption = EDIT_MELEE;
+ pMS->Initialized = FALSE;
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ pMS->side = 0;
+ pMS->row = 0;
+ pMS->col = 0;
+ }
+ else
+ {
+ pMS->side = 0;
+ pMS->row = NUM_MELEE_ROWS - 1;
+ pMS->col = NUM_MELEE_COLUMNS - 1;
+ }
+ DoEdit (pMS);
+ }
+ else
+ {
+ MELEE_OPTIONS NewMeleeOption;
+
+ NewMeleeOption = pMS->MeleeOption;
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ pMS->LastInputTime = GetTimeCounter ();
+ NewMeleeOption = MeleeOptionUp (pMS->MeleeOption);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ pMS->LastInputTime = GetTimeCounter ();
+ NewMeleeOption = MeleeOptionDown (pMS->MeleeOption);
+ }
+
+ if ((PlayerControl[0] & PlayerControl[1] & PSYTRON_CONTROL)
+ && GetTimeCounter () - pMS->LastInputTime > ONE_SECOND * 10)
+ {
+ force_select = TRUE;
+ NewMeleeOption = START_MELEE;
+ }
+
+ if (NewMeleeOption != pMS->MeleeOption)
+ {
+#ifdef NETPLAY
+ if (pMS->MeleeOption == CONTROLS_TOP ||
+ pMS->MeleeOption == CONTROLS_BOT)
+ UpdateMeleeStatusMessage (-1);
+#endif
+ Deselect (pMS->MeleeOption);
+ pMS->MeleeOption = NewMeleeOption;
+ Select (pMS->MeleeOption);
+#ifdef NETPLAY
+ if (NewMeleeOption == CONTROLS_TOP ||
+ NewMeleeOption == CONTROLS_BOT)
+ {
+ COUNT side = (NewMeleeOption == CONTROLS_TOP) ? 1 : 0;
+ if (PlayerControl[side] & NETWORK_CONTROL)
+ UpdateMeleeStatusMessage (side);
+ else
+ UpdateMeleeStatusMessage (-1);
+ }
+#endif
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT] || force_select)
+ {
+ MeleeOptionSelect (pMS);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+ }
+ }
+
+#ifdef NETPLAY
+ flushPacketQueues ();
+
+ check_for_disconnects (pMS);
+#endif
+
+ Melee_flashSelection (pMS);
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+static int
+LoadMeleeConfig (MELEE_STATE *pMS)
+{
+ uio_Stream *stream;
+ int status;
+ COUNT side;
+
+ stream = uio_fopen (configDir, "melee.cfg", "rb");
+ if (stream == NULL)
+ goto err;
+
+ {
+ struct stat sb;
+
+ if (uio_fstat(uio_streamHandle(stream), &sb) == -1)
+ goto err;
+ if ((size_t) sb.st_size != (1 + MeleeTeam_serialSize) * NUM_SIDES)
+ goto err;
+ }
+
+ for (side = 0; side < NUM_SIDES; side++)
+ {
+ status = uio_getc (stream);
+ if (status == EOF)
+ goto err;
+ PlayerControl[side] = (BYTE) status;
+ // XXX: insert sanity check on PlanetControl here.
+
+ if (MeleeSetup_deserializeTeam (pMS->meleeSetup, side, stream) == -1)
+ goto err;
+
+ /* Do not allow netplay mode at the start. */
+ if (PlayerControl[side] & NETWORK_CONTROL)
+ PlayerControl[side] = HUMAN_CONTROL | STANDARD_RATING;
+ }
+
+ uio_fclose (stream);
+ return 0;
+
+err:
+ if (stream)
+ uio_fclose (stream);
+ return -1;
+}
+
+static int
+WriteMeleeConfig (MELEE_STATE *pMS)
+{
+ uio_Stream *stream;
+ COUNT side;
+
+ stream = res_OpenResFile (configDir, "melee.cfg", "wb");
+ if (stream == NULL)
+ goto err;
+
+ for (side = 0; side < NUM_SIDES; side++)
+ {
+ if (uio_putc (PlayerControl[side], stream) == EOF)
+ goto err;
+
+ if (MeleeSetup_serializeTeam (pMS->meleeSetup, side, stream) == -1)
+ goto err;
+ }
+
+ if (!res_CloseResFile (stream))
+ goto err;
+
+ return 0;
+
+err:
+ if (stream)
+ {
+ res_CloseResFile (stream);
+ DeleteResFile (configDir, "melee.cfg");
+ }
+ return -1;
+}
+
+void
+Melee (void)
+{
+ InitGlobData ();
+ {
+ MELEE_STATE MenuState;
+
+ pMeleeState = &MenuState;
+ memset (pMeleeState, 0, sizeof (*pMeleeState));
+
+ MenuState.InputFunc = DoMelee;
+ MenuState.Initialized = FALSE;
+
+ MenuState.meleeSetup = MeleeSetup_new ();
+
+ MenuState.randomContext = RandomContext_New ();
+ RandomContext_SeedRandom (MenuState.randomContext,
+ GetTimeCounter ());
+ // Using the current time still leaves the random state a bit
+ // predictable, but it is good enough.
+
+#ifdef NETPLAY
+ {
+ COUNT player;
+ for (player = 0; player < NUM_PLAYERS; player++)
+ netConnections[player] = NULL;
+ }
+#endif
+
+ MenuState.currentShip = MELEE_NONE;
+ MenuState.CurIndex = MELEE_STATE_INDEX_DONE;
+ InitMeleeLoadState (&MenuState);
+
+ GLOBAL (CurrentActivity) = SUPER_MELEE;
+
+ GameSounds = CaptureSound (LoadSound (GAME_SOUNDS));
+ LoadMeleeInfo (&MenuState);
+ if (LoadMeleeConfig (&MenuState) == -1)
+ {
+ PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING;
+ Melee_LocalChange_team (&MenuState, 0,
+ MenuState.load.preBuiltList[0]);
+ PlayerControl[1] = COMPUTER_CONTROL | STANDARD_RATING;
+ Melee_LocalChange_team (&MenuState, 1,
+ MenuState.load.preBuiltList[1]);
+ }
+
+ MenuState.side = 0;
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ DoInput (&MenuState, TRUE);
+
+ StopMusic ();
+ WaitForSoundEnd (TFBSOUND_WAIT_ALL);
+
+ WriteMeleeConfig (&MenuState);
+ FreeMeleeInfo (&MenuState);
+ DestroySound (ReleaseSound (GameSounds));
+ GameSounds = 0;
+
+ UninitMeleeLoadState (&MenuState);
+
+ RandomContext_Delete (MenuState.randomContext);
+
+ MeleeSetup_delete (MenuState.meleeSetup);
+
+ FlushInput ();
+ }
+}
+
+#ifdef NETPLAY
+void
+updateRandomSeed (MELEE_STATE *pMS, COUNT side, DWORD seed)
+{
+ TFB_SeedRandom (seed);
+ (void) pMS;
+ (void) side;
+}
+
+// The remote player has done something which invalidates our confirmation.
+void
+confirmationCancelled (MELEE_STATE *pMS, COUNT side)
+{
+ if (side == 0)
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 16));
+ // "Bottom player changed something -- need to reconfirm."
+ else
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 17));
+ // "Top player changed something -- need to reconfirm."
+
+ if (pMS->InputFunc == DoConfirmSettings)
+ pMS->InputFunc = DoMelee;
+}
+
+static void
+connectionFeedback (NetConnection *conn, const char *str, bool forcePopup) {
+ struct battlestate_struct *bs = NetMelee_getBattleState (conn);
+
+ if (bs == NULL && !forcePopup)
+ {
+ // bs == NULL means the game has not started yet.
+ DrawMeleeStatusMessage (str);
+ }
+ else
+ {
+ DoPopupWindow (str);
+ }
+}
+
+void
+connectedFeedback (NetConnection *conn) {
+ if (NetConnection_getPlayerNr (conn) == 0)
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 8),
+ false);
+ // "Bottom player is connected."
+ else
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 9),
+ false);
+ // "Top player is connected."
+
+ PlayMenuSound (MENU_SOUND_INVOKED);
+}
+
+static const char *
+abortReasonString (NetplayAbortReason reason)
+{
+ switch (reason)
+ {
+ case AbortReason_unspecified:
+ return GAME_STRING (NETMELEE_STRING_BASE + 25);
+ // "Disconnect for an unspecified reason.'
+ case AbortReason_versionMismatch:
+ return GAME_STRING (NETMELEE_STRING_BASE + 26);
+ // "Connection aborted due to version mismatch."
+ case AbortReason_invalidHash:
+ return GAME_STRING (NETMELEE_STRING_BASE + 27);
+ // "Connection aborted because the remote side sent a "
+ // "fake signature."
+ case AbortReason_protocolError:
+ return GAME_STRING (NETMELEE_STRING_BASE + 28);
+ // "Connection aborted due to an internal protocol "
+ // "error."
+ }
+
+ return NULL;
+ // Should not happen.
+}
+
+void
+abortFeedback (NetConnection *conn, NetplayAbortReason reason)
+{
+ const char *msg;
+
+ msg = abortReasonString (reason);
+ if (msg != NULL)
+ connectionFeedback (conn, msg, true);
+}
+
+static const char *
+resetReasonString (NetplayResetReason reason)
+{
+ switch (reason)
+ {
+ case ResetReason_unspecified:
+ return GAME_STRING (NETMELEE_STRING_BASE + 29);
+ // "Game aborted for an unspecified reason."
+ case ResetReason_syncLoss:
+ return GAME_STRING (NETMELEE_STRING_BASE + 30);
+ // "Game aborted due to loss of synchronisation."
+ case ResetReason_manualReset:
+ return GAME_STRING (NETMELEE_STRING_BASE + 31);
+ // "Game aborted by the remote player."
+ }
+
+ return NULL;
+ // Should not happen.
+}
+
+void
+resetFeedback (NetConnection *conn, NetplayResetReason reason,
+ bool byRemote)
+{
+ const char *msg;
+
+ flushPacketQueues ();
+ // If the local side queued a reset packet as a result of a
+ // remote reset, that packet will not have been sent yet.
+ // We flush the queue now, so that the remote side won't be
+ // waiting for the reset packet while this side is waiting
+ // for an acknowledgement of the feedback message.
+
+ if (reason == ResetReason_manualReset && !byRemote) {
+ // No message needed, the player initiated the reset.
+ return;
+ }
+
+ msg = resetReasonString (reason);
+ if (msg != NULL)
+ connectionFeedback (conn, msg, false);
+
+ // End supermelee. This must not be done before connectionFeedback(),
+ // otherwise the message will immediately disappear.
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+}
+
+void
+errorFeedback (NetConnection *conn)
+{
+ if (NetConnection_getPlayerNr (conn) == 0)
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 10),
+ false);
+ // "Bottom player: connection failed."
+ else
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 11),
+ false);
+ // "Top player: connection failed."
+}
+
+void
+closeFeedback (NetConnection *conn)
+{
+ if (NetConnection_getPlayerNr (conn) == 0)
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 12),
+ false);
+ // "Bottom player: connection closed."
+ else
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 13),
+ false);
+ // "Top player: connection closed."
+}
+
+#endif /* NETPLAY */
+
+
+///////////////////////////////////////////////////////////////////////////
+
+// Melee_UpdateView_xxx() functions are called when some value in the
+// supermelee fleet setup screen needs to be updated visually.
+
+static void
+Melee_UpdateView_fleetValue (MELEE_STATE *pMS, COUNT side)
+{
+ if (pMS->meleeStarted)
+ return;
+
+ DrawFleetValue (pMS, side, DTSHS_REPAIR);
+ // BUG: The fleet value is always drawn as deselected.
+}
+
+static void
+Melee_UpdateView_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index)
+{
+ MeleeShip ship;
+
+ if (pMS->meleeStarted)
+ return;
+
+ ship = MeleeSetup_getShip (pMS->meleeSetup, side, index);
+
+ if (ship == MELEE_NONE)
+ {
+ ClearShipBox (side, index);
+ }
+ else
+ {
+ DrawShipBox (side, index, ship, FALSE);
+ }
+}
+
+static void
+Melee_UpdateView_teamName (MELEE_STATE *pMS, COUNT side)
+{
+ if (pMS->meleeStarted)
+ return;
+
+ DrawTeamString (pMS, side, DTSHS_REPAIR, NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+// Melee_Change_xxx() functions are helper functions, called when some value
+// in the supermelee fleet setup screen has changed, eithed because of a
+// local change, or a remote change.
+
+static bool
+Melee_Change_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index,
+ MeleeShip ship)
+{
+ if (!MeleeSetup_setShip (pMS->meleeSetup, side, index, ship))
+ {
+ // No change.
+ return false;
+ }
+
+ // Update the view
+ Melee_UpdateView_ship (pMS, side, index);
+ Melee_UpdateView_fleetValue (pMS, side);
+
+ // If the modified slot is currently selected, display the new ship icon
+ // on the right of the screen.
+ if (isShipSlotSelected (pMS, side, index))
+ {
+ pMS->currentShip = ship;
+ DrawMeleeShipStrings (pMS, ship);
+ }
+
+ return true;
+}
+
+// Pre: 'name' is '\0'-terminated
+static bool
+Melee_Change_teamName (MELEE_STATE *pMS, COUNT side, const char *name)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ if (!MeleeSetup_setTeamName (setup, side, name))
+ {
+ // No change.
+ return false;
+ }
+
+ if (pMS->row != NUM_MELEE_ROWS || pMS->side != side ||
+ pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
+ {
+ // The team name is not currently being edited, so we can
+ // update it on screen. If it was edited, then this function
+ // will be called again after it is done.
+ Melee_UpdateView_teamName (pMS, side);
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+// Melee_LocalChange_xxx() functions are called when some value in the
+// supermelee fleet setup screen has changed because of a local action.
+// The behavior of these functions (and the comments therein) follow the
+// description in doc/devel/netplay/protocol.
+
+bool
+Melee_LocalChange_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index,
+ MeleeShip ship)
+{
+ if (!Melee_Change_ship (pMS, side, index, ship))
+ return false;
+
+#ifdef NETPLAY
+ {
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ MeleeShip sentShip = MeleeSetup_getSentShip (setup, side, index);
+ if (sentShip == MELEE_UNSET)
+ {
+ // State 1.
+ // Notify network connections of the change.
+ Netplay_NotifyAll_setShip (pMS, side, index);
+ MeleeSetup_setSentShip (setup, side, index, ship);
+ }
+ }
+#endif /* NETPLAY */
+
+ return true;
+}
+
+
+// Pre: 'name' is '\0'-terminated
+bool
+Melee_LocalChange_teamName (MELEE_STATE *pMS, COUNT side, const char *name)
+{
+ if (!Melee_Change_teamName (pMS, side, name))
+ return false;
+
+#ifdef NETPLAY
+ {
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ const char *sentName = MeleeSetup_getSentTeamName (setup, side);
+ if (sentName == NULL)
+ {
+ // State 1.
+ // Notify network connections of the change.
+ Netplay_NotifyAll_setTeamName (pMS, side);
+ MeleeSetup_setSentTeamName (setup, side, name);
+ }
+ }
+#endif /* NETPLAY */
+
+ return true;
+}
+
+bool
+Melee_LocalChange_fleet (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeShip *fleet)
+{
+ FleetShipIndex slotI;
+ bool changed = false;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ if (Melee_LocalChange_ship (pMS, teamNr, slotI, fleet[slotI]))
+ changed = true;
+ }
+ return changed;
+}
+
+bool
+Melee_LocalChange_team (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeTeam *team)
+{
+ const MeleeShip *fleet = MeleeTeam_getFleet (team);
+ const char *name = MeleeTeam_getTeamName (team);
+ bool changed = false;
+
+ if (Melee_LocalChange_fleet (pMS, teamNr, fleet))
+ changed = true;
+ if (Melee_LocalChange_teamName (pMS, teamNr, name))
+ changed = true;
+
+ return changed;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+#ifdef NETPLAY
+
+// Send the entire team to the remote side. Used when the connection has
+// just been established, or after the setup menu is reentered after battle.
+void
+Melee_bootstrapSyncTeam (MELEE_STATE *meleeState, size_t teamNr)
+{
+ MeleeSetup *setup = meleeState->meleeSetup;
+ FleetShipIndex slotI;
+ const char *teamName;
+
+ // Send the current fleet.
+ Netplay_NotifyAll_setFleet(meleeState, teamNr);
+
+ // Update the last sent fleet.
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ MeleeShip ship = MeleeSetup_getShip (setup, teamNr, slotI);
+ assert (MeleeSetup_getSentShip (setup, teamNr, slotI) == MELEE_UNSET);
+ MeleeSetup_setSentShip (setup, teamNr, slotI, ship);
+ }
+
+ // Send the current team name.
+ Netplay_NotifyAll_setTeamName (meleeState, teamNr);
+
+ // Update the last sent team name.
+ teamName = MeleeSetup_getTeamName (setup, teamNr);
+ MeleeSetup_setSentTeamName (setup, teamNr, teamName);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+// Melee_RemoteChange_xxx() functions are called when some value in the
+// supermelee fleet setup screen has changed remotely.
+// The behavior of these functions (and the comments therein) follow the
+// description in doc/devel/netplay/protocol.
+
+void
+Melee_RemoteChange_ship (MELEE_STATE *pMS, NetConnection *conn, COUNT side,
+ FleetShipIndex index, MeleeShip ship)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ MeleeShip sentShip = MeleeSetup_getSentShip (setup, side, index);
+ MeleeShip currentShip;
+
+ if (sentShip == MELEE_UNSET)
+ {
+ // State 1
+
+ // Change the ship locally.
+ Melee_Change_ship (pMS, side, index, ship);
+
+ // Notify the remote side.
+ Netplay_NotifyAll_setShip (pMS, side, index);
+
+ // A packet has now been received and sent. End of turn.
+ return;
+ }
+
+ // A packet has been sent and received. End of turn.
+ MeleeSetup_setSentShip (setup, side, index, MELEE_UNSET);
+
+ if (ship != sentShip)
+ {
+ // Rule 2c or 3d. The value which we sent is different from the value
+ // which the opponent sent. We need a tie-breaker to determine which
+ // value prevails.
+ if (NetConnection_getPlayerNr (conn) != side)
+ {
+ // Rule 2c+ or 3d+
+ // We win the tie-breaker. The value which we sent prevails.
+ }
+ else
+ {
+ // Rule 2c- or 3d-.
+ // We lose the tie-breaker. We adopt the remote value.
+ Melee_Change_ship (pMS, side, index, ship);
+ return;
+ }
+ }
+ /*
+ else
+ {
+ // Rule 2b or 3c. The value which we sent is the value which
+ // the opponent sent. This confirms the value.
+ }
+ */
+
+ // Rule 2b, 2c+, 3c, or 3d+. The value which we sent is confirmed.
+
+ currentShip = MeleeSetup_getShip (setup, side, index);
+ if (currentShip != sentShip)
+ {
+ // Rule 3c or 3d+. We had a local change which was yet
+ // unreported.
+
+ // Notify the remote side and keep track of what we sent.
+ Netplay_NotifyAll_setShip (pMS, side, index);
+ MeleeSetup_setSentShip (setup, side, index, ship);
+ }
+}
+
+void
+Melee_RemoteChange_teamName (MELEE_STATE *pMS, NetConnection *conn,
+ COUNT side, const char *newName)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ const char *sentName = MeleeSetup_getSentTeamName (setup, side);
+ const char *currentName;
+
+ if (sentName == NULL)
+ {
+ // State 1
+
+ // Change the team name locally.
+ Melee_Change_teamName (pMS, side, newName);
+
+ // Notify the remote side.
+ Netplay_NotifyAll_setTeamName (pMS, side);
+
+ // A packet has now been received and sent. End of turn.
+ // The sent team name is still unset, so we don't have to reset it.
+ return;
+ }
+
+ if (strcmp (newName, sentName) == 0)
+ {
+ // Rule 2c or 3d. The value which we sent is different from the value
+ // which the opponent sent. We need a tie-breaker to determine which
+ // value prevails.
+ if (NetConnection_getPlayerNr (conn) != side)
+ {
+ // Rule 2c+ or 3d+
+ // We win the tie-breaker. The value which we sent prevails.
+ }
+ else
+ {
+ // Rule 2c- or 3d-.
+ // We lose the tie-breaker. We adopt the remote value.
+ Melee_Change_teamName (pMS, side, newName);
+ MeleeSetup_setSentTeamName (setup, side, NULL);
+ return;
+ }
+ }
+ /*
+ else
+ {
+ // Rule 2b or 3c. The value which we sent is the value which
+ // the opponent sent. This confirms the value.
+ }
+ */
+
+ // Rule 2b, 2c+, 3c, or 3d+. The value which we sent is confirmed.
+
+ currentName = MeleeSetup_getTeamName (setup, side);
+ if (strcmp (currentName, sentName) != 0)
+ {
+ // Rule 3c or 3d+. We had a local change which was yet
+ // unreported.
+
+ // A packet has been sent and received, which ends the turn.
+ // We don't bother clearing the sent team name, as we're going
+ // to send a new packet immediately.
+
+ // Notify the remote side and keep track of what we sent.
+ Netplay_NotifyAll_setTeamName (pMS, side);
+
+ // Update the last sent message.
+ MeleeSetup_setSentTeamName (setup, side, newName);
+ }
+ else
+ {
+ // A packet has been sent and received. End of turn.
+ MeleeSetup_setSentTeamName (setup, side, NULL);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+#endif /* NETPLAY */
+
diff --git a/src/uqm/supermelee/melee.h b/src/uqm/supermelee/melee.h
new file mode 100644
index 0000000..e6026a3
--- /dev/null
+++ b/src/uqm/supermelee/melee.h
@@ -0,0 +1,144 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_SUPERMELEE_MELEE_H_
+#define UQM_SUPERMELEE_MELEE_H_
+
+#include "../init.h"
+#include "libs/gfxlib.h"
+#include "libs/mathlib.h"
+#include "libs/sndlib.h"
+#include "libs/timelib.h"
+#include "libs/reslib.h"
+#include "netplay/packet.h"
+ // for NetplayAbortReason and NetplayResetReason.
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct melee_state MELEE_STATE;
+
+#define NUM_MELEE_ROWS 2
+#define NUM_MELEE_COLUMNS 7
+//#define NUM_MELEE_COLUMNS 6
+#define MELEE_FLEET_SIZE (NUM_MELEE_ROWS * NUM_MELEE_COLUMNS)
+#define ICON_WIDTH 16
+#define ICON_HEIGHT 16
+
+extern FRAME PickMeleeFrame;
+
+#define PICK_BG_COLOR BUILD_COLOR (MAKE_RGB15 (0x00, 0x01, 0x0F), 0x01)
+#define PICK_VALUE_COLOR BUILD_COLOR (MAKE_RGB15 (0x13, 0x00, 0x00), 0x2C)
+ // Used for the current fleet value in the next ship selection
+ // in SuperMelee.
+
+#define MAX_TEAM_CHARS 30
+#define NUM_PICK_COLS 5
+#define NUM_PICK_ROWS 5
+
+typedef BYTE MELEE_OPTIONS;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#include "loadmele.h"
+#include "meleesetup.h"
+#include "meleeship.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct melee_state
+{
+ BOOLEAN (*InputFunc) (struct melee_state *pInputState);
+
+ BOOLEAN Initialized;
+ BOOLEAN meleeStarted;
+ MELEE_OPTIONS MeleeOption;
+ COUNT side;
+ COUNT row;
+ COUNT col;
+ MeleeSetup *meleeSetup;
+ struct melee_load_state load;
+ MeleeShip currentShip;
+ // The ship currently displayed. Not really needed.
+ // Also the current ship position when selecting a ship.
+ COUNT CurIndex;
+#define MELEE_STATE_INDEX_DONE ((COUNT) -1)
+ // Current position in the team string when editing it.
+ // Set to MELEE_STATE_INDEX_DONE when done.
+ BOOLEAN buildPickConfirmed;
+ // Used by DoPickShip () to communicate to the calling
+ // function BuildPickShip() whether a ship has been selected
+ // to add to the fleet, or whether the operation has been
+ // cancelled. If a ship was selected, it is set in
+ // currentShip.
+ RandomContext *randomContext;
+ /* RNG state for all local random decisions, i.e. those
+ * decisions that are not shared among network parties. */
+ TimeCount LastInputTime;
+
+ MUSIC_REF hMusic;
+};
+
+extern void Melee (void);
+
+// Some prototypes for use by loadmele.c:
+BOOLEAN DoMelee (MELEE_STATE *pMS);
+void DrawMeleeIcon (COUNT which_icon);
+void GetShipBox (RECT *pRect, COUNT side, COUNT row, COUNT col);
+void RepairMeleeFrame (const RECT *pRect);
+void DrawMeleeShipStrings (MELEE_STATE *pMS, MeleeShip NewStarShip);
+extern FRAME MeleeFrame;
+void Melee_flashSelection (MELEE_STATE *pMS);
+
+COUNT GetShipValue (MeleeShip StarShip);
+
+void updateRandomSeed (MELEE_STATE *pMS, COUNT side, DWORD seed);
+void confirmationCancelled(MELEE_STATE *pMS, COUNT side);
+void connectedFeedback (NetConnection *conn);
+void abortFeedback (NetConnection *conn, NetplayAbortReason reason);
+void resetFeedback (NetConnection *conn, NetplayResetReason reason,
+ bool byRemote);
+void errorFeedback (NetConnection *conn);
+void closeFeedback (NetConnection *conn);
+
+bool Melee_LocalChange_ship (MELEE_STATE *pMS, COUNT side,
+ FleetShipIndex index, MeleeShip ship);
+bool Melee_LocalChange_teamName (MELEE_STATE *pMS, COUNT side,
+ const char *name);
+bool Melee_LocalChange_fleet (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeShip *fleet);
+bool Melee_LocalChange_team (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeTeam *team);
+
+void Melee_bootstrapSyncTeam (MELEE_STATE *pMS, size_t teamNr);
+
+void Melee_RemoteChange_ship (MELEE_STATE *pMS, NetConnection *conn,
+ COUNT side, FleetShipIndex index, MeleeShip ship);
+void Melee_RemoteChange_teamName (MELEE_STATE *pMS, NetConnection *conn,
+ COUNT side, const char *name);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_MELEE_H_ */
diff --git a/src/uqm/supermelee/meleesetup.c b/src/uqm/supermelee/meleesetup.c
new file mode 100644
index 0000000..a45f172
--- /dev/null
+++ b/src/uqm/supermelee/meleesetup.c
@@ -0,0 +1,440 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define MELEESETUP_INTERNAL
+#include "port.h"
+#include "meleesetup.h"
+
+#include "../master.h"
+#include "libs/log.h"
+
+
+///////////////////////////////////////////////////////////////////////////
+
+// Temporary
+const size_t MeleeTeam_serialSize = MELEE_FLEET_SIZE +
+ sizeof (((MeleeTeam*)0)->name);
+
+void
+MeleeTeam_init (MeleeTeam *team)
+{
+ FleetShipIndex slotI;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ team->ships[slotI] = MELEE_NONE;
+
+ team->name[0] = '\0';
+}
+
+void
+MeleeTeam_uninit (MeleeTeam *team)
+{
+ (void) team;
+}
+
+MeleeTeam *
+MeleeTeam_new (void)
+{
+ MeleeTeam *result = HMalloc (sizeof (MeleeTeam));
+ MeleeTeam_init (result);
+ return result;
+}
+
+void
+MeleeTeam_delete (MeleeTeam *team)
+{
+ MeleeTeam_uninit (team);
+ HFree (team);
+}
+
+int
+MeleeTeam_serialize (const MeleeTeam *team, uio_Stream *stream)
+{
+ FleetShipIndex slotI;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++) {
+ if (uio_putc ((int) team->ships[slotI], stream) == EOF)
+ return -1;
+ }
+ if (uio_fwrite ((const char *) team->name, sizeof team->name, 1,
+ stream) != 1)
+ return -1;
+
+ return 0;
+}
+
+int
+MeleeTeam_deserialize (MeleeTeam *team, uio_Stream *stream)
+{
+ FleetShipIndex slotI;
+
+ // Sanity check on the ships.
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ int ship = uio_getc (stream);
+ if (ship == EOF)
+ goto err;
+ team->ships[slotI] = (MeleeShip) ship;
+
+ if (team->ships[slotI] == MELEE_NONE)
+ continue;
+
+ if (team->ships[slotI] >= NUM_MELEE_SHIPS)
+ {
+ log_add (log_Warning, "Invalid ship type in loaded team (index "
+ "%d, ship type is %d, max valid is %d).",
+ slotI, team->ships[slotI], NUM_MELEE_SHIPS - 1);
+ team->ships[slotI] = MELEE_NONE;
+ }
+ }
+
+ if (uio_fread (team->name, sizeof team->name, 1, stream) != 1)
+ goto err;
+
+ team->name[MAX_TEAM_CHARS] = '\0';
+
+ return 0;
+
+err:
+ MeleeTeam_delete(team);
+ return -1;
+}
+
+// XXX: move this to elsewhere?
+COUNT
+MeleeTeam_getValue (const MeleeTeam *team)
+{
+ COUNT total = 0;
+ FleetShipIndex slotI;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ MeleeShip ship = team->ships[slotI];
+ COUNT shipValue = GetShipValue (ship);
+ if (shipValue == (COUNT)~0)
+ {
+ // Invalid ship.
+ continue;
+ }
+ total += shipValue;
+ }
+
+ return total;
+}
+
+MeleeShip
+MeleeTeam_getShip (const MeleeTeam *team, FleetShipIndex slotNr)
+{
+ return team->ships[slotNr];
+}
+
+void
+MeleeTeam_setShip (MeleeTeam *team, FleetShipIndex slotNr, MeleeShip ship)
+{
+ team->ships[slotNr] = ship;
+}
+
+const MeleeShip *
+MeleeTeam_getFleet (const MeleeTeam *team)
+{
+ return team->ships;
+}
+
+const char *
+MeleeTeam_getTeamName (const MeleeTeam *team)
+{
+ return team->name;
+}
+
+// Returns true iff the state has actually changed.
+void
+MeleeTeam_setName (MeleeTeam *team, const char *name)
+{
+ strncpy (team->name, name, sizeof team->name - 1);
+ team->name[sizeof team->name - 1] = '\0';
+}
+
+void
+MeleeTeam_copy (MeleeTeam *copy, const MeleeTeam *original)
+{
+ *copy = *original;
+}
+
+#if 0
+bool
+MeleeTeam_isEqual (const MeleeTeam *team1, const MeleeTeam *team2)
+{
+ const MeleeShip *fleet1;
+ const MeleeShip *fleet2;
+ FleetShipIndex slotI;
+
+ if (strcmp (team1->name, team2->name) != 0)
+ return false;
+
+ fleet1 = team1->ships;
+ fleet2 = team2->ships;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ if (fleet1[slotI] != fleet2[slotI])
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+
+#ifdef NETPLAY
+static void
+MeleeSetup_initSentTeam (MeleeSetup *setup, size_t teamNr)
+{
+ MeleeTeam *team = &setup->sentTeams[teamNr];
+ FleetShipIndex slotI;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ MeleeTeam_setShip (team, slotI, MELEE_UNSET);
+
+ setup->haveSentTeamName[teamNr] = false;
+#ifdef DEBUG
+ // The actual team name should be irrelevant if haveSentTeamName is
+ // set to false. In a debug build, we set it to invalid, so that
+ // it is more likely that it will be noticed if it is ever used.
+ MeleeTeam_setName (team, "<INVALID>");
+#endif /* DEBUG */
+}
+#endif /* NETPLAY */
+
+MeleeSetup *
+MeleeSetup_new (void)
+{
+ size_t teamI;
+ MeleeSetup *result = HMalloc (sizeof (MeleeSetup));
+ if (result == NULL)
+ return NULL;
+
+ for (teamI = 0; teamI < NUM_SIDES; teamI++)
+ {
+ MeleeTeam_init (&result->teams[teamI]);
+ result->fleetValue[teamI] = 0;
+#ifdef NETPLAY
+ MeleeSetup_initSentTeam (result, teamI);
+#endif /* NETPLAY */
+ }
+ return result;
+}
+
+void
+MeleeSetup_delete (MeleeSetup *setup)
+{
+ HFree (setup);
+}
+
+#ifdef NETPLAY
+void
+MeleeSetup_resetSentTeams (MeleeSetup *setup)
+{
+ size_t teamI;
+
+ for (teamI = 0; teamI < NUM_SIDES; teamI++)
+ MeleeSetup_initSentTeam (setup, teamI);
+}
+#endif /* NETPLAY */
+
+// Returns true iff the state has actually changed.
+bool
+MeleeSetup_setShip (MeleeSetup *setup, size_t teamNr, FleetShipIndex slotNr,
+ MeleeShip ship)
+{
+ MeleeTeam *team = &setup->teams[teamNr];
+ MeleeShip oldShip = MeleeTeam_getShip (team, slotNr);
+
+ if (ship == oldShip)
+ return false;
+
+ if (oldShip != MELEE_NONE)
+ setup->fleetValue[teamNr] -= GetShipCostFromIndex (oldShip);
+
+ MeleeTeam_setShip (team, slotNr, ship);
+
+ if (ship != MELEE_NONE)
+ setup->fleetValue[teamNr] += GetShipCostFromIndex (ship);
+
+ return true;
+}
+
+MeleeShip
+MeleeSetup_getShip (const MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr)
+{
+ return MeleeTeam_getShip (&setup->teams[teamNr], slotNr);
+}
+
+const MeleeShip *
+MeleeSetup_getFleet (const MeleeSetup *setup, size_t teamNr)
+{
+ return MeleeTeam_getFleet (&setup->teams[teamNr]);
+}
+
+// Returns true iff the state has actually changed.
+bool
+MeleeSetup_setTeamName (MeleeSetup *setup, size_t teamNr,
+ const char *name)
+{
+ MeleeTeam *team = &setup->teams[teamNr];
+ const char *oldName = MeleeTeam_getTeamName (team);
+
+ if (strcmp (oldName, name) == 0)
+ return false;
+
+ MeleeTeam_setName (team, name);
+ return true;
+}
+
+// NB. This function returns a pointer to a static buffer, which is
+// overwritten by calls to MeleeSetup_setTeamName().
+const char *
+MeleeSetup_getTeamName (const MeleeSetup *setup, size_t teamNr)
+{
+ return MeleeTeam_getTeamName (&setup->teams[teamNr]);
+}
+
+COUNT
+MeleeSetup_getFleetValue (const MeleeSetup *setup, size_t teamNr)
+{
+ return setup->fleetValue[teamNr];
+}
+
+int
+MeleeSetup_deserializeTeam (MeleeSetup *setup, size_t teamNr,
+ uio_Stream *stream)
+{
+ MeleeTeam *team = &setup->teams[teamNr];
+ int ret = MeleeTeam_deserialize (team, stream);
+ if (ret == 0)
+ setup->fleetValue[teamNr] = MeleeTeam_getValue (team);
+ return ret;
+}
+
+int
+MeleeSetup_serializeTeam (const MeleeSetup *setup, size_t teamNr,
+ uio_Stream *stream)
+{
+ const MeleeTeam *team = &setup->teams[teamNr];
+ return MeleeTeam_serialize (team, stream);
+}
+
+#ifdef NETPLAY
+MeleeShip
+MeleeSetup_getSentShip (const MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr)
+{
+ return MeleeTeam_getShip (&setup->sentTeams[teamNr], slotNr);
+}
+
+// Returns NULL if there is no team name set. This is not the same
+// as when an empty (zero-length) team name is set.
+// NB. This function returns a pointer to a static buffer, which is
+// overwritten by calls to MeleeSetup_setSentTeamName().
+const char *
+MeleeSetup_getSentTeamName (const MeleeSetup *setup, size_t teamNr)
+{
+ if (!setup->haveSentTeamName[teamNr])
+ return NULL;
+
+ return MeleeTeam_getTeamName (&setup->sentTeams[teamNr]);
+}
+
+// Returns true iff the state has actually changed.
+bool
+MeleeSetup_setSentShip (MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr, MeleeShip ship)
+{
+ MeleeTeam *team = &setup->sentTeams[teamNr];
+ MeleeShip oldShip = MeleeTeam_getShip (team, slotNr);
+
+ if (ship == oldShip)
+ return false;
+
+ MeleeTeam_setShip (team, slotNr, ship);
+ return true;
+}
+
+// Returns true iff the state has actually changed.
+// 'name' can be NULL to indicate that no team name set. This is not the same
+// as when an empty (zero-length) team name is set.
+bool
+MeleeSetup_setSentTeamName (MeleeSetup *setup, size_t teamNr,
+ const char *name)
+{
+ bool haveSentName = setup->haveSentTeamName[teamNr];
+
+ if (name == NULL)
+ {
+ if (!haveSentName)
+ {
+ // Had not sent a team name, and still haven't.
+ return false;
+ }
+
+#ifdef DEBUG
+ {
+ // The actual team name should be irrelevant if haveSentTeamName
+ // is set to false. In a debug build, we set it to invalid, so
+ // that it is more likely that it will be noticed if it is ever
+ // used.
+ MeleeTeam *team = &setup->sentTeams[teamNr];
+ MeleeTeam_setName (team, "<INVALID>");
+ }
+#endif
+ }
+ else
+ {
+ MeleeTeam *team = &setup->sentTeams[teamNr];
+
+ if (haveSentName)
+ {
+ // Have sent a team name. Check whether it has actually changed.
+ const char *oldName = MeleeTeam_getTeamName (team);
+ if (strcmp (oldName, name) == 0)
+ return false; // Team name has not changed.
+ }
+
+ MeleeTeam_setName (team, name);
+ }
+
+ setup->haveSentTeamName[teamNr] = (name != NULL);
+
+ return true;
+}
+
+#if 0
+bool
+MeleeSetup_isTeamSent (MeleeSetup *setup, size_t teamNr)
+{
+ MeleeTeam *localTeam = &setup->teams[teamNr];
+ MeleeTeam *sentTeam = &setup->sentTeams[teamNr];
+
+ return MeleeTeam_isEqual (localTeam, sentTeam);
+}
+#endif
+
+#endif /* NETPLAY */
+
+///////////////////////////////////////////////////////////////////////////
+
+
diff --git a/src/uqm/supermelee/meleesetup.h b/src/uqm/supermelee/meleesetup.h
new file mode 100644
index 0000000..0097d92
--- /dev/null
+++ b/src/uqm/supermelee/meleesetup.h
@@ -0,0 +1,143 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MELEESETUP_H
+#define MELEESETUP_H
+
+typedef struct MeleeTeam MeleeTeam;
+typedef struct MeleeSetup MeleeSetup;
+
+#ifdef MELEESETUP_INTERNAL
+# define MELEETEAM_INTERNAL
+#endif /* MELEESETUP_INTERNAL */
+
+#include "libs/compiler.h"
+
+typedef COUNT FleetShipIndex;
+
+#include "libs/uio.h"
+#include "melee.h"
+#include "meleeship.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#ifdef MELEETEAM_INTERNAL
+struct MeleeTeam
+{
+ MeleeShip ships[MELEE_FLEET_SIZE];
+ char name[MAX_TEAM_CHARS + 1 + 24];
+ /* The +1 is for the terminating \0; the +24 is in case some
+ * default name in starcon.txt is unknowingly mangled. */
+ // XXX: SvdB: Why would it be mangled? Why don't we just reject
+ // it if it is? Is this so that we have some space
+ // for multibyte UTF-8 chars?
+};
+#endif /* MELEETEAM_INTERNAL */
+
+#ifdef MELEESETUP_INTERNAL
+struct MeleeSetup
+{
+ MeleeTeam teams[NUM_SIDES];
+ COUNT fleetValue[NUM_SIDES];
+#ifdef NETPLAY
+ MeleeTeam sentTeams[NUM_SIDES];
+ // The last sent (parts of) teams.
+ // Used in the Update protocol. See doc/devel/netplay/protocol
+ // XXX: this may actually be deallocated when the battle starts.
+ bool haveSentTeamName[NUM_SIDES];
+ // Whether we have sent a team name this 'turn'.
+ // Used in the Update protocol. See doc/devel/netplay/protocol
+ // (also for the term 'turn').
+#endif
+};
+
+#endif /* MELEESETUP_INTERNAL */
+
+extern const size_t MeleeTeam_serialSize;
+
+void MeleeTeam_init (MeleeTeam *team);
+void MeleeTeam_uninit (MeleeTeam *team);
+MeleeTeam *MeleeTeam_new (void);
+void MeleeTeam_delete (MeleeTeam *team);
+#ifdef NETPLAY
+void MeleeSetup_resetSentTeams (MeleeSetup *setup);
+#endif /* NETPLAY */
+int MeleeTeam_serialize (const MeleeTeam *team, uio_Stream *stream);
+int MeleeTeam_deserialize (MeleeTeam *team, uio_Stream *stream);
+COUNT MeleeTeam_getValue (const MeleeTeam *team);
+MeleeShip MeleeTeam_getShip (const MeleeTeam *team, FleetShipIndex slotNr);
+void MeleeTeam_setShip (MeleeTeam *team, FleetShipIndex slotNr,
+ MeleeShip ship);
+const MeleeShip *MeleeTeam_getFleet (const MeleeTeam *team);
+const char *MeleeTeam_getTeamName (const MeleeTeam *team);
+void MeleeTeam_setName (MeleeTeam *team, const char *name);
+void MeleeTeam_copy (MeleeTeam *copy, const MeleeTeam *original);
+#if 0
+bool MeleeTeam_isEqual (const MeleeTeam *team1, const MeleeTeam *team2);
+#endif
+
+#ifdef NETPLAY
+MeleeShip MeleeSetup_getSentShip (const MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr);
+const char *MeleeSetup_getSentTeamName (const MeleeSetup *setup,
+ size_t teamNr);
+bool MeleeSetup_setSentShip (MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr, MeleeShip ship);
+bool MeleeSetup_setSentTeamName (MeleeSetup *setup, size_t teamNr,
+ const char *name);
+#if 0
+bool MeleeSetup_isTeamSent (MeleeSetup *setup, size_t teamNr);
+#endif
+#endif /* NETPLAY */
+
+MeleeSetup *MeleeSetup_new (void);
+void MeleeSetup_delete (MeleeSetup *setup);
+
+bool MeleeSetup_setShip (MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr, MeleeShip ship);
+MeleeShip MeleeSetup_getShip (const MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr);
+bool MeleeSetup_setFleet (MeleeSetup *setup, size_t teamNr,
+ const MeleeShip *fleet);
+const MeleeShip *MeleeSetup_getFleet (const MeleeSetup *setup, size_t teamNr);
+bool MeleeSetup_setTeamName (MeleeSetup *setup, size_t teamNr,
+ const char *name);
+const char *MeleeSetup_getTeamName (const MeleeSetup *setup,
+ size_t teamNr);
+COUNT MeleeSetup_getFleetValue (const MeleeSetup *setup, size_t teamNr);
+int MeleeSetup_deserializeTeam (MeleeSetup *setup, size_t teamNr,
+ uio_Stream *stream);
+int MeleeSetup_serializeTeam (const MeleeSetup *setup, size_t teamNr,
+ uio_Stream *stream);
+
+
+void MeleeState_setShip (MELEE_STATE *pMS, size_t teamNr,
+ FleetShipIndex slotNr, MeleeShip ship);
+void MeleeState_setFleet (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeShip *fleet);
+void MeleeState_setTeamName (MELEE_STATE *pMS, size_t teamNr,
+ const char *name);
+void MeleeState_setTeam (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeTeam *team);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* MELEESETUP_H */
+
diff --git a/src/uqm/supermelee/meleeship.h b/src/uqm/supermelee/meleeship.h
new file mode 100644
index 0000000..e917b75
--- /dev/null
+++ b/src/uqm/supermelee/meleeship.h
@@ -0,0 +1,55 @@
+#ifndef MELEESHIP_H
+#define MELEESHIP_H
+
+#include "types.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef enum MeleeShip {
+ MELEE_ANDROSYNTH,
+ MELEE_ARILOU,
+ MELEE_CHENJESU,
+ MELEE_CHMMR,
+ MELEE_DRUUGE,
+ MELEE_EARTHLING,
+ MELEE_ILWRATH,
+ MELEE_KOHR_AH,
+ MELEE_MELNORME,
+ MELEE_MMRNMHRM,
+ MELEE_MYCON,
+ MELEE_ORZ,
+ MELEE_PKUNK,
+ MELEE_SHOFIXTI,
+ MELEE_SLYLANDRO,
+ MELEE_SPATHI,
+ MELEE_SUPOX,
+ MELEE_SYREEN,
+ MELEE_THRADDASH,
+ MELEE_UMGAH,
+ MELEE_URQUAN,
+ MELEE_UTWIG,
+ MELEE_VUX,
+ MELEE_YEHAT,
+ MELEE_ZOQFOTPIK,
+
+ MELEE_UNSET = ((BYTE) ~0) - 1,
+ // Used with the Update protocol, to register in the sentTeam
+ MELEE_NONE = (BYTE) ~0
+ // Empty fleet position.
+} MeleeShip;
+#define NUM_MELEE_SHIPS (MELEE_ZOQFOTPIK + 1)
+
+static inline bool
+MeleeShip_valid (MeleeShip ship)
+{
+ return (ship < NUM_MELEE_SHIPS) || (ship == MELEE_NONE);
+}
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* MELEESHIP_H */
+
diff --git a/src/uqm/supermelee/netplay/FILES b/src/uqm/supermelee/netplay/FILES
new file mode 100644
index 0000000..7b93fd1
--- /dev/null
+++ b/src/uqm/supermelee/netplay/FILES
@@ -0,0 +1,50 @@
+In netplay/:
+crc.{c,h} Generic CRC routines.
+checkbuf.{c,h} Buffer of checksums.
+checksum.{c,h} Routines for checksumming the game state.
+netconnection.{c,h} Definition of NetConnection, being the state of a
+ network connection, and operations on it.
+nc_connect.ci Part of netconnection.c that handles establishing
+ connections.
+netinput.{c,h} Definitions and operations for melee input commands
+ over a network connection.
+netmelee.{c,h} Keeps track of network connections used in the game,
+ with methods to control them all at once.
+ Functions that directly access the game data are kept
+ in the relevant files (melee.c, pickmele.c, battle.c).
+netmisc.{c,h} Miscelaneous functions that didn't fit in elsewhere.
+netoptions.{c,h} Description of a network connection to be established.
+netplay.h Some global netplay definitions.
+netrcv.{c,h} Processes incoming packets. Does know about the protocol
+ and will do consistency checking.
+ Does not directly manipulate the game state.
+netsend.{c,h} Enqueues all sorts of packets for sending.
+ Does not know about the protocol and hence will do
+ no checks for protocol sanity.
+netstate.{c,h} Definitions of the states of a network connection.
+notify.{c,h} Routines for notifying a remote side of local changes.
+ Knows about the protocal and has assert()s to
+ check for local consistency.
+packet.{c,h} Definition and creation of packets. Create functions
+ should only be called from netsend.c.
+packethandlers.{c,h} Routines for processing each type of incoming packet.
+packetq.{c,h} Manages the packet queue.
+packetsenders.{c,h} Creates and sends/queues packets.
+
+In netplay/proto/:
+npconfirm.{c,h} Functions for handing the 'confirmation' protocol.
+ready.{c,h} Functions for handling the 'ready' protocol.
+reset.{c,h} Functions for handling the 'reset' protocol.
+
+
+
+
+
+TODO:
+Division:
+- files that interface with sockets and knows nothing of the game
+ (these are in libs/network)
+- files that know of sockets and the game but don't directly interface
+ with either
+- files that interface with the game and know nothing of sockets
+
diff --git a/src/uqm/supermelee/netplay/Makeinfo b/src/uqm/supermelee/netplay/Makeinfo
new file mode 100644
index 0000000..ff31011
--- /dev/null
+++ b/src/uqm/supermelee/netplay/Makeinfo
@@ -0,0 +1,4 @@
+uqm_SUBDIRS="proto"
+uqm_CFILES="checkbuf.c checksum.c crc.c netconnection.c netinput.c netmelee.c netmisc.c netoptions.c netrcv.c netsend.c netstate.c notify.c notifyall.c packet.c packethandlers.c packetsenders.c packetq.c"
+uqm_HFILES="checkbuf.h checksum.h crc.h netconnection.h netinput.h netmelee.h netmisc.h netoptions.h netplay.h netrcv.h netsend.h netstate.h notifyall.h notify.h packet.h packethandlers.h packetq.h packetsenders.h"
+
diff --git a/src/uqm/supermelee/netplay/checkbuf.c b/src/uqm/supermelee/netplay/checkbuf.c
new file mode 100644
index 0000000..e9c5a32
--- /dev/null
+++ b/src/uqm/supermelee/netplay/checkbuf.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#include "port.h"
+
+#include "netplay.h"
+#include "checkbuf.h"
+#include "libs/log.h"
+
+#include "../../battle.h"
+ // for battleFrameCount
+
+
+#include <errno.h>
+#include <stdlib.h>
+
+
+
+static inline BattleFrameCounter
+ChecksumBuffer_getCurrentFrameNr(void) {
+ return battleFrameCount;
+}
+
+void
+ChecksumBuffer_init(ChecksumBuffer *cb, size_t delay, size_t interval) {
+ // The input buffer lags BattleInput_inputDelay frames behind,
+ // but only every interval frames will there be a checksum to be
+ // checked.
+
+ // Checksums will be checked when 'frameNr % interval == 0'.
+ // (and frameNr is zero-based).
+ // The checksum of frame n will be processed in frame 'n + delay'.
+
+ // In the worst case, side 1 processes frames 'n' through 'n + delay - 1',
+ // then blocks in frame 'n + delay' (after sending a checksum, if that's
+ // pertinent for that frame).
+ // Then side 2 receives all this input and these checksums, and
+ // progresses to 'delay' frames after the last frame the received input
+ // originated from, and blocks in the frame after it (after sending a
+ // checksum, if that's pertinent for the frame).
+ // So it sent input and checksums for frames 'n' through
+ // 'n + delay + delay + 1'.
+ // The input and checksums for these '2*delay + 2' frames are still
+ // unhandled by side 1, so it needs buffer space for this.
+ // With checksums only sent every interval frames, the buffer space
+ // needed will be 'roundUp(2*delay + 2)' spaces.
+
+ size_t bufSize = ((2 * delay + 2) + (interval - 1)) / interval;
+
+ {
+#ifdef NETPLAY_DEBUG
+ size_t i;
+#endif
+
+ cb->checksums = malloc(bufSize * sizeof (ChecksumEntry));
+ cb->maxSize = bufSize;
+ cb->interval = interval;
+
+#ifdef NETPLAY_DEBUG
+ for (i = 0; i < bufSize; i++) {
+ cb->checksums[i].checksum = 0;
+ cb->checksums[i].frameNr = (BattleFrameCounter) -1;
+ }
+#endif
+ }
+}
+
+void
+ChecksumBuffer_uninit(ChecksumBuffer *cb) {
+ if (cb->checksums != NULL) {
+ free(cb->checksums);
+ cb->checksums = NULL;
+ }
+}
+
+// Returns the entry that would be used for the checksum for the specified
+// frame. Whether the entry is actually valid is not checked.
+static ChecksumEntry *
+ChecksumBuffer_getChecksumEntry(ChecksumBuffer *cb,
+ BattleFrameCounter frameNr) {
+ size_t index;
+ ChecksumEntry *entry;
+
+ assert(frameNr % cb->interval == 0);
+ // We only record checksums exactly every 'interval' frames.
+
+ index = (frameNr / cb->interval) % cb->maxSize;
+ entry = &cb->checksums[index];
+
+ return entry;
+}
+
+bool
+ChecksumBuffer_addChecksum(ChecksumBuffer *cb, BattleFrameCounter frameNr,
+ Checksum checksum) {
+ ChecksumEntry *entry;
+
+ assert(frameNr % cb->interval == 0);
+
+ entry = ChecksumBuffer_getChecksumEntry(cb, frameNr);
+
+#ifdef NETPLAY_DEBUG
+ entry->frameNr = frameNr;
+#endif
+ entry->checksum = checksum;
+ return true;
+}
+
+// Pre: frameNr is within the range of the checksums stored in cb.
+bool
+ChecksumBuffer_getChecksum(ChecksumBuffer *cb, BattleFrameCounter frameNr,
+ Checksum *result) {
+ ChecksumEntry *entry;
+
+ entry = ChecksumBuffer_getChecksumEntry(cb, frameNr);
+
+#ifdef NETPLAY_DEBUG
+ if (frameNr != entry->frameNr) {
+ log_add(log_Error, "Checksum buffer entry for requested frame %u "
+ "(still?) contains a checksum for frame %u.\n",
+ frameNr, entry->frameNr);
+ return false;
+ }
+#endif
+
+ *result = entry->checksum;
+ return true;
+}
+
diff --git a/src/uqm/supermelee/netplay/checkbuf.h b/src/uqm/supermelee/netplay/checkbuf.h
new file mode 100644
index 0000000..f609448
--- /dev/null
+++ b/src/uqm/supermelee/netplay/checkbuf.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_
+#define UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct ChecksumEntry ChecksumEntry;
+typedef struct ChecksumBuffer ChecksumBuffer;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#include "../../battle.h"
+ // for BattleFrameCounter
+#include "checksum.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+struct ChecksumEntry {
+#ifdef NETPLAY_DEBUG
+ BattleFrameCounter frameNr;
+ // The number of the frame this checksum originated from.
+ // If the checksumming code is working correctly, the checksum
+ // can only come from one frame, so this value is not needed
+ // for normal operation.
+ // Its only use is to detect some cases where checksumming code
+ // is *not* working correctly.
+#endif
+ Checksum checksum;
+};
+
+struct ChecksumBuffer {
+ ChecksumEntry *checksums;
+ // Cyclic buffer. if 'size' > 0, then 'first' is an index to
+ // the first used entry, 'size' is the number of used
+ // entries in the buffer, and (first + size) % maxSize is the
+ // index to just past the end of the buffer.
+ size_t maxSize;
+
+ size_t interval;
+};
+
+void ChecksumBuffer_init(ChecksumBuffer *cb, size_t delay, size_t interval);
+void ChecksumBuffer_uninit(ChecksumBuffer *cb);
+bool ChecksumBuffer_addChecksum(ChecksumBuffer *cb,
+ BattleFrameCounter frameNr, Checksum checksum);
+bool ChecksumBuffer_getChecksum(ChecksumBuffer *cb,
+ BattleFrameCounter frameNr, Checksum *result);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_ */
diff --git a/src/uqm/supermelee/netplay/checksum.c b/src/uqm/supermelee/netplay/checksum.c
new file mode 100644
index 0000000..5d687f0
--- /dev/null
+++ b/src/uqm/supermelee/netplay/checksum.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef NETPLAY
+
+#include "checksum.h"
+#include "netoptions.h"
+
+#ifdef NETPLAY_CHECKSUM
+
+#include "checkbuf.h"
+#include "crc.h"
+ // for DUMP_CRC_OPS
+#include "netconnection.h"
+#include "netmelee.h"
+#include "libs/log.h"
+#include "libs/mathlib.h"
+
+ChecksumBuffer localChecksumBuffer;
+
+void
+crc_processEXTENT(crc_State *state, const EXTENT *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processEXTENT().");
+#endif
+ crc_processCOORD(state, val->width);
+ crc_processCOORD(state, val->height);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processEXTENT().");
+#endif
+}
+
+void
+crc_processVELOCITY_DESC(crc_State *state, const VELOCITY_DESC *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processVELOCITY_DESC().");
+#endif
+ crc_processCOUNT(state, val->TravelAngle);
+ crc_processEXTENT(state, &val->vector);
+ crc_processEXTENT(state, &val->fract);
+ crc_processEXTENT(state, &val->error);
+ crc_processEXTENT(state, &val->incr);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processVELOCITY_DESC().");
+#endif
+}
+
+void
+crc_processPOINT(crc_State *state, const POINT *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processPOINT().");
+#endif
+ crc_processCOORD(state, val->x);
+ crc_processCOORD(state, val->y);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processPOINT().");
+#endif
+}
+
+#if 0
+void
+crc_processSTAMP(crc_State *state, const STAMP *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processSTAMP().");
+#endif
+ crc_processPOINT(state, val->origin);
+ crc_processFRAME(state, val->frame);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processSTAMP().");
+#endif
+}
+
+void
+crc_processINTERSECT_CONTROL(crc_State *state, const INTERSECT_CONTROL *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processINTERSECT_CONTROL().");
+#endif
+ crc_processTIME_VALUE(state, val->last_time_val);
+ crc_processPOINT(state, &val->EndPoint);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processINTERSECT_CONTROL().");
+#endif
+}
+#endif
+
+void
+crc_processSTATE(crc_State *state, const STATE *val) {
+ crc_processPOINT(state, &val->location);
+}
+
+void
+crc_processELEMENT(crc_State *state, const ELEMENT *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processELEMENT().");
+#endif
+ if (val->state_flags & BACKGROUND_OBJECT) {
+ // The element never influences the state of other elements,
+ // and is to be excluded from checksums.
+#ifdef DUMP_CRC_OPS
+ crc_log(" BACKGROUND_OBJECT element omited");
+#endif
+ } else {
+ crc_processELEMENT_FLAGS(state, val->state_flags);
+ crc_processCOUNT(state, val->life_span);
+ crc_processCOUNT(state, val->crew_level);
+ crc_processBYTE(state, val->mass_points);
+ crc_processBYTE(state, val->turn_wait);
+ crc_processBYTE(state, val->thrust_wait);
+ crc_processVELOCITY_DESC(state, &val->velocity);
+ crc_processSTATE(state, &val->current);
+ crc_processSTATE(state, &val->next);
+ }
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processELEMENT().");
+#endif
+}
+
+void
+crc_processDispQueue(crc_State *state) {
+ HELEMENT element;
+ HELEMENT nextElement;
+
+#ifdef DUMP_CRC_OPS
+ size_t i = 0;
+ crc_log("START crc_processDispQueue().");
+#endif
+ for (element = GetHeadElement(); element != 0; element = nextElement) {
+ ELEMENT *elementPtr;
+
+#ifdef DUMP_CRC_OPS
+ crc_log("===== disp_q[%d]:", i);
+#endif
+ LockElement(element, &elementPtr);
+
+ crc_processELEMENT(state, elementPtr);
+
+ nextElement = GetSuccElement(elementPtr);
+ UnlockElement(element);
+#ifdef DUMP_CRC_OPS
+ i++;
+#endif
+ }
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processDispQueue().");
+#endif
+}
+
+void
+crc_processRNG(crc_State *state) {
+ DWORD seed;
+
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processRNG().");
+#endif
+
+ seed = TFB_SeedRandom(0);
+ // This modifies the seed too.
+ crc_processDWORD(state, seed);
+ TFB_SeedRandom(seed);
+ // Restore the old seed.
+
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processRNG().");
+#endif
+}
+
+void
+crc_processState(crc_State *state) {
+#ifdef DUMP_CRC_OPS
+ crc_log("--------------------\n"
+ "START crc_processState() (frame %u).", battleFrameCount);
+#endif
+
+ crc_processRNG(state);
+ crc_processDispQueue(state);
+
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processState() (frame %u).",
+ battleFrameCount);
+#endif
+}
+
+void
+initChecksumBuffers(void) {
+ size_t player;
+
+ for (player = 0; player < NETPLAY_NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+ ChecksumBuffer *cb;
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ cb = NetConnection_getChecksumBuffer(conn);
+ ChecksumBuffer_init(cb, getBattleInputDelay(),
+ NETPLAY_CHECKSUM_INTERVAL);
+ }
+
+ ChecksumBuffer_init(&localChecksumBuffer, getBattleInputDelay(),
+ NETPLAY_CHECKSUM_INTERVAL);
+}
+
+void
+uninitChecksumBuffers(void)
+{
+ size_t player;
+
+ for (player = 0; player < NETPLAY_NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+ ChecksumBuffer *cb;
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ cb = NetConnection_getChecksumBuffer(conn);
+
+ ChecksumBuffer_uninit(cb);
+ }
+
+ ChecksumBuffer_uninit(&localChecksumBuffer);
+}
+
+void
+addLocalChecksum(BattleFrameCounter frameNr, Checksum checksum) {
+ assert(frameNr == battleFrameCount);
+
+ ChecksumBuffer_addChecksum(&localChecksumBuffer, frameNr, checksum);
+}
+
+void
+addRemoteChecksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum) {
+ ChecksumBuffer *cb;
+
+ assert(frameNr <= battleFrameCount + getBattleInputDelay() + 1);
+ assert(frameNr + getBattleInputDelay() >= battleFrameCount);
+
+ cb = NetConnection_getChecksumBuffer(conn);
+ ChecksumBuffer_addChecksum(cb, frameNr, checksum);
+}
+
+bool
+verifyChecksums(BattleFrameCounter frameNr) {
+ Checksum localChecksum;
+ size_t player;
+
+ if (!ChecksumBuffer_getChecksum(&localChecksumBuffer, frameNr,
+ &localChecksum)) {
+ // Right now, we require that a checksum is present.
+ // If/when we move to UDP, and packets may get lost, we may prefer
+ // not to do any checks in this case.
+ return false;
+ }
+
+ for (player = 0; player < NETPLAY_NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+ ChecksumBuffer *cb;
+ Checksum remoteChecksum;
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ cb = NetConnection_getChecksumBuffer(conn);
+
+ if (!ChecksumBuffer_getChecksum(cb, frameNr, &remoteChecksum))
+ return false;
+
+ if (localChecksum != remoteChecksum) {
+ log_add(log_Error, "Network connections have gone out of "
+ "sync.\n");
+ return false;
+ }
+ }
+ return true;
+}
+
+
+#endif /* NETPLAY_CHECKSUM */
+
+#endif /* NETPLAY */
+
diff --git a/src/uqm/supermelee/netplay/checksum.h b/src/uqm/supermelee/netplay/checksum.h
new file mode 100644
index 0000000..cfb48d6
--- /dev/null
+++ b/src/uqm/supermelee/netplay/checksum.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_
+#define UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_
+
+
+#include "types.h"
+
+typedef uint32 Checksum;
+
+
+#include "netplay.h"
+#include "crc.h"
+
+#include "../../element.h"
+#include "libs/gfxlib.h"
+
+#include "netconnection.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+static inline void
+crc_processELEMENT_FLAGS(crc_State *state, ELEMENT_FLAGS val) {
+ crc_processUint16(state, (uint16) val);
+}
+
+static inline void
+crc_processCOUNT(crc_State *state, COUNT val) {
+ crc_processUint16(state, (uint16) val);
+}
+
+static inline void
+crc_processBYTE(crc_State *state, BYTE val) {
+ crc_processUint8(state, (uint8) val);
+}
+
+static inline void
+crc_processDWORD(crc_State *state, DWORD val) {
+ crc_processUint32(state, (uint32) val);
+}
+
+static inline void
+crc_processCOORD(crc_State *state, COORD val) {
+ crc_processUint16(state, (uint16) val);
+}
+
+#if 0
+static inline void
+crc_processTIME_VALUE(crc_State *state, const TIME_VALUE val) {
+ crc_processUint16(state, (uint16) val);
+}
+#endif
+
+void crc_processEXTENT(crc_State *state, const EXTENT *val);
+void crc_processVELOCITY_DESC(crc_State *state, const VELOCITY_DESC *val);
+void crc_processPOINT(crc_State *state, const POINT *val);
+#if 0
+void crc_processSTAMP(crc_State *state, const STAMP *val);
+void crc_processINTERSECT_CONTROL(crc_State *state,
+ const INTERSECT_CONTROL *val);
+#endif
+void crc_processSTATE(crc_State *state, const STATE *val);
+void crc_processELEMENT(crc_State *state, const ELEMENT *val);
+void crc_processDispQueue(crc_State *state);
+void crc_processRNG(crc_State *state);
+void crc_processState(crc_State *state);
+
+
+void initChecksumBuffers(void);
+void uninitChecksumBuffers(void);
+void addLocalChecksum(BattleFrameCounter frameNr, Checksum checksum);
+void addRemoteChecksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum);
+bool verifyChecksums(BattleFrameCounter frameNr);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_ */
diff --git a/src/uqm/supermelee/netplay/crc.c b/src/uqm/supermelee/netplay/crc.c
new file mode 100644
index 0000000..677b36f
--- /dev/null
+++ b/src/uqm/supermelee/netplay/crc.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+ // For DUMP_CRC_OPS
+
+#include "crc.h"
+
+#ifdef DUMP_CRC_OPS
+# include "libs/log.h"
+#endif
+
+
+// CRC table for Polynomial 0x04c11db7 (0xedb88320 reversed)
+uint32 crcTable[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+void
+crc_init(crc_State *state) {
+ state->crc = 0xffffffff;
+}
+
+void
+crc_processBytes(crc_State *state, uint8 *buf, size_t bufLen) {
+ uint8 *end = buf + bufLen;
+ uint32 newCrc = state->crc;
+
+ while (buf < end)
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ *buf) & 0xff];
+
+#ifdef DUMP_CRC_OPS
+ crc_log("crc_processBytes(%08x, [%zu bytes]) --> %08x.",
+ state->crc, bufLen, newCrc);
+#endif
+ state->crc = newCrc;
+}
+
+void
+crc_processUint8(crc_State *state, uint8 val) {
+ uint32 newCrc = state->crc;
+
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ val) & 0xff];
+#ifdef DUMP_CRC_OPS
+ crc_log("crc_processUint8(%08x, %02x) --> %08x.",
+ state->crc, (int) val, newCrc);
+#endif
+ state->crc = newCrc;
+}
+
+void
+crc_processUint16(crc_State *state, uint16 val) {
+ uint32 newCrc = state->crc;
+
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val & 0xff)) & 0xff];
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val >> 8) ) & 0xff];
+#ifdef DUMP_CRC_OPS
+ crc_log("crc_processUint16(%08x, %04x) --> %08x.",
+ state->crc, (int) val, newCrc);
+#endif
+ state->crc = newCrc;
+}
+
+void
+crc_processUint32(crc_State *state, uint32 val) {
+ uint32 newCrc = state->crc;
+
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val & 0xff)) & 0xff];
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 8) & 0xff)) & 0xff];
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 16) & 0xff)) & 0xff];
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 24) )) & 0xff];
+
+#ifdef DUMP_CRC_OPS
+ crc_log("crc_processUint32(%08x, %08x) --> %08x.",
+ state->crc, (int) val, newCrc);
+#endif
+ state->crc = newCrc;
+}
+
+uint32
+crc_finish(const crc_State *state) {
+ return ~state->crc;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/crc.h b/src/uqm/supermelee/netplay/crc.h
new file mode 100644
index 0000000..1744d11
--- /dev/null
+++ b/src/uqm/supermelee/netplay/crc.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_CRC_H_
+#define UQM_SUPERMELEE_NETPLAY_CRC_H_
+
+typedef struct crc_State crc_State;
+
+#include "types.h"
+
+#include <stddef.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct crc_State {
+ uint32 crc;
+};
+
+void crc_init(crc_State *state);
+void crc_processBytes(crc_State *state, uint8 *buf, size_t bufLen);
+void crc_processUint8(crc_State *state, uint8 val);
+void crc_processUint16(crc_State *state, uint16 val);
+void crc_processUint32(crc_State *state, uint32 val);
+uint32 crc_finish(const crc_State *state);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef DUMP_CRC_OPS
+#include "netconnection.h"
+ // for netplayDebugFile
+//#define crc_log(...) log_add (logDebug, __VA_ARGS__)
+#define crc_log(...) if (netplayDebugFile != NULL) \
+ { \
+ uio_fprintf (netplayDebugFile, __VA_ARGS__); \
+ uio_putc ('\n', netplayDebugFile); \
+ } else \
+ (void) 0
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_CRC_H_ */
+
diff --git a/src/uqm/supermelee/netplay/nc_connect.ci b/src/uqm/supermelee/netplay/nc_connect.ci
new file mode 100644
index 0000000..6c67fed
--- /dev/null
+++ b/src/uqm/supermelee/netplay/nc_connect.ci
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// This file is part of netconnection.c, from where it is #included.
+
+static inline ConnectStateData *ConnectStateData_alloc(void);
+static inline ConnectStateData *ConnectStateData_new(void);
+static inline void ConnectStateData_free(ConnectStateData *connectStateData);
+static void ConnectStateData_delete(ConnectStateData *connectStateData);
+
+static int NetConnection_go(NetConnection *conn);
+static ListenState *NetConnection_serverGo(NetConnection *conn);
+static ConnectState *NetConnection_clientGo(NetConnection *conn);
+static void NetConnection_connected(NetConnection *conn);
+static void NetConnection_connectedServerCallback(ListenState *listenState,
+ NetDescriptor *listenNd, NetDescriptor *newNd,
+ const struct sockaddr *addr, SOCKLEN_T addrLen);
+static void NetConnection_connectedClientCallback(ConnectState *connectState,
+ NetDescriptor *newNd, const struct sockaddr *addr, SOCKLEN_T addrLen);
+static void NetConnection_connectedServerErrorCallback(
+ ListenState *listenState, const ListenError *listenError);
+static void NetConnection_connectedClientErrorCallback(
+ ConnectState *connectState, const ConnectError *connectError);
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+static inline ConnectStateData *
+ConnectStateData_alloc(void) {
+ return (ConnectStateData *) malloc(sizeof (ConnectStateData));
+}
+
+static inline ConnectStateData *
+ConnectStateData_new(void) {
+ ConnectStateData *connectStateData = ConnectStateData_alloc();
+ connectStateData->releaseFunction =
+ (NetConnectionStateData_ReleaseFunction) ConnectStateData_delete;
+ return connectStateData;
+}
+
+static inline void
+ConnectStateData_free(ConnectStateData *connectStateData) {
+ free(connectStateData);
+}
+
+static void
+ConnectStateData_delete(ConnectStateData *connectStateData) {
+ if (connectStateData->isServer) {
+ ListenState *listenState = connectStateData->state.listenState;
+ ListenState_close(listenState);
+ } else {
+ ConnectState *connectState = connectStateData->state.connectState;
+ ConnectState_close(connectState);
+ }
+ ConnectStateData_free(connectStateData);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+static int
+NetConnection_go(NetConnection *conn) {
+ ConnectStateData *connectStateData;
+
+ if (NetConnection_isConnected(conn))
+ return 0;
+
+ if (conn->options->isServer) {
+ ListenState *listenState;
+ listenState = NetConnection_serverGo(conn);
+ if (listenState == NULL) {
+ // errno is set
+ return -1;
+ }
+ connectStateData = ConnectStateData_new();
+ connectStateData->state.listenState = listenState;
+ } else {
+ ConnectState *connectState;
+ connectState = NetConnection_clientGo(conn);
+ if (connectState == NULL) {
+ // errno is set
+ return -1;
+ }
+ connectStateData = ConnectStateData_new();
+ connectStateData->state.connectState = connectState;
+ }
+ connectStateData->isServer = conn->options->isServer;
+
+ NetConnection_setStateData(conn, (void *) connectStateData);
+ return 0;
+}
+
+static ListenState *
+NetConnection_serverGo(NetConnection *conn) {
+ ListenFlags listenFlags;
+ ListenState *result;
+
+ assert(conn->state == NetState_unconnected);
+ assert(conn->options->isServer);
+
+ NetConnection_setState(conn, NetState_connecting);
+
+ memset (&listenFlags, 0, sizeof listenFlags);
+ listenFlags.familyDemand =
+#if NETPLAY == NETPLAY_IPV4
+ PF_inet;
+#else
+ PF_unspec;
+#endif
+ listenFlags.familyPrefer = PF_unspec;
+ listenFlags.backlog = NETPLAY_LISTEN_BACKLOG;
+
+ result = listenPort(conn->options->port, IPProto_tcp, &listenFlags,
+ NetConnection_connectedServerCallback,
+ NetConnection_connectedServerErrorCallback, (void *) conn);
+
+ return result;
+}
+
+static ConnectState *
+NetConnection_clientGo(NetConnection *conn) {
+ ConnectFlags connectFlags;
+ ConnectState *result;
+
+ assert(conn->state == NetState_unconnected);
+ assert(!conn->options->isServer);
+
+ NetConnection_setState(conn, NetState_connecting);
+
+ memset (&connectFlags, 0, sizeof connectFlags);
+ connectFlags.familyDemand =
+#if NETPLAY == NETPLAY_IPV4
+ PF_inet;
+#else
+ PF_unspec;
+#endif
+ connectFlags.familyPrefer = PF_unspec;
+ connectFlags.timeout = NETPLAY_CONNECTTIMEOUT;
+ connectFlags.retryDelayMs = NETPLAY_RETRYDELAY;
+
+ result = connectHostByName(conn->options->host, conn->options->port,
+ IPProto_tcp, &connectFlags, NetConnection_connectedClientCallback,
+ NetConnection_connectedClientErrorCallback, (void *) conn);
+
+ return result;
+}
+
+// Called when an incoming connection has been established.
+// The caller gives up ownership of newNd
+static void
+NetConnection_connectedServerCallback(ListenState *listenState,
+ NetDescriptor *listenNd, NetDescriptor *newNd,
+ const struct sockaddr *addr, SOCKLEN_T addrLen) {
+ NetConnection *conn =
+ (NetConnection *) ListenState_getExtra(listenState);
+
+ assert(conn->nd == NULL);
+
+ conn->nd = newNd;
+ // No incRef(); the caller gives up ownership.
+ NetDescriptor_setExtra(conn->nd, (void *) conn);
+
+ (void) Socket_setInteractive(NetDescriptor_getSocket(conn->nd));
+ // Ignore errors; it's not a big deal. In debug mode, a message
+ // will already have been printed from the function itself.
+
+ conn->stateFlags.discriminant = true;
+
+ NetConnection_connected(conn);
+ (void) listenNd;
+ (void) addr;
+ (void) addrLen;
+}
+
+// Called when an outgoing connection has been established.
+// The caller gives up ownership of newNd
+static void
+NetConnection_connectedClientCallback(ConnectState *connectState,
+ NetDescriptor *newNd,
+ const struct sockaddr *addr, SOCKLEN_T addrLen) {
+ NetConnection *conn =
+ (NetConnection *) ConnectState_getExtra(connectState);
+
+ assert(conn->nd == NULL);
+
+ conn->nd = newNd;
+ // No incRef(); the caller gives up ownership.
+ NetDescriptor_setExtra(conn->nd, (void *) conn);
+
+ (void) Socket_setInteractive(NetDescriptor_getSocket(conn->nd));
+ // Ignore errors; it's not a big deal. In debug mode, a message
+ // will already have been printed from the function itself.
+
+ conn->stateFlags.discriminant = false;
+
+ NetConnection_connected(conn);
+ (void) addr;
+ (void) addrLen;
+}
+
+// Called when a connection has been established.
+static void
+NetConnection_connected(NetConnection *conn) {
+ ConnectStateData *connectStateData;
+
+ conn->stateFlags.connected = true;
+ NetConnection_setState(conn, NetState_init);
+
+ connectStateData = (ConnectStateData *) NetConnection_getStateData(conn);
+ ConnectStateData_delete(connectStateData);
+ NetConnection_setStateData(conn, NULL);
+
+ NetDescriptor_setReadCallback(conn->nd, dataReadyCallback);
+ NetDescriptor_setCloseCallback(conn->nd, closeCallback);
+
+ (*conn->connectCallback)(conn);
+}
+
+static void
+NetConnection_connectedServerErrorCallback(ListenState *listenState,
+ const ListenError *listenError) {
+ NetConnection *conn =
+ (NetConnection *) ListenState_getExtra(listenState);
+ NetConnectionError error;
+
+ conn->stateFlags.connected = false;
+ conn->stateFlags.disconnected = true;
+ ListenState_close(listenState);
+ NetConnection_setState(conn, NetState_unconnected);
+
+ {
+ ConnectStateData *connectStateData;
+ connectStateData =
+ (ConnectStateData *) NetConnection_getStateData(conn);
+ ConnectStateData_free(connectStateData);
+ NetConnection_setStateData(conn, NULL);
+ }
+
+ if (conn->errorCallback != NULL) {
+ error.state = NetState_connecting;
+ error.err = listenError->err;
+ error.extra.listenError = listenError;
+ //NetConnection_incRef(conn);
+ (*conn->errorCallback)(conn, &error);
+ //NetConnection_decRef(conn);
+ }
+
+ NetConnection_close(conn);
+}
+
+static void
+NetConnection_connectedClientErrorCallback(ConnectState *connectState,
+ const ConnectError *connectError) {
+ NetConnection *conn =
+ (NetConnection *) ConnectState_getExtra(connectState);
+ NetConnectionError error;
+
+ conn->stateFlags.connected = false;
+ conn->stateFlags.disconnected = true;
+ ConnectState_close(connectState);
+ NetConnection_setState(conn, NetState_unconnected);
+
+ {
+ ConnectStateData *connectStateData;
+ connectStateData =
+ (ConnectStateData *) NetConnection_getStateData(conn);
+ ConnectStateData_free(connectStateData);
+ NetConnection_setStateData(conn, NULL);
+ }
+
+ if (conn->errorCallback != NULL) {
+ error.state = NetState_connecting;
+ error.err = connectError->err;
+ error.extra.connectError = connectError;
+ //NetConnection_incRef(conn);
+ (*conn->errorCallback)(conn, &error);
+ //NetConnection_decRef(conn);
+ }
+
+ NetConnection_close(conn);
+}
+
+
diff --git a/src/uqm/supermelee/netplay/netconnection.c b/src/uqm/supermelee/netplay/netconnection.c
new file mode 100644
index 0000000..48ab46b
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netconnection.c
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "netconnection.h"
+
+#include "netrcv.h"
+
+#if defined(DEBUG) || defined(NETPLAY_DEBUG)
+# include "libs/log.h"
+#endif
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+# include "options.h"
+ // for configDir
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+# include <errno.h>
+# include <time.h>
+#endif
+
+
+static void closeCallback(NetDescriptor *nd);
+static void NetConnection_doClose(NetConnection *conn);
+
+
+#include "nc_connect.ci"
+
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+uio_Stream *netplayDebugFile;
+#endif
+
+// Used as initial value for Agreement structures, by structure assignment.
+const Agreement Agreement_nothingAgreed;
+
+
+// The NetConnection keeps a pointer to the passed NetplayPeerOptions;
+// do not free it as long as the NetConnection exists.
+NetConnection *
+NetConnection_open(int player, const NetplayPeerOptions *options,
+ NetConnection_ConnectCallback connectCallback,
+ NetConnection_CloseCallback closeCallback,
+ NetConnection_ErrorCallback errorCallback,
+ NetConnection_DeleteCallback deleteCallback, void *extra) {
+ NetConnection *conn;
+
+ conn = malloc(sizeof (NetConnection));
+
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+ {
+ char dumpFileName[PATH_MAX];
+ time_t now;
+ struct tm *nowTm;
+ size_t strftimeResult;
+
+ now = time (NULL);
+ if (now == (time_t) -1) {
+ log_add (log_Fatal, "time() failed: %s.", strerror (errno));
+ abort ();
+ }
+
+ nowTm = localtime(&now);
+ // XXX: I would like to use localtime_r(), but it isn't very
+ // portable (yet), and adding a check for it to the build.sh script
+ // is not worth the effort for a debugging function right now.
+
+ strftimeResult = strftime (dumpFileName, sizeof dumpFileName,
+ "debug/netlog-%Y%m%d%H%M%S", nowTm);
+ if (strftimeResult == 0) {
+ log_add (log_Fatal, "strftime() failed: %s.", strerror (errno));
+ abort ();
+ }
+
+ // The user needs to create the debug/ dir manually. If there
+ // is no debug/ dir, no log will be created.
+ conn->debugFile = uio_fopen (configDir, dumpFileName, "wt");
+ if (conn->debugFile == NULL) {
+ log_add (log_Debug, "Not creating a netplay debug log for "
+ "player %d.", player);
+ } else {
+ log_add (log_Debug, "Creating netplay debug log '%s' for "
+ "player %d.", dumpFileName, player);
+ if (netplayDebugFile == NULL) {
+ // Debug info relating to no specific network connection
+ // is sent to the first opened one.
+ netplayDebugFile = conn->debugFile;
+ }
+ }
+ }
+#endif
+
+ conn->nd = NULL;
+ conn->player = player;
+ conn->state = NetState_unconnected;
+ conn->options = options;
+ conn->extra = extra;
+ PacketQueue_init(&conn->queue);
+
+ conn->connectCallback = connectCallback;
+ conn->closeCallback = closeCallback;
+ conn->errorCallback = errorCallback;
+ conn->deleteCallback = deleteCallback;
+ conn->readyCallback = NULL;
+ conn->readyCallbackArg = NULL;
+ conn->resetCallback = NULL;
+ conn->resetCallbackArg = NULL;
+
+ conn->readBuf = malloc(NETPLAY_READBUFSIZE);
+ conn->readEnd = conn->readBuf;
+
+ conn->stateData = NULL;
+ conn->stateFlags.connected = false;
+ conn->stateFlags.disconnected = false;
+ conn->stateFlags.discriminant = false;
+ conn->stateFlags.handshake.localOk = false;
+ conn->stateFlags.handshake.remoteOk = false;
+ conn->stateFlags.handshake.canceling = false;
+ conn->stateFlags.ready.localReady = false;
+ conn->stateFlags.ready.remoteReady = false;
+ conn->stateFlags.reset.localReset = false;
+ conn->stateFlags.reset.remoteReset = false;
+ conn->stateFlags.agreement = Agreement_nothingAgreed;
+ conn->stateFlags.inputDelay = 0;
+#ifdef NETPLAY_CHECKSUM
+ conn->stateFlags.checksumInterval = NETPLAY_CHECKSUM_INTERVAL;
+#endif
+
+#ifdef NETPLAY_STATISTICS
+ {
+ size_t i;
+
+ conn->statistics.packetsReceived = 0;
+ conn->statistics.packetsSent = 0;
+ for (i = 0; i < PACKET_NUM; i++)
+ {
+ conn->statistics.packetTypeReceived[i] = 0;
+ conn->statistics.packetTypeSent[i] = 0;
+ }
+ }
+#endif
+
+ NetConnection_go(conn);
+
+ return conn;
+}
+
+static void
+NetConnection_doDeleteCallback(NetConnection *conn) {
+ if (conn->deleteCallback != NULL) {
+ //NetConnection_incRef(conn);
+ conn->deleteCallback(conn);
+ //NetConnection_decRef(conn);
+ }
+}
+
+static void
+NetConnection_delete(NetConnection *conn) {
+ NetConnection_doDeleteCallback(conn);
+ if (conn->stateData != NULL) {
+ NetConnectionStateData_release(conn->stateData);
+ conn->stateData = NULL;
+ }
+ free(conn->readBuf);
+ PacketQueue_uninit(&conn->queue);
+
+#ifdef NETPLAY_DEBUG_FILE
+ if (conn->debugFile != NULL) {
+ if (netplayDebugFile == conn->debugFile) {
+ // There may be other network connections, with an open
+ // debug file, but we don't know about that.
+ // The debugging person just has to work around that.
+ netplayDebugFile = NULL;
+ }
+ uio_fclose(conn->debugFile);
+ }
+#endif
+
+ free(conn);
+}
+
+static void
+Netplay_doCloseCallback(NetConnection *conn) {
+ if (conn->closeCallback != NULL) {
+ //NetConnection_incRef(conn);
+ conn->closeCallback(conn);
+ //NetConnection_decRef(conn);
+ }
+}
+
+// Auxiliary function for closing, used by both closeCallback() and
+// NetConnection_close()
+static void
+NetConnection_doClose(NetConnection *conn) {
+ conn->stateFlags.connected = false;
+ conn->stateFlags.disconnected = true;
+
+ // First the callback, so that it can still use the information
+ // of what is the current state, and the stateData:
+ Netplay_doCloseCallback(conn);
+
+ NetConnection_setState(conn, NetState_unconnected);
+}
+
+// Called when the NetDescriptor is shut down.
+static void
+closeCallback(NetDescriptor *nd) {
+ NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd);
+ if (conn == NULL)
+ return;
+ conn->nd = NULL;
+ NetConnection_doClose(conn);
+}
+
+// Close and release a NetConnection.
+void
+NetConnection_close(NetConnection *conn) {
+ if (conn->nd != NULL) {
+ NetDescriptor_setCloseCallback(conn->nd, NULL);
+ // We're not interested in the close callback of the
+ // NetDescriptor anymore.
+ NetDescriptor_close(conn->nd);
+ // This would queue the close callback.
+ conn->nd = NULL;
+ }
+ if (!conn->stateFlags.disconnected)
+ NetConnection_doClose(conn);
+ NetConnection_delete(conn);
+}
+
+void
+NetConnection_doErrorCallback(NetConnection *nd, int err) {
+ NetConnectionError error;
+
+ if (nd->errorCallback != NULL) {
+ error.state = nd->state;
+ error.err = err;
+ }
+ (*nd->errorCallback)(nd, &error);
+}
+
+void
+NetConnection_setStateData(NetConnection *conn,
+ NetConnectionStateData *stateData) {
+ conn->stateData = stateData;
+}
+
+NetConnectionStateData *
+NetConnection_getStateData(const NetConnection *conn) {
+ return conn->stateData;
+}
+
+void
+NetConnection_setExtra(NetConnection *conn, void *extra) {
+ conn->extra = extra;
+}
+
+void *
+NetConnection_getExtra(const NetConnection *conn) {
+ return conn->extra;
+}
+
+void
+NetConnection_setReadyCallback(NetConnection *conn,
+ NetConnection_ReadyCallback callback, void *arg) {
+ conn->readyCallback = callback;
+ conn->readyCallbackArg = arg;
+}
+
+NetConnection_ReadyCallback
+NetConnection_getReadyCallback(const NetConnection *conn) {
+ return conn->readyCallback;
+}
+
+void *
+NetConnection_getReadyCallbackArg(const NetConnection *conn) {
+ return conn->readyCallbackArg;
+}
+
+void
+NetConnection_setResetCallback(NetConnection *conn,
+ NetConnection_ResetCallback callback, void *arg) {
+ conn->resetCallback = callback;
+ conn->resetCallbackArg = arg;
+}
+
+NetConnection_ResetCallback
+NetConnection_getResetCallback(const NetConnection *conn) {
+ return conn->resetCallback;
+}
+
+void *
+NetConnection_getResetCallbackArg(const NetConnection *conn) {
+ return conn->resetCallbackArg;
+}
+
+void
+NetConnection_setState(NetConnection *conn, NetState state) {
+#ifdef NETPLAY_DEBUG
+ log_add(log_Debug, "NETPLAY: [%d] +/- Connection state changed to: "
+ "%s.\n", conn->player, netStateData[state].name);
+#endif
+#ifdef DEBUG
+ if (state == conn->state) {
+ log_add(log_Warning, "NETPLAY: [%d] Connection state set to %s "
+ "while already in that state.\n",
+ conn->player, netStateData[state].name);
+ }
+#endif
+ conn->state = state;
+}
+
+NetState
+NetConnection_getState(const NetConnection *conn) {
+ return conn->state;
+}
+
+bool
+NetConnection_getDiscriminant(const NetConnection *conn) {
+ return conn->stateFlags.discriminant;
+}
+
+const NetplayPeerOptions *
+NetConnection_getPeerOptions(const NetConnection *conn) {
+ return conn->options;
+}
+
+bool
+NetConnection_isConnected(const NetConnection *conn) {
+ return conn->stateFlags.connected;
+}
+
+int
+NetConnection_getPlayerNr(const NetConnection *conn) {
+ return conn->player;
+}
+
+size_t
+NetConnection_getInputDelay(const NetConnection *conn) {
+ return conn->stateFlags.inputDelay;
+}
+
+#ifdef NETPLAY_CHECKSUM
+ChecksumBuffer *
+NetConnection_getChecksumBuffer(NetConnection *conn) {
+ return &conn->checksumBuffer;
+}
+
+size_t
+NetConnection_getChecksumInterval(const NetConnection *conn) {
+ return conn->stateFlags.checksumInterval;
+}
+#endif /* NETPLAY_CHECKSUM */
+
+#ifdef NETPLAY_STATISTICS
+NetStatistics *
+NetConnection_getStatistics(NetConnection *conn) {
+ return &conn->statistics;
+}
+#endif
+
diff --git a/src/uqm/supermelee/netplay/netconnection.h b/src/uqm/supermelee/netplay/netconnection.h
new file mode 100644
index 0000000..485d3c4
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netconnection.h
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_
+#define UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_
+
+#include "netplay.h"
+ // for NETPLAY_STATISTICS
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct NetConnection NetConnection;
+typedef struct NetConnectionError NetConnectionError;
+typedef struct ConnectStateData ConnectStateData;
+#ifdef NETPLAY_STATISTICS
+typedef struct NetStatistics NetStatistics;
+#endif
+
+typedef void (*NetConnection_ConnectCallback)(NetConnection *nd);
+typedef void (*NetConnection_CloseCallback)(NetConnection *nd);
+typedef void (*NetConnection_ErrorCallback)(NetConnection *nd,
+ const NetConnectionError *error);
+typedef void (*NetConnection_DeleteCallback)(NetConnection *nd);
+
+typedef void (*NetConnection_ReadyCallback)(NetConnection *conn, void *arg);
+typedef void (*NetConnection_ResetCallback)(NetConnection *conn, void *arg);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#include "netstate.h"
+#include "netoptions.h"
+#ifdef NETPLAY_CHECKSUM
+# include "checkbuf.h"
+#endif
+#if defined(NETPLAY_STATISTICS) || defined(NETCONNECTION_INTERNAL)
+# include "packet.h"
+#endif
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+# include "libs/uio.h"
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct NetConnectionError {
+ NetState state;
+ int err;
+ union {
+ const struct ListenError *listenError;
+ const struct ConnectError *connectError;
+ } extra;
+};
+
+#ifdef NETPLAY_STATISTICS
+struct NetStatistics {
+ size_t packetsReceived;
+ size_t packetTypeReceived[PACKET_NUM];
+ size_t packetsSent;
+ size_t packetTypeSent[PACKET_NUM];
+};
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef NETCONNECTION_INTERNAL
+#include "libs/net.h"
+#include "packetq.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct {
+ // For actions that require agreement by both parties.
+ bool localOk : 1; /* Action confirmed by us */
+ bool remoteOk : 1; /* Action confirmed by the remote party */
+ bool canceling : 1; /* Awaiting cancel confirmation */
+} HandShakeFlags;
+
+typedef struct {
+ // For actions that do not require agreement, but for which it
+ // only is relevant that both sides are ready.
+ bool localReady : 1;
+ bool remoteReady : 1;
+} ReadyFlags;
+
+typedef struct {
+ bool localReset : 1;
+ bool remoteReset : 1;
+} ResetFlags;
+
+// Which parameters have we both sides of a connection reached agreement on?
+typedef struct {
+ bool randomSeed : 1;
+} Agreement;
+
+typedef struct {
+ bool connected;
+ /* This NetConnection is connected. */
+ bool disconnected;
+ /* This NetConnection has been disconnected. This implies
+ * !connected. It is only set if the NetConnection was once
+ * connected, but is no longer. */
+ bool discriminant;
+ /* If it is true here, it is false on the remote side
+ * of the same connection. It may be used to break ties.
+ * It is guaranteed not to change during a connection. Undefined
+ * while not connected. */
+ HandShakeFlags handshake;
+ ReadyFlags ready;
+ ResetFlags reset;
+ Agreement agreement;
+ size_t inputDelay;
+ /* Used during negotiation of the actual inputDelay. This
+ * field does NOT necessarilly contain the actual input delay,
+ * which is a property of the game, not of any specific
+ * connection. Use getBattleInputDelay() to get at it. */
+#ifdef NETPLAY_CHECKSUM
+ size_t checksumInterval;
+#endif
+} NetStateFlags;
+
+struct NetConnection {
+ NetDescriptor *nd;
+ int player;
+ // Number of the player for this connection, as it is
+ // known locally. For the other player, it may be
+ // differently.
+ NetState state;
+ NetStateFlags stateFlags;
+
+ NetConnection_ReadyCallback readyCallback;
+ // Called when both sides have indicated that they are ready.
+ // Set by Netplay_localReady().
+ void *readyCallbackArg;
+ // Extra argument for readyCallback().
+ // XXX: when is this cleaned up if a connection is broken?
+
+ NetConnection_ResetCallback resetCallback;
+ // Called when a reset has been signalled and confirmed.
+ // Set by Netplay_localReset().
+ void *resetCallbackArg;
+ // Extra argument for resetCallback().
+ // XXX: when is this cleaned up if a connection is broken?
+
+ const NetplayPeerOptions *options;
+ PacketQueue queue;
+#ifdef NETPLAY_STATISTICS
+ NetStatistics statistics;
+#endif
+#ifdef NETPLAY_CHECKSUM
+ ChecksumBuffer checksumBuffer;
+#endif
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+ uio_Stream *debugFile;
+#endif
+
+ NetConnection_ConnectCallback connectCallback;
+ NetConnection_CloseCallback closeCallback;
+ // Called when the NetConnection becomes disconnected.
+ NetConnection_ErrorCallback errorCallback;
+ NetConnection_DeleteCallback deleteCallback;
+ // Called when the NetConnection is destroyed.
+ uint8 *readBuf;
+ uint8 *readEnd;
+ NetConnectionStateData *stateData;
+ // State dependant information.
+ void *extra;
+};
+
+struct ConnectStateData {
+ NETCONNECTION_STATE_DATA_COMMON
+
+ bool isServer;
+ union {
+ struct ConnectState *connectState;
+ struct ListenState *listenState;
+ } state;
+};
+
+#endif /* NETCONNECTION_INTERNAL */
+
+
+NetConnection *NetConnection_open(int player,
+ const NetplayPeerOptions *options,
+ NetConnection_ConnectCallback connectCallback,
+ NetConnection_CloseCallback closeCallback,
+ NetConnection_ErrorCallback errorCallback,
+ NetConnection_DeleteCallback deleteCallback, void *extra);
+void NetConnection_close(NetConnection *conn);
+bool NetConnection_isConnected(const NetConnection *conn);
+
+void NetConnection_doErrorCallback(NetConnection *nd, int err);
+
+void NetConnection_setStateData(NetConnection *conn,
+ NetConnectionStateData *stateData);
+NetConnectionStateData *NetConnection_getStateData(const NetConnection *conn);
+void NetConnection_setExtra(NetConnection *conn, void *extra);
+void *NetConnection_getExtra(const NetConnection *conn);
+void NetConnection_setState(NetConnection *conn, NetState state);
+NetState NetConnection_getState(const NetConnection *conn);
+bool NetConnection_getDiscriminant(const NetConnection *conn);
+const NetplayPeerOptions *NetConnection_getPeerOptions(
+ const NetConnection *conn);
+int NetConnection_getPlayerNr(const NetConnection *conn);
+size_t NetConnection_getInputDelay(const NetConnection *conn);
+#ifdef NETPLAY_CHECKSUM
+ChecksumBuffer *NetConnection_getChecksumBuffer(NetConnection *conn);
+size_t NetConnection_getChecksumInterval(const NetConnection *conn);
+#endif
+#ifdef NETPLAY_STATISTICS
+NetStatistics *NetConnection_getStatistics(NetConnection *conn);
+#endif
+
+void NetConnection_setReadyCallback(NetConnection *conn,
+ NetConnection_ReadyCallback callback, void *arg);
+NetConnection_ReadyCallback NetConnection_getReadyCallback(
+ const NetConnection *conn);
+void *NetConnection_getReadyCallbackArg(const NetConnection *conn);
+
+void NetConnection_setResetCallback(NetConnection *conn,
+ NetConnection_ResetCallback callback, void *arg);
+NetConnection_ResetCallback NetConnection_getResetCallback(
+ const NetConnection *conn);
+void *NetConnection_getResetCallbackArg(const NetConnection *conn);
+
+
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+extern uio_Stream *netplayDebugFile;
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_ */
+
+
diff --git a/src/uqm/supermelee/netplay/netinput.c b/src/uqm/supermelee/netplay/netinput.c
new file mode 100644
index 0000000..9823deb
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netinput.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#include "port.h"
+
+#include "netplay.h"
+#include "netinput.h"
+
+#include "../../intel.h"
+ // for NETWORK_CONTROL
+#include "../../setup.h"
+ // For PlayerControl
+#include "libs/log.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+
+static BattleInputBuffer battleInputBuffers[NUM_PLAYERS];
+static size_t BattleInput_inputDelay;
+
+// Call before initBattleInputBuffers()
+void
+setBattleInputDelay(size_t delay) {
+ BattleInput_inputDelay = delay;
+}
+
+size_t
+getBattleInputDelay(void) {
+ return BattleInput_inputDelay;
+}
+
+static void
+BattleInputBuffer_init(BattleInputBuffer *bib, size_t bufSize) {
+ bib->buf = malloc(bufSize * sizeof (BATTLE_INPUT_STATE));
+ bib->maxSize = bufSize;
+ bib->first = 0;
+ bib->size = 0;
+}
+
+static void
+BattleInputBuffer_uninit(BattleInputBuffer *bib) {
+ if (bib->buf != NULL) {
+ free(bib->buf);
+ bib->buf = NULL;
+ }
+ bib->maxSize = 0;
+ bib->first = 0;
+ bib->size = 0;
+}
+
+void
+initBattleInputBuffers(void) {
+ size_t player;
+ int bufSize = BattleInput_inputDelay * 2 + 2;
+
+ // The input of frame n will be processed in frame 'n + delay'.
+ //
+ // In the worst case, side 1 processes frames 'n' through 'n + delay - 1',
+ // then blocks in frame 'n + delay'.
+ // Then side 2 receives all this input, and progresses to 'delay' frames
+ // after the last frame the received input originated from, and blocks
+ // in the frame after it.
+ // So it sent input for frames 'n' through 'n + delay + delay + 1'.
+ // The input for these '2*delay + 2' frames are still
+ // unhandled by side 1, so it needs buffer space for this.
+ //
+ // Initially the buffer is filled with inputDelay zeroes,
+ // so that a party can process at least that much frames.
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ BattleInputBuffer *bib = &battleInputBuffers[player];
+ BattleInputBuffer_init(bib, bufSize);
+
+ {
+ // Initially a party must be able to process at least inputDelay
+ // frames, so we fill the buffer with inputDelay zeros.
+ size_t i;
+ for (i = 0; i < BattleInput_inputDelay; i++)
+ BattleInputBuffer_push(bib, (BATTLE_INPUT_STATE) 0);
+ }
+ }
+}
+
+void
+uninitBattleInputBuffers(void)
+{
+ size_t player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ BattleInputBuffer *bib;
+
+ bib = &battleInputBuffers[player];
+ BattleInputBuffer_uninit(bib);
+ }
+}
+
+// On error, returns false and sets errno.
+bool
+BattleInputBuffer_push(BattleInputBuffer *bib, BATTLE_INPUT_STATE input)
+{
+ size_t next;
+
+ if (bib->size == bib->maxSize) {
+ // No more space.
+ log_add(log_Error, "NETPLAY: battleInputBuffer full.\n");
+ errno = ENOBUFS;
+ return false;
+ }
+
+ next = (bib->first + bib->size) % bib->maxSize;
+ bib->buf[next] = input;
+ bib->size++;
+ return true;
+}
+
+// On error, returns false and sets errno, and *input remains unchanged.
+bool
+BattleInputBuffer_pop(BattleInputBuffer *bib, BATTLE_INPUT_STATE *input)
+{
+ if (bib->size == 0)
+ {
+ // Buffer is empty.
+ errno = EAGAIN;
+ return false;
+ }
+
+ *input = bib->buf[bib->first];
+ bib->first = (bib->first + 1) % bib->maxSize;
+ bib->size--;
+ return true;
+}
+
+BattleInputBuffer *
+getBattleInputBuffer(size_t player) {
+ return &battleInputBuffers[player];
+}
+
+
diff --git a/src/uqm/supermelee/netplay/netinput.h b/src/uqm/supermelee/netplay/netinput.h
new file mode 100644
index 0000000..2afdf02
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netinput.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETINPUT_H_
+#define UQM_SUPERMELEE_NETPLAY_NETINPUT_H_
+
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../../init.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+ // for NUM_PLAYERS
+
+typedef struct BattleInputBuffer {
+ BATTLE_INPUT_STATE *buf;
+ // Cyclic buffer. if 'size' > 0, then 'first' is an index to
+ // the first used entry, 'size' is the number of used
+ // entries in the buffer, and (first + size) % maxSize is the
+ // index to just past the end of the buffer.
+ size_t maxSize;
+ size_t first;
+ size_t size;
+} BattleInputBuffer;
+
+void setBattleInputDelay(size_t delay);
+size_t getBattleInputDelay(void);
+void initBattleInputBuffers(void);
+void uninitBattleInputBuffers(void);
+bool BattleInputBuffer_push(BattleInputBuffer *bib,
+ BATTLE_INPUT_STATE input);
+bool BattleInputBuffer_pop(BattleInputBuffer *bib,
+ BATTLE_INPUT_STATE *input);
+
+BattleInputBuffer *getBattleInputBuffer(size_t player);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETINPUT_H_ */
diff --git a/src/uqm/supermelee/netplay/netmelee.c b/src/uqm/supermelee/netplay/netmelee.c
new file mode 100644
index 0000000..e9368fd
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netmelee.c
@@ -0,0 +1,740 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#include "port.h"
+#include "netmelee.h"
+#include "libs/async.h"
+#include "libs/callback.h"
+#include "libs/log.h"
+#include "libs/net.h"
+#include "netinput.h"
+#include "netmisc.h"
+#include "netsend.h"
+#include "notify.h"
+#include "packetq.h"
+#include "proto/npconfirm.h"
+#include "proto/ready.h"
+#include "proto/reset.h"
+
+#include "../../battlecontrols.h"
+ // for NetworkInputContext
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../../init.h"
+ // for NUM_PLAYERS
+#include "../../globdata.h"
+ // for GLOBAL
+
+#include <errno.h>
+#include <stdlib.h>
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+NetConnection *netConnections[NUM_PLAYERS];
+size_t numNetConnections;
+
+void
+addNetConnection(NetConnection *conn, int playerNr) {
+ netConnections[playerNr] = conn;
+ numNetConnections++;
+}
+
+void
+removeNetConnection(int playerNr) {
+ netConnections[playerNr] = NULL;
+ numNetConnections--;
+}
+
+size_t
+getNumNetConnections(void) {
+ return numNetConnections;
+}
+
+// If the callback function returns 'false', the function will immediately
+// return with 'false'. Otherwise it will return 'true' after calling
+// the callback function for each connected player.
+bool
+forEachConnectedPlayer(ForEachConnectionCallback callback, void *arg) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ if (!(*callback)(conn, arg))
+ return false;
+ }
+ return true;
+}
+
+void
+closeAllConnections(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+
+ if (conn != NULL)
+ closePlayerNetworkConnection(player);
+ }
+}
+
+void
+closeDisconnectedConnections(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+
+ if (conn != NULL && !NetConnection_isConnected(conn))
+ closePlayerNetworkConnection(player);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+
+struct melee_state *
+NetMelee_getMeleeState(NetConnection *conn) {
+ if (NetConnection_getState(conn) > NetState_connecting) {
+ BattleStateData *battleStateData =
+ (BattleStateData *) NetConnection_getStateData(conn);
+ return battleStateData->meleeState;
+ } else {
+ return (struct melee_state *) NetConnection_getExtra(conn);
+ }
+}
+
+struct battlestate_struct *
+NetMelee_getBattleState(NetConnection *conn) {
+ if (NetConnection_getState(conn) > NetState_connecting) {
+ BattleStateData *battleStateData =
+ (BattleStateData *) NetConnection_getStateData(conn);
+ return battleStateData->battleState;
+ } else {
+ return NULL;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+static inline void
+netInputAux(uint32 timeoutMs) {
+ NetManager_process(&timeoutMs);
+ // This may cause more packets to be queued, hence the
+ // flushPacketQueues().
+ Async_process();
+ flushPacketQueues();
+ // During the flush, a disconnect may be noticed, which triggers
+ // another callback. It must be handled immediately, before
+ // another flushPacketQueue() can occur, which would not know
+ // that the socket is no longer valid.
+ // TODO: modify the close handling so this order isn't
+ // necessary.
+ Callback_process();
+}
+
+// Check the network connections for input.
+void
+netInput(void) {
+ netInputAux(0);
+}
+
+void
+netInputBlocking(uint32 timeoutMs) {
+ uint32 nextAsyncMs;
+
+ nextAsyncMs = Async_timeBeforeNextMs();
+ if (nextAsyncMs < timeoutMs)
+ timeoutMs = nextAsyncMs;
+
+ netInputAux(timeoutMs);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+// Send along all pending network packets.
+void
+flushPacketQueues(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+ int flushStatus;
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ flushStatus = flushPacketQueue(conn);
+ if (flushStatus == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
+ closePlayerNetworkConnection(player);
+ }
+}
+
+void
+confirmConnections(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_confirm(conn);
+ }
+}
+
+void
+cancelConfirmations(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_cancelConfirmation(conn);
+ }
+}
+
+void
+connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_localReady(conn, callback, arg, true);
+ }
+}
+
+bool
+allConnected(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ return false;
+ }
+ return true;
+}
+
+void
+initBattleStateDataConnections(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ BattleStateData *battleStateData;
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ battleStateData =
+ (BattleStateData *) NetConnection_getStateData(conn);
+ battleStateData->endFrameCount = 0;
+ }
+}
+
+void
+setBattleStateConnections(struct battlestate_struct *bs) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ BattleStateData *battleStateData;
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ battleStateData =
+ (BattleStateData *) NetConnection_getStateData(conn);
+ battleStateData->battleState = bs;
+ }
+}
+
+BATTLE_INPUT_STATE
+networkBattleInput(NetworkInputContext *context, STARSHIP *StarShipPtr) {
+ BattleInputBuffer *bib = getBattleInputBuffer(context->playerNr);
+ BATTLE_INPUT_STATE result;
+
+ for (;;) {
+ bool ok;
+
+#if 0
+ // This is a useful debugging trick. By enabling this #if
+ // block, this side will always lag the maximum number of frames
+ // behind the other side. When the remote side stops on some event
+ // (a breakpoint or so), this side will stop too, waiting for input
+ // in the loop below, but it won't have processed the frame that
+ // triggered the event yet. If you then jump over this 'if'
+ // statement here, you can walk through the decisive frames
+ // manually. Works best with no input delay.
+ if (bib->size <= getBattleInputDelay() + 1) {
+ ok = false;
+ } else
+#endif
+ ok = BattleInputBuffer_pop(bib, &result);
+ // Get the input from the front of the
+ // buffer.
+ if (ok)
+ break;
+
+ {
+ NetConnection *conn = netConnections[context->playerNr];
+
+ // First try whether there is incoming data, without blocking.
+ // If there isn't any, only then give a warning, and then
+ // block after all.
+ netInput();
+ if (!NetConnection_isConnected(conn))
+ {
+ // Connection aborted.
+ GLOBAL(CurrentActivity) |= CHECK_ABORT;
+ return (BATTLE_INPUT_STATE) 0;
+ }
+
+ if (GLOBAL(CurrentActivity) & CHECK_ABORT)
+ return (BATTLE_INPUT_STATE) 0;
+
+#if 0
+ log_add(log_Warning, "NETPLAY: [%d] stalling for "
+ "network input. Increase the input delay if this "
+ "happens a lot.\n", context->playerNr);
+#endif
+#define MAX_BLOCK_TIME 500
+ netInputBlocking(MAX_BLOCK_TIME);
+ if (!NetConnection_isConnected(conn))
+ {
+ // Connection aborted.
+ GLOBAL(CurrentActivity) |= CHECK_ABORT;
+ return (BATTLE_INPUT_STATE) 0;
+ }
+ }
+ }
+
+ (void) StarShipPtr;
+ return result;
+}
+
+static void
+deleteConnectionCallback(NetConnection *conn) {
+ removeNetConnection(NetConnection_getPlayerNr(conn));
+}
+
+NetConnection *
+openPlayerNetworkConnection(COUNT player, void *extra) {
+ NetConnection *conn;
+
+ assert(netConnections[player] == NULL);
+
+ conn = NetConnection_open(player,
+ &netplayOptions.peer[player], NetMelee_connectCallback,
+ NetMelee_closeCallback, NetMelee_errorCallback,
+ deleteConnectionCallback, extra);
+
+ addNetConnection(conn, player);
+ return conn;
+}
+
+void
+closePlayerNetworkConnection(COUNT player) {
+ assert(netConnections[player] != NULL);
+
+ NetConnection_close(netConnections[player]);
+}
+
+bool
+setupInputDelay(size_t localInputDelay) {
+ COUNT player;
+ bool haveNetworkPlayer = false;
+ // We have at least one network controlled player.
+ size_t inputDelay = 0;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ haveNetworkPlayer = true;
+ if (NetConnection_getInputDelay(conn) > inputDelay)
+ inputDelay = NetConnection_getInputDelay(conn);
+ }
+
+ if (haveNetworkPlayer && inputDelay < localInputDelay)
+ inputDelay = localInputDelay;
+
+ setBattleInputDelay(inputDelay);
+ return true;
+}
+
+static bool
+setStateConnection(NetConnection *conn, void *arg) {
+ const NetState *state = (NetState *) arg;
+ NetConnection_setState(conn, *state);
+ return true;
+}
+
+bool
+setStateConnections(NetState state) {
+ return forEachConnectedPlayer(setStateConnection, &state);
+}
+
+static bool
+sendAbortConnection(NetConnection *conn, void *arg) {
+ const NetplayAbortReason *reason = (NetplayAbortReason *) arg;
+ sendAbort(conn, *reason);
+ return true;
+}
+
+bool
+sendAbortConnections(NetplayAbortReason reason) {
+ return forEachConnectedPlayer(sendAbortConnection, &reason);
+}
+
+static bool
+resetConnection(NetConnection *conn, void *arg) {
+ const NetplayResetReason *reason = (NetplayResetReason *) arg;
+ Netplay_localReset(conn, *reason);
+ return true;
+}
+
+bool
+resetConnections(NetplayResetReason reason) {
+ return forEachConnectedPlayer(resetConnection, &reason);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct {
+ NetConnection_ReadyCallback readyCallback;
+ void *readyCallbackArg;
+ bool notifyRemote;
+} LocalReadyConnectionArg;
+
+static bool
+localReadyConnection(NetConnection *conn, void *arg) {
+ LocalReadyConnectionArg *readyArg = (LocalReadyConnectionArg *) arg;
+ Netplay_localReady(conn, readyArg->readyCallback,
+ readyArg->readyCallbackArg, readyArg->notifyRemote);
+ return true;
+}
+
+bool
+localReadyConnections(NetConnection_ReadyCallback readyCallback,
+ void *readyArg, bool notifyRemote) {
+ LocalReadyConnectionArg arg;
+ arg.readyCallback = readyCallback;
+ arg.readyCallbackArg = readyArg;
+ arg.notifyRemote = notifyRemote;
+
+ return forEachConnectedPlayer(localReadyConnection, &arg);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define NETWORK_POLL_DELAY (ONE_SECOND / 24)
+
+typedef struct NegotiateReadyState NegotiateReadyState;
+struct NegotiateReadyState {
+ // Common fields of INPUT_STATE_DESC, from which this structure
+ // "inherits".
+ BOOLEAN(*InputFunc)(void *pInputState);
+
+ NetConnection *conn;
+ NetState nextState;
+ bool done;
+};
+
+static BOOLEAN
+negotiateReadyInputFunc(NegotiateReadyState *state) {
+ netInputBlocking(NETWORK_POLL_DELAY);
+ // The timing out is necessary so that immediate key presses get
+ // handled while we wait. If we could do without the timeout,
+ // we wouldn't even need negotiateReadyInputFunc() and the
+ // DoInput() call.
+
+ // No need to call flushPacketQueues(); nothing needs to be sent
+ // right now.
+
+ if (!NetConnection_isConnected(state->conn))
+ return FALSE;
+
+ return !state->done;
+}
+
+// Called when both sides are ready
+static void
+negotiateReadyBothReadyCallback(NetConnection *conn, void *arg) {
+ NegotiateReadyState *state =(NegotiateReadyState *) arg;
+
+ NetConnection_setState(conn, state->nextState);
+ // This has to be done immediately, as more packets in the
+ // receive queue may be handled by the netInput() call that
+ // triggered this callback.
+ // This is the reason for the nextState argument to
+ // negotiateReady(); setting the state after the call to
+ // negotiateReady() would be too late.
+ state->done = true;
+}
+
+bool
+negotiateReady(NetConnection *conn, bool notifyRemote, NetState nextState) {
+ NegotiateReadyState state;
+ state.InputFunc = (BOOLEAN(*)(void *)) negotiateReadyInputFunc;
+ state.conn = conn;
+ state.nextState = nextState;
+ state.done = false;
+
+ Netplay_localReady(conn, negotiateReadyBothReadyCallback,
+ (void *) &state, notifyRemote);
+ flushPacketQueue(conn);
+ if (!state.done)
+ DoInput(&state, FALSE);
+
+ return NetConnection_isConnected(conn);
+}
+
+// Wait for all connections to get ready.
+// XXX: Right now all connections are handled one by one. Handling them all
+// at once would be faster but would require more work, which is
+// not worth it as the time is minimal and this function is not
+// time critical.
+bool
+negotiateReadyConnections(bool notifyRemote, NetState nextState) {
+ COUNT player;
+ size_t numDisconnected = 0;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn)) {
+ numDisconnected++;
+ continue;
+ }
+
+ negotiateReady(conn, notifyRemote, nextState);
+ }
+
+ return numDisconnected == 0;
+}
+
+typedef struct WaitReadyState WaitReadyState;
+struct WaitReadyState {
+ // Common fields of INPUT_STATE_DESC, from which this structure
+ // "inherits".
+ BOOLEAN (*InputFunc)(void *pInputState);
+
+ NetConnection *conn;
+ NetConnection_ReadyCallback readyCallback;
+ void *readyCallbackArg;
+ bool done;
+};
+
+static void
+waitReadyCallback(NetConnection *conn, void *arg) {
+ WaitReadyState *state =(WaitReadyState *) arg;
+ state->done = true;
+
+ // Call the original callback.
+ state->readyCallback(conn, state->readyCallbackArg);
+}
+
+static BOOLEAN
+waitReadyInputFunc(WaitReadyState *state) {
+ netInputBlocking(NETWORK_POLL_DELAY);
+ // The timing out is necessary so that immediate key presses get
+ // handled while we wait. If we could do without the timeout,
+ // we wouldn't even need negotiateReadyInputFunc() and the
+ // DoInput() call.
+
+ // No need to call flushPacketQueues(); nothing needs to be sent
+ // right now.
+
+ if (!NetConnection_isConnected(state->conn))
+ return FALSE;
+
+ return !state->done;
+}
+
+bool
+waitReady(NetConnection *conn) {
+ WaitReadyState state;
+ state.InputFunc =(BOOLEAN(*)(void *)) waitReadyInputFunc;
+ state.conn = conn;
+ state.readyCallback = NetConnection_getReadyCallback(conn);
+ state.readyCallbackArg = NetConnection_getReadyCallbackArg(conn);
+ state.done = false;
+
+ NetConnection_setReadyCallback(conn, waitReadyCallback, (void *) &state);
+
+ DoInput(&state, FALSE);
+
+ return NetConnection_isConnected(conn);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+typedef struct WaitResetState WaitResetState;
+struct WaitResetState {
+ // Common fields of INPUT_STATE_DESC, from which this structure
+ // "inherits".
+ BOOLEAN(*InputFunc)(void *pInputState);
+
+ NetConnection *conn;
+ NetState nextState;
+ bool done;
+};
+
+static BOOLEAN
+waitResetInputFunc(WaitResetState *state) {
+ netInputBlocking(NETWORK_POLL_DELAY);
+ // The timing out is necessary so that immediate key presses get
+ // handled while we wait. If we could do without the timeout,
+ // we wouldn't even need waitResetInputFunc() and the
+ // DoInput() call.
+
+ // No need to call flushPacketQueues(); nothing needs to be sent
+ // right now.
+
+ if (!NetConnection_isConnected(state->conn))
+ return FALSE;
+
+ return !state->done;
+}
+
+// Called when both sides are reset.
+static void
+waitResetBothResetCallback(NetConnection *conn, void *arg) {
+ WaitResetState *state = (WaitResetState *) arg;
+
+ if (state->nextState != (NetState) -1) {
+ NetConnection_setState(conn, state->nextState);
+ // This has to be done immediately, as more packets in the
+ // receive queue may be handled by the netInput() call that
+ // triggered this callback.
+ // This is the reason for the nextState argument to
+ // waitReset(); setting the state after the call to
+ // waitReset() would be too late.
+ }
+ state->done = true;
+}
+
+bool
+waitReset(NetConnection *conn, NetState nextState) {
+ WaitResetState state;
+ state.InputFunc = (BOOLEAN(*)(void *)) waitResetInputFunc;
+ state.conn = conn;
+ state.nextState = nextState;
+ state.done = false;
+
+ Netplay_setResetCallback(conn, waitResetBothResetCallback,
+ (void *) &state);
+ if (state.done)
+ goto out;
+
+
+ if (!Netplay_isLocalReset(conn)) {
+ Netplay_localReset(conn, ResetReason_manualReset);
+ flushPacketQueue(conn);
+ }
+
+ if (!state.done)
+ DoInput(&state, FALSE);
+
+out:
+ return NetConnection_isConnected(conn);
+}
+
+// Wait until we have received a reset packet from all connections. If we
+// ourselves have not sent a reset packet, one is sent, with reason
+// 'manualReset'.
+// XXX: Right now all connections are handled one by one. Handling them all
+// at once would be faster but would require more work, which is
+// not worth it as the time is minimal and this function is not
+// time critical.
+// Use '(NetState) -1' for nextState to keep the current state.
+bool
+waitResetConnections(NetState nextState) {
+ COUNT player;
+ size_t numDisconnected = 0;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn)) {
+ numDisconnected++;
+ continue;
+ }
+
+ waitReset(conn, nextState);
+ }
+
+ return numDisconnected == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
diff --git a/src/uqm/supermelee/netplay/netmelee.h b/src/uqm/supermelee/netplay/netmelee.h
new file mode 100644
index 0000000..83f3562
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netmelee.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#if !defined(UQM_SUPERMELEE_NETPLAY_NETMELEE_H_) && defined(NETPLAY)
+#define UQM_SUPERMELEE_NETPLAY_NETMELEE_H_
+
+#include "netplay.h"
+#include "netinput.h"
+#include "netconnection.h"
+#include "packetsenders.h"
+
+#include "../../battlecontrols.h"
+ // for NetworkInputContext
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../../races.h"
+ // for STARSHIP
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern struct NetConnection *netConnections[];
+
+
+void addNetConnection(NetConnection *conn, int playerNr);
+void removeNetConnection(int playerNr);
+void closeAllConnections(void);
+void closeDisconnectedConnections(void);
+size_t getNumNetConnections(void);
+typedef bool(*ForEachConnectionCallback)(NetConnection *conn, void *arg);
+bool forEachConnectedPlayer(ForEachConnectionCallback callback, void *arg);
+
+struct melee_state *NetMelee_getMeleeState(NetConnection *conn);
+struct battlestate_struct *NetMelee_getBattleState(NetConnection *conn);
+
+void netInput(void);
+void netInputBlocking(uint32 timeoutMs);
+void flushPacketQueues(void);
+
+void confirmConnections(void);
+void cancelConfirmations(void);
+void connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg);
+
+bool allConnected(void);
+
+void initBattleStateDataConnections(void);
+void setBattleStateConnections(struct battlestate_struct *bs);
+
+BATTLE_INPUT_STATE networkBattleInput(NetworkInputContext *context,
+ STARSHIP *StarShipPtr);
+
+NetConnection *openPlayerNetworkConnection(COUNT player, void *extra);
+void closePlayerNetworkConnection(COUNT player);
+
+bool setupInputDelay(size_t localInputDelay);
+bool setStateConnections(NetState state);
+bool sendAbortConnections(NetplayAbortReason reason);
+bool resetConnections(NetplayResetReason reason);
+bool localReadyConnections(NetConnection_ReadyCallback readyCallback,
+ void *arg, bool notifyRemote);
+
+bool negotiateReady(NetConnection *conn, bool notifyRemote,
+ NetState nextState);
+bool negotiateReadyConnections(bool notifyRemote, NetState nextState);
+bool waitReady(NetConnection *conn);
+
+bool waitReset(NetConnection *conn, NetState nextState);
+bool waitResetConnections(NetState nextState);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETMELEE_H_ */
diff --git a/src/uqm/supermelee/netplay/netmisc.c b/src/uqm/supermelee/netplay/netmisc.c
new file mode 100644
index 0000000..3ab2f72
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netmisc.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+#include "netmisc.h"
+
+#include "netmelee.h"
+#include "notifyall.h"
+#include "packetsenders.h"
+#include "proto/ready.h"
+
+#include "../melee.h"
+ // For feedback functions.
+
+#include <stdlib.h>
+
+
+static BattleStateData *BattleStateData_alloc(void);
+static void BattleStateData_free(BattleStateData *battleStateData);
+static inline BattleStateData *BattleStateData_new(
+ struct melee_state *meleeState,
+ struct battlestate_struct *battleState,
+ struct getmelee_struct *getMeleeState);
+static void BattleStateData_delete(BattleStateData *battleStateData);
+
+
+static BattleStateData *
+BattleStateData_alloc(void) {
+ return (BattleStateData *) malloc(sizeof (BattleStateData));
+}
+
+static void
+BattleStateData_free(BattleStateData *battleStateData) {
+ free(battleStateData);
+}
+
+static inline BattleStateData *
+BattleStateData_new(struct melee_state *meleeState,
+ struct battlestate_struct *battleState,
+ struct getmelee_struct *getMeleeState) {
+ BattleStateData *battleStateData = BattleStateData_alloc();
+ battleStateData->releaseFunction =
+ (NetConnectionStateData_ReleaseFunction) BattleStateData_delete;
+ battleStateData->meleeState = meleeState;
+ battleStateData->battleState = battleState;
+ battleStateData->getMeleeState = getMeleeState;
+ return battleStateData;
+}
+
+static void
+BattleStateData_delete(BattleStateData *battleStateData) {
+ BattleStateData_free(battleStateData);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+static void NetMelee_enterState_inSetup(NetConnection *conn, void *arg);
+
+// Called when a connection has been established.
+void
+NetMelee_connectCallback(NetConnection *conn) {
+ BattleStateData *battleStateData;
+ struct melee_state *meleeState;
+
+ meleeState = (struct melee_state *) NetConnection_getExtra(conn);
+ battleStateData = BattleStateData_new(meleeState, NULL, NULL);
+ NetConnection_setStateData(conn, (void *) battleStateData);
+ NetConnection_setExtra(conn, NULL);
+
+ // We have sent no teams yet. Initialize the state accordingly.
+ MeleeSetup_resetSentTeams (meleeState->meleeSetup);
+
+ sendInit(conn);
+ Netplay_localReady (conn, NetMelee_enterState_inSetup, NULL, false);
+}
+
+// Called when a connection is closed.
+void
+NetMelee_closeCallback(NetConnection *conn) {
+ closeFeedback(conn);
+}
+
+// Called when a network error occurs during connect.
+void
+NetMelee_errorCallback(NetConnection *conn,
+ const NetConnectionError *error) {
+ errorFeedback(conn);
+ (void) error;
+}
+
+// Callback function for when both sides have finished initialisation after
+// initial connect.
+static void
+NetMelee_enterState_inSetup(NetConnection *conn, void *arg) {
+ BattleStateData *battleStateData;
+ struct melee_state *meleeState;
+ int player;
+
+ NetConnection_setState(conn, NetState_inSetup);
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ meleeState = battleStateData->meleeState;
+
+ player = NetConnection_getPlayerNr(conn);
+
+ connectedFeedback(conn);
+
+ // Send our team to the remote side.
+ // XXX This only works with 2 players atm.
+ assert (NUM_PLAYERS == 2);
+ Melee_bootstrapSyncTeam (meleeState, player);
+
+ flushPacketQueues();
+
+ (void) arg;
+}
+
diff --git a/src/uqm/supermelee/netplay/netmisc.h b/src/uqm/supermelee/netplay/netmisc.h
new file mode 100644
index 0000000..ea14921
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netmisc.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETMISC_H_
+#define UQM_SUPERMELEE_NETPLAY_NETMISC_H_
+
+typedef struct BattleStateData BattleStateData;
+
+#include "netconnection.h"
+#include "netstate.h"
+#include "types.h"
+
+#include "../../battle.h"
+ // for BattleFrameCounter, BATTLE_FRAME_RATE
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct BattleStateData {
+ NETCONNECTION_STATE_DATA_COMMON
+
+ struct melee_state *meleeState;
+ struct battlestate_struct *battleState;
+ struct getmelee_struct *getMeleeState;
+ BattleFrameCounter endFrameCount;
+};
+
+
+void NetMelee_connectCallback(NetConnection *conn);
+void NetMelee_closeCallback(NetConnection *conn);
+void NetMelee_errorCallback(NetConnection *conn,
+ const NetConnectionError *error);
+
+void NetMelee_reenterState_inSetup(NetConnection *conn);
+
+
+// Returns true iff the connection is in a state where the confirmation
+// handshake is meaningful. Right now this is only when we're in the
+// pre-game setup menu.
+static inline bool
+handshakeMeaningful(NetState state) {
+ return state == NetState_inSetup;
+}
+
+static inline bool
+readyFlagsMeaningful(NetState state) {
+ return state == NetState_init ||
+ state == NetState_preBattle ||
+ state == NetState_selectShip ||
+ state == NetState_interBattle ||
+ state == NetState_inBattle ||
+ state == NetState_endingBattle ||
+ state == NetState_endingBattle2;
+}
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETMISC_H_ */
diff --git a/src/uqm/supermelee/netplay/netoptions.c b/src/uqm/supermelee/netplay/netoptions.c
new file mode 100644
index 0000000..a74e1a3
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netoptions.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netoptions.h"
+
+NetplayOptions netplayOptions = {
+ /* .metaServer = */ "uqm.stack.nl",
+ /* .metaPort = */ "21836",
+ /* .peer = */ {
+ /* [0] Player 1 (bottom) */ {
+ /* .isServer = */ true,
+ /* .host = */ "localhost",
+ /* .port = */ "21837" /* 0x554d - "UM" */,
+ },
+ /* [1] Player 2 (top) */ {
+ /* .isServer = */ true,
+ /* .host = */ "localhost",
+ /* .port = */ "21837" /* 0x554d - "UM" */,
+ },
+ },
+ /* .inputDelay = */ 2,
+};
+
+
diff --git a/src/uqm/supermelee/netplay/netoptions.h b/src/uqm/supermelee/netplay/netoptions.h
new file mode 100644
index 0000000..77b1637
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netoptions.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_
+#define UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_
+
+#include "types.h"
+
+#include <stddef.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define NETPLAY_NUM_PLAYERS 2
+ // Not using NUM_PLAYERS because that would mean we'd have
+ // to include init.h, and all that comes with it.
+ // XXX: Don't use a hardcoded limit.
+
+typedef struct {
+ bool isServer;
+ const char *host;
+ const char *port;
+ // May be given as a service name.
+} NetplayPeerOptions;
+
+typedef struct {
+ const char *metaServer;
+ const char *metaPort;
+ // May be given as a service name.
+ NetplayPeerOptions peer[NETPLAY_NUM_PLAYERS];
+ size_t inputDelay;
+} NetplayOptions;
+extern NetplayOptions netplayOptions;
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_ */
diff --git a/src/uqm/supermelee/netplay/netplay.h b/src/uqm/supermelee/netplay/netplay.h
new file mode 100644
index 0000000..b78c69a
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netplay.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#if !defined(UQM_SUPERMELEE_NETPLAY_NETPLAY_H_) && defined(NETPLAY)
+#define UQM_SUPERMELEE_NETPLAY_NETPLAY_H_
+
+// NETPLAY can either be unset (in which case we will never get here)
+// NETPLAY_FULL, or NETPLAY_IPV4 (disables IPv6)
+#define NETPLAY_IPV4 1
+#define NETPLAY_FULL 2
+
+#define NETPLAY_PROTOCOL_VERSION_MAJOR 0
+#define NETPLAY_PROTOCOL_VERSION_MINOR 4
+
+#define NETPLAY_MIN_UQM_VERSION_MAJOR 0
+#define NETPLAY_MIN_UQM_VERSION_MINOR 6
+#define NETPLAY_MIN_UQM_VERSION_PATCH 9
+
+#undef NETPLAY_DEBUG
+ /* Extra debugging for netplay */
+#undef NETPLAY_DEBUG_FILE
+ /* Dump extra debugging information to file.
+ * Implies NETPLAY_DEBUG.*/
+#define NETPLAY_STATISTICS
+ /* Keep some statistics */
+#define NETPLAY_CHECKSUM
+ /* Send/process checksums to verify that both sides of a network
+ * connection are still in sync.
+ * If not enabled, incoming checksum packets will be ignored.
+ * TODO: make compilation of crc.c and checksum.c conditional. */
+#define NETPLAY_CHECKSUM_INTERVAL 1
+ /* If NETPLAY_CHECKSUM is defined, this define determines
+ * every how many frames a checksum packet is sent. */
+
+#define NETPLAY_READBUFSIZE 2048
+#define NETPLAY_CONNECTTIMEOUT 2000
+ /* Time to wait for a connect() to succeed. In ms. */
+//#define NETPLAY_LISTENTIMEOUT 30000
+// /* Time to wait for a listen() to succeed. In ms. */
+#define NETPLAY_RETRYDELAY 2000
+ /* Time to wait after all addresses of a host have been tried
+ * before starting retrying them all. In ms. */
+#define NETPLAY_LISTEN_BACKLOG 2
+ /* Second argument to listen(). */
+
+
+#ifdef _MSC_VER
+# if _MSC_VER < 1300
+ /* NETPLAY_DEBUG_FILE requires the __VA_ARGS__ macro, which is
+ * not available on MSVC 6.0. */
+# undef NETPLAY_DEBUG_FILE
+# endif
+#endif
+
+#ifdef NETPLAY_DEBUG_FILE
+# define NETPLAY_DEBUG
+# define DUMP_CRC_OPS
+#endif
+
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETPLAY_H_ */
+
diff --git a/src/uqm/supermelee/netplay/netrcv.c b/src/uqm/supermelee/netplay/netrcv.c
new file mode 100644
index 0000000..b9ea5f7
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netrcv.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "port.h"
+
+#include "netconnection.h"
+#include "netrcv.h"
+#include "packet.h"
+
+#include "types.h"
+#include "libs/log.h"
+
+#include <errno.h>
+#include <string.h>
+
+
+// Try to get a single packet from a stream of data.
+// Returns 0 if the packet was successfully processed, or
+// -1 on an error, in which case the state is unchanged.
+static ssize_t
+dataReceivedSingle(NetConnection *conn, const uint8 *data,
+ size_t dataLen) {
+ uint32 packetLen;
+ PacketType type;
+ int result;
+
+ if (dataLen < sizeof (PacketHeader)) {
+ // Incomplete packet. We'll have to wait for the rest.
+ return 0;
+ }
+
+ packetLen = packetLength((const Packet *) data);
+ type = packetType((const Packet *) data);
+
+ if (!validPacketType(type)) {
+ log_add(log_Warning, "Packet with invalid type %d received.\n", type);
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (packetLen < packetTypeData[type].len) {
+ // Bad len field of packet.
+ log_add(log_Warning, "Packet with bad length field received (type="
+ "%s, lenfield=%d.\n", packetTypeData[type].name,
+ packetLen);
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (dataLen < packetLen) {
+ // Incomplete packet. We'll have to wait for the rest.
+ return 0;
+ }
+
+#ifdef NETPLAY_STATISTICS
+ NetConnection_getStatistics(conn)->packetsReceived++;
+ NetConnection_getStatistics(conn)->packetTypeReceived[type]++;
+#endif
+
+#ifdef NETPLAY_DEBUG
+ if (type != PACKET_BATTLEINPUT && type != PACKET_CHECKSUM) {
+ // Reporting BattleInput and Checksum would get so spammy that it
+ // would slow down the battle.
+ log_add(log_Debug, "NETPLAY: [%d] <== Received packet of type %s.\n",
+ NetConnection_getPlayerNr(conn), packetTypeData[type].name);
+ }
+#ifdef NETPLAY_DEBUG_FILE
+ if (conn->debugFile != NULL) {
+ uio_fprintf(conn->debugFile,
+ "NETPLAY: [%d] <== Received packet of type %s.\n",
+ NetConnection_getPlayerNr(conn), packetTypeData[type].name);
+ }
+#endif /* NETPLAY_DEBUG_FILE */
+#endif /* NETPLAY_DEBUG */
+
+ result = packetTypeData[type].handler(conn, data);
+ if (result == -1) {
+ // An error occured. errno is set by the handler.
+ return -1;
+ }
+
+ return packetLen;
+}
+
+// Try to get all the packets from a stream of data.
+// Returns the number of bytes processed.
+static ssize_t
+dataReceivedMulti(NetConnection *conn, const uint8 *data, size_t len) {
+ size_t processed;
+
+ processed = 0;
+ while (len > 0) {
+ ssize_t packetLen = dataReceivedSingle(conn, data, len);
+ if (packetLen == -1) {
+ // Bad packet. Errno is set.
+ return -1;
+ }
+
+ if (packetLen == 0) {
+ // No packet was processed. This means that no complete
+ // packet arrived.
+ break;
+ }
+
+ processed += packetLen;
+ data += packetLen;
+ len -= packetLen;
+ }
+
+ return processed;
+}
+
+void
+dataReadyCallback(NetDescriptor *nd) {
+ NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd);
+ Socket *socket = NetDescriptor_getSocket(nd);
+
+ for (;;) {
+ ssize_t numRead;
+ ssize_t numProcessed;
+
+ numRead = Socket_recv(socket, conn->readEnd,
+ NETPLAY_READBUFSIZE - (conn->readEnd - conn->readBuf), 0);
+ if (numRead == 0) {
+ // Other side closed the connection.
+ NetDescriptor_close(nd);
+ return;
+ }
+
+ if (numRead == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ return; // No more data for now.
+ else if (errno == EINTR)
+ continue; // System call was interrupted. Retry.
+ else
+ {
+ int savedErrno = errno;
+ log_add(log_Error, "recv() failed: %s.\n",
+ strerror(errno));
+ NetConnection_doErrorCallback(conn, savedErrno);
+ NetDescriptor_close(nd);
+ return;
+ }
+ }
+
+ conn->readEnd += numRead;
+
+ numProcessed = dataReceivedMulti(conn, conn->readBuf,
+ conn->readEnd - conn->readBuf);
+ if (numProcessed == -1) {
+ // An error occured during processing.
+ // errno is set.
+ NetConnection_doErrorCallback(conn, errno);
+ NetDescriptor_close(nd);
+ return;
+ }
+ if (numProcessed == 0) {
+ // No packets could be processed. This means we need to receive
+ // more data first.
+ return;
+ }
+
+ // Some packets have been processed.
+ // We more any rest to the front of the buffer, to make room
+ // for more data.
+ // A cyclic buffer would obviate the need for this move,
+ // but it would complicate things a lot.
+ memmove(conn->readBuf, conn->readBuf + numProcessed,
+ (conn->readEnd - conn->readBuf) - numProcessed);
+ conn->readEnd -= numProcessed;
+ }
+}
+
+
+
diff --git a/src/uqm/supermelee/netplay/netrcv.h b/src/uqm/supermelee/netplay/netrcv.h
new file mode 100644
index 0000000..a7577d9
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netrcv.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETRCV_H_
+#define UQM_SUPERMELEE_NETPLAY_NETRCV_H_
+
+#include "libs/net.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void dataReadyCallback(NetDescriptor *nd);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETRCV_H_ */
diff --git a/src/uqm/supermelee/netplay/netsend.c b/src/uqm/supermelee/netplay/netsend.c
new file mode 100644
index 0000000..b9f371f
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netsend.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "port.h"
+
+#include "netsend.h"
+#include "netconnection.h"
+#include "packet.h"
+#include "libs/log.h"
+#include "libs/net.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+
+int
+sendPacket(NetConnection *conn, Packet *packet) {
+ ssize_t sendResult;
+ size_t len;
+ Socket *socket;
+
+ assert(NetConnection_isConnected(conn));
+
+#ifdef NETPLAY_DEBUG
+ //if (packetType(packet) != PACKET_BATTLEINPUT &&
+ // packetType(packet) != PACKET_CHECKSUM) {
+ // // Reporting BattleInput or Checksum would get so spammy that it
+ // // would slow down the battle.
+ // log_add(log_Debug, "NETPLAY: [%d] ==> Sending packet of type %s.\n",
+ // conn->player, packetTypeData[packetType(packet)].name);
+ //}
+#ifdef NETPLAY_DEBUG_FILE
+ if (conn->debugFile != NULL) {
+ uio_fprintf(conn->debugFile,
+ "NETPLAY: [%d] ==> Sending packet of type %s.\n",
+ conn->player, packetTypeData[packetType(packet)].name);
+ }
+#endif /* NETPLAY_DEBUG_FILE */
+#endif /* NETPLAY_DEBUG */
+
+ socket = NetDescriptor_getSocket(conn->nd);
+
+ len = packetLength(packet);
+ while (len > 0) {
+ sendResult = Socket_send(socket, (void *) packet, len, 0);
+ if (sendResult >= 0) {
+ len -= sendResult;
+ continue;
+ }
+
+ switch (errno) {
+ case EINTR: // System call interrupted, retry;
+ continue;
+ case ECONNRESET: { // Connection reset by peer.
+ // keep errno
+ return -1;
+ }
+ default: {
+ // Should not happen.
+ int savedErrno = errno;
+ log_add(log_Error, "send() failed: %s.\n", strerror(errno));
+ errno = savedErrno;
+ return -1;
+ }
+ }
+ }
+
+#ifdef NETPLAY_STATISTICS
+ NetConnection_getStatistics(conn)->packetsSent++;
+ NetConnection_getStatistics(conn)->packetTypeSent[packetType(packet)]++;
+#endif
+
+ return 0;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/netsend.h b/src/uqm/supermelee/netplay/netsend.h
new file mode 100644
index 0000000..e005d03
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netsend.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETSEND_H_
+#define UQM_SUPERMELEE_NETPLAY_NETSEND_H_
+
+#include "packet.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+int sendPacket(NetConnection *conn, Packet *packet);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETSEND_H_ */
diff --git a/src/uqm/supermelee/netplay/netstate.c b/src/uqm/supermelee/netplay/netstate.c
new file mode 100644
index 0000000..4382b94
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netstate.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+#include "netstate.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+
+#define DEFINE_NETSTATEDATA(stateName) \
+ { \
+ /* .name = */ #stateName, \
+ }
+NetStateData netStateData[] = {
+ DEFINE_NETSTATEDATA(unconnected),
+ DEFINE_NETSTATEDATA(connecting),
+ DEFINE_NETSTATEDATA(init),
+ DEFINE_NETSTATEDATA(inSetup),
+ DEFINE_NETSTATEDATA(preBattle),
+ DEFINE_NETSTATEDATA(interBattle),
+ DEFINE_NETSTATEDATA(selectShip),
+ DEFINE_NETSTATEDATA(inBattle),
+ DEFINE_NETSTATEDATA(endingBattle),
+ DEFINE_NETSTATEDATA(endingBattle2),
+};
+
+void
+NetConnectionStateData_release(NetConnectionStateData *stateData) {
+ assert(stateData->releaseFunction != NULL);
+ stateData->releaseFunction(stateData);
+}
+
diff --git a/src/uqm/supermelee/netplay/netstate.h b/src/uqm/supermelee/netplay/netstate.h
new file mode 100644
index 0000000..1d46d49
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netstate.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETSTATE_H_
+#define UQM_SUPERMELEE_NETPLAY_NETSTATE_H_
+
+#include "port.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct NetConnectionStateData NetConnectionStateData;
+
+// State of a NetConnection.
+typedef enum {
+ NetState_unconnected, /* No connection initiated */
+ NetState_connecting, /* Connection being setup */
+ NetState_init, /* Initialising the connection */
+ NetState_inSetup, /* In the network game setup */
+ NetState_preBattle, /* Pre-battle initialisations */
+ NetState_interBattle, /* Negotiations between battles. */
+ NetState_selectShip, /* Selecting a ship in battle */
+ NetState_inBattle, /* Battle has started */
+ NetState_endingBattle, /* Both sides are prepared to end */
+ NetState_endingBattle2, /* Waiting for the final synchronisation */
+} NetState;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#include "types.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct {
+ const char *name;
+} NetStateData;
+extern NetStateData netStateData[];
+
+typedef void (*NetConnectionStateData_ReleaseFunction)(
+ NetConnectionStateData *stateData);
+
+#define NETCONNECTION_STATE_DATA_COMMON \
+ NetConnectionStateData_ReleaseFunction releaseFunction;
+
+struct
+NetConnectionStateData {
+ NETCONNECTION_STATE_DATA_COMMON
+};
+
+void NetConnectionStateData_release(NetConnectionStateData *stateData);
+
+static inline bool
+NetState_battleActive(NetState state) {
+ return state == NetState_inBattle || state == NetState_endingBattle ||
+ state == NetState_endingBattle2;
+}
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETSTATE_H_ */
diff --git a/src/uqm/supermelee/netplay/notify.c b/src/uqm/supermelee/netplay/notify.c
new file mode 100644
index 0000000..8b35ead
--- /dev/null
+++ b/src/uqm/supermelee/netplay/notify.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// This files contains functions that notify the other side of local
+// changes.
+
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "notify.h"
+
+#include "packetsenders.h"
+
+
+// Convert a local player number to a side indication relative to this
+// party.
+static inline NetplaySide
+netSide(NetConnection *conn, int side) {
+ if (side == conn->player)
+ return NetplaySide_remote;
+
+ return NetplaySide_local;
+}
+
+void
+Netplay_Notify_shipSelected(NetConnection *conn, FleetShipIndex index) {
+ assert(NetConnection_getState(conn) == NetState_selectShip);
+
+ sendSelectShip(conn, index);
+}
+
+void
+Netplay_Notify_battleInput(NetConnection *conn, BATTLE_INPUT_STATE input) {
+ assert(NetConnection_getState(conn) == NetState_inBattle ||
+ NetConnection_getState(conn) == NetState_endingBattle ||
+ NetConnection_getState(conn) == NetState_endingBattle2);
+
+ sendBattleInput(conn, input);
+}
+
+void
+Netplay_Notify_setTeamName(NetConnection *conn, int player,
+ const char *name, size_t len) {
+ assert(NetConnection_getState(conn) == NetState_inSetup);
+ assert(!conn->stateFlags.handshake.localOk);
+
+ sendTeamName(conn, netSide(conn, player), name, len);
+}
+
+// On initialisation, or load.
+void
+Netplay_Notify_setFleet(NetConnection *conn, int player,
+ const MeleeShip *fleet, size_t fleetSize) {
+ assert(NetConnection_getState(conn) == NetState_inSetup);
+ assert(!conn->stateFlags.handshake.localOk);
+
+ sendFleet(conn, netSide(conn, player), fleet, fleetSize);
+}
+
+void
+Netplay_Notify_setShip(NetConnection *conn, int player,
+ FleetShipIndex index, MeleeShip ship) {
+ assert(NetConnection_getState(conn) == NetState_inSetup);
+ assert(!conn->stateFlags.handshake.localOk);
+
+ sendFleetShip(conn, netSide(conn, player), index, ship);
+}
+
+void
+Netplay_Notify_seedRandom(NetConnection *conn, uint32 seed) {
+ assert(NetConnection_getState(conn) == NetState_preBattle);
+
+ sendSeedRandom(conn, seed);
+ conn->stateFlags.agreement.randomSeed = true;
+}
+
+void
+Netplay_Notify_inputDelay(NetConnection *conn, uint32 delay) {
+ assert(NetConnection_getState(conn) == NetState_preBattle);
+
+ sendInputDelay(conn, delay);
+}
+
+void
+Netplay_Notify_frameCount(NetConnection *conn,
+ BattleFrameCounter frameCount) {
+ assert(NetConnection_getState(conn) == NetState_endingBattle);
+
+ sendFrameCount(conn, frameCount);
+}
+
+#ifdef NETPLAY_CHECKSUM
+void
+Netplay_Notify_checksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum) {
+ assert(NetState_battleActive(NetConnection_getState(conn)));
+
+ sendChecksum(conn, frameNr, checksum);
+}
+#endif /* NETPLAY_CHECKSUM */
+
+
+
+
diff --git a/src/uqm/supermelee/netplay/notify.h b/src/uqm/supermelee/netplay/notify.h
new file mode 100644
index 0000000..60a127d
--- /dev/null
+++ b/src/uqm/supermelee/netplay/notify.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NOTIFY_H_
+#define UQM_SUPERMELEE_NETPLAY_NOTIFY_H_
+
+#include "netplay.h"
+ // for NETPLAY_CHECKSUM
+#include "netconnection.h"
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#ifdef NETPLAY_CHECKSUM
+# include "checksum.h"
+#endif
+#include "../meleeship.h"
+ // for MeleeShip
+#include "../meleesetup.h"
+ // for FleetShipIndex
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void Netplay_Notify_shipSelected(NetConnection *conn, FleetShipIndex index);
+void Netplay_Notify_battleInput(NetConnection *conn,
+ BATTLE_INPUT_STATE input);
+void Netplay_Notify_setTeamName(NetConnection *conn, int player,
+ const char *name, size_t len);
+void Netplay_Notify_setFleet(NetConnection *conn, int player,
+ const MeleeShip *fleet, size_t fleetSize);
+void Netplay_Notify_setShip(NetConnection *conn, int player,
+ FleetShipIndex index, MeleeShip ship);
+void Netplay_Notify_seedRandom(NetConnection *conn, uint32 seed);
+void Netplay_Notify_inputDelay(NetConnection *conn, uint32 delay);
+void Netplay_Notify_frameCount(NetConnection *conn,
+ BattleFrameCounter frameCount);
+#ifdef NETPLAY_CHECKSUM
+void Netplay_Notify_checksum(NetConnection *conn,
+ BattleFrameCounter frameCount, Checksum checksum);
+#endif
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NOTIFY_H_ */
diff --git a/src/uqm/supermelee/netplay/notifyall.c b/src/uqm/supermelee/netplay/notifyall.c
new file mode 100644
index 0000000..2d0cc8a
--- /dev/null
+++ b/src/uqm/supermelee/netplay/notifyall.c
@@ -0,0 +1,146 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "notifyall.h"
+
+#include "netmelee.h"
+#include "notify.h"
+
+// Notify the network connections of a team name change.
+void
+Netplay_NotifyAll_setTeamName (MELEE_STATE *pMS, size_t playerNr)
+{
+ const char *name;
+ size_t len;
+ size_t playerI;
+
+ name = MeleeSetup_getTeamName (pMS->meleeSetup, playerNr);
+ len = strlen (name);
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn = netConnections[playerI];
+
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ if (NetConnection_getState (conn) != NetState_inSetup)
+ continue;
+
+ Netplay_Notify_setTeamName (conn, playerNr, name, len);
+ }
+}
+
+// Notify the network connections of the configuration of a fleet.
+void
+Netplay_NotifyAll_setFleet (MELEE_STATE *pMS, size_t playerNr)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+ const MeleeShip *ships = MeleeSetup_getFleet (setup, playerNr);
+ size_t playerI;
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++) {
+ NetConnection *conn = netConnections[playerI];
+
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ if (NetConnection_getState (conn) != NetState_inSetup)
+ continue;
+
+ Netplay_Notify_setFleet (conn, playerNr, ships, MELEE_FLEET_SIZE);
+ }
+}
+
+// Notify the network of a change in the configuration of a fleet.
+void
+Netplay_NotifyAll_setShip (MELEE_STATE *pMS, size_t playerNr, size_t index)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+ MeleeShip ship = MeleeSetup_getShip (setup, playerNr, index);
+
+ size_t playerI;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn = netConnections[playerI];
+
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ if (NetConnection_getState (conn) != NetState_inSetup)
+ continue;
+
+ Netplay_Notify_setShip (conn, playerNr, index, ship);
+ }
+}
+
+static bool
+Netplay_NotifyAll_inputDelayCallback(NetConnection *conn, void *arg) {
+ const size_t *delay = (size_t *) arg;
+ Netplay_Notify_inputDelay(conn, *delay);
+ return true;
+}
+
+bool
+Netplay_NotifyAll_inputDelay(size_t delay) {
+ return forEachConnectedPlayer(Netplay_NotifyAll_inputDelayCallback,
+ &delay);
+}
+
+#ifdef NETPLAY_CHECKSUM
+void
+Netplay_NotifyAll_checksum(BattleFrameCounter frameNr, Checksum checksum) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_Notify_checksum(conn, frameNr, checksum);
+ }
+}
+#endif /* NETPLAY_CHECKSUM */
+
+void
+Netplay_NotifyAll_battleInput(BATTLE_INPUT_STATE input) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_Notify_battleInput(conn, input);
+ }
+}
+
diff --git a/src/uqm/supermelee/netplay/notifyall.h b/src/uqm/supermelee/netplay/notifyall.h
new file mode 100644
index 0000000..d20ca81
--- /dev/null
+++ b/src/uqm/supermelee/netplay/notifyall.h
@@ -0,0 +1,49 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef NOTIFYALL_H
+#define NOTIFYALL_H
+
+#include "../../battle.h"
+#include "../../battlecontrols.h"
+#include "../melee.h"
+#ifdef NETPLAY_CHECKSUM
+# include "checksum.h"
+#endif /* NETPLAY_CHECKSUM */
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void Netplay_NotifyAll_setTeamName (MELEE_STATE *pMS, size_t playerNr);
+void Netplay_NotifyAll_setFleet (MELEE_STATE *pMS, size_t playerNr);
+void Netplay_NotifyAll_setShip (MELEE_STATE *pMS, size_t playerNr,
+ size_t index);
+
+bool Netplay_NotifyAll_inputDelay(size_t delay);
+#ifdef NETPLAY_CHECKSUM
+void Netplay_NotifyAll_checksum(BattleFrameCounter frameNr,
+ Checksum checksum);
+#endif /* NETPLAY_CHECKSUM */
+void Netplay_NotifyAll_battleInput(BATTLE_INPUT_STATE input);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* NOTIFYALL_H */
+
diff --git a/src/uqm/supermelee/netplay/packet.c b/src/uqm/supermelee/netplay/packet.c
new file mode 100644
index 0000000..442be17
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packet.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+#include "packet.h"
+
+#include "uqmversion.h"
+
+#include "netrcv.h"
+#include "packethandlers.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#define DEFINE_PACKETDATA(name) \
+ { \
+ /* .len = */ sizeof (Packet_##name), \
+ /* .handler = */ (PacketHandler) PacketHandler_##name, \
+ /* .name = */ #name, \
+ }
+PacketTypeData packetTypeData[PACKET_NUM] = {
+ DEFINE_PACKETDATA(Init),
+ DEFINE_PACKETDATA(Ping),
+ DEFINE_PACKETDATA(Ack),
+ DEFINE_PACKETDATA(Ready),
+ DEFINE_PACKETDATA(Fleet),
+ DEFINE_PACKETDATA(TeamName),
+ DEFINE_PACKETDATA(Handshake0),
+ DEFINE_PACKETDATA(Handshake1),
+ DEFINE_PACKETDATA(HandshakeCancel),
+ DEFINE_PACKETDATA(HandshakeCancelAck),
+ DEFINE_PACKETDATA(SeedRandom),
+ DEFINE_PACKETDATA(InputDelay),
+ DEFINE_PACKETDATA(SelectShip),
+ DEFINE_PACKETDATA(BattleInput),
+ DEFINE_PACKETDATA(FrameCount),
+ DEFINE_PACKETDATA(Checksum),
+ DEFINE_PACKETDATA(Abort),
+ DEFINE_PACKETDATA(Reset),
+};
+
+static inline void *
+Packet_alloc(size_t size) {
+ return malloc(size);
+}
+
+static Packet *
+Packet_create(PacketType type, size_t extraSize) {
+ Packet *result;
+ size_t len;
+
+ // Alignment requirement.
+ assert(extraSize % 4 == 0);
+
+ len = packetTypeData[type].len + extraSize;
+ result = Packet_alloc(len);
+ result->header.len = hton16((uint16) len);
+ result->header.type = hton16((uint16) type);
+ return result;
+}
+
+void
+Packet_delete(Packet *packet) {
+ free(packet);
+}
+
+Packet_Init *
+Packet_Init_create(void) {
+ Packet_Init *packet = (Packet_Init *) Packet_create(PACKET_INIT, 0);
+
+ packet->protoVersion.major = NETPLAY_PROTOCOL_VERSION_MAJOR;
+ packet->protoVersion.minor = NETPLAY_PROTOCOL_VERSION_MINOR;
+ packet->padding0 = 0;
+ packet->uqmVersion.major = UQM_MAJOR_VERSION;
+ packet->uqmVersion.minor = UQM_MINOR_VERSION;
+ packet->uqmVersion.patch = UQM_PATCH_VERSION;
+ packet->padding1 = 0;
+ return packet;
+}
+
+Packet_Ping *
+Packet_Ping_create(uint32 id) {
+ Packet_Ping *packet = (Packet_Ping *) Packet_create(PACKET_PING, 0);
+
+ packet->id = hton32(id);
+ return packet;
+}
+
+Packet_Ack *
+Packet_Ack_create(uint32 id) {
+ Packet_Ack *packet = (Packet_Ack *) Packet_create(PACKET_ACK, 0);
+
+ packet->id = hton32(id);
+ return packet;
+}
+
+Packet_Ready *
+Packet_Ready_create(void) {
+ Packet_Ready *packet = (Packet_Ready *) Packet_create(PACKET_READY, 0);
+
+ return packet;
+}
+
+// The fleet itself still needs to be filled by the caller.
+// This function takes care of the necessary padding; it is allocated,
+// and filled with zero chars.
+Packet_Fleet *
+Packet_Fleet_create(NetplaySide side, size_t numShips) {
+ Packet_Fleet *packet;
+ size_t fleetSize;
+ size_t extraSize;
+
+ fleetSize = numShips * sizeof (FleetEntry);
+ extraSize = (fleetSize + 3) & ~0x03;
+ packet = (Packet_Fleet *) Packet_create(PACKET_FLEET, extraSize);
+ packet->side = (uint8) side;
+ packet->padding = 0;
+ packet->numShips = hton16((uint16) numShips);
+ memset((char *) packet + sizeof (Packet_Fleet) + fleetSize,
+ '\0', extraSize - fleetSize);
+
+ return packet;
+}
+
+// 'size' is the number of bytes (not characters) in 'name', excluding
+// a possible terminating '\0'. A '\0' will be included in the packet though.
+// This function takes care of the required padding.
+Packet_TeamName *
+Packet_TeamName_create(NetplaySide side, const char *name, size_t size) {
+ Packet_TeamName *packet;
+ size_t extraSize;
+
+ extraSize = ((size + 1) + 3) & ~0x03;
+ // The +1 is for the '\0'.
+ packet = (Packet_TeamName *) Packet_create(PACKET_TEAMNAME, extraSize);
+ packet->side = (uint8) side;
+ packet->padding = 0;
+ memcpy(packet->name, name, size);
+ memset((char *) packet + sizeof (Packet_TeamName) + size, '\0',
+ extraSize - size);
+ // This takes care of the terminating '\0', as well as the
+ // padding.
+
+ return packet;
+}
+
+Packet_Handshake0 *
+Packet_Handshake0_create(void) {
+ Packet_Handshake0 *packet =
+ (Packet_Handshake0 *) Packet_create(PACKET_HANDSHAKE0, 0);
+ return packet;
+}
+
+Packet_Handshake1 *
+Packet_Handshake1_create(void) {
+ Packet_Handshake1 *packet =
+ (Packet_Handshake1 *) Packet_create(PACKET_HANDSHAKE1, 0);
+ return packet;
+}
+
+Packet_HandshakeCancel *
+Packet_HandshakeCancel_create(void) {
+ Packet_HandshakeCancel *packet =
+ (Packet_HandshakeCancel *) Packet_create(
+ PACKET_HANDSHAKECANCEL, 0);
+ return packet;
+}
+
+Packet_HandshakeCancelAck *
+Packet_HandshakeCancelAck_create(void) {
+ Packet_HandshakeCancelAck *packet =
+ (Packet_HandshakeCancelAck *) Packet_create(
+ PACKET_HANDSHAKECANCELACK, 0);
+ return packet;
+}
+
+Packet_SeedRandom *
+Packet_SeedRandom_create(uint32 seed) {
+ Packet_SeedRandom *packet =
+ (Packet_SeedRandom *) Packet_create(PACKET_SEEDRANDOM, 0);
+
+ packet->seed = hton32(seed);
+ return packet;
+}
+
+Packet_InputDelay *
+Packet_InputDelay_create(uint32 delay) {
+ Packet_InputDelay *packet =
+ (Packet_InputDelay *) Packet_create(PACKET_INPUTDELAY, 0);
+
+ packet->delay = hton32(delay);
+ return packet;
+}
+
+Packet_SelectShip *
+Packet_SelectShip_create(uint16 ship) {
+ Packet_SelectShip *packet =
+ (Packet_SelectShip *) Packet_create(PACKET_SELECTSHIP, 0);
+ packet->ship = hton16(ship);
+ packet->padding = 0;
+ return packet;
+}
+
+Packet_BattleInput *
+Packet_BattleInput_create(uint8 state) {
+ Packet_BattleInput *packet =
+ (Packet_BattleInput *) Packet_create(PACKET_BATTLEINPUT, 0);
+ packet->state = (uint8) state;
+ packet->padding0 = 0;
+ packet->padding1 = 0;
+ return packet;
+}
+
+Packet_FrameCount *
+Packet_FrameCount_create(uint32 frameCount) {
+ Packet_FrameCount *packet =
+ (Packet_FrameCount *) Packet_create(PACKET_FRAMECOUNT, 0);
+ packet->frameCount = hton32(frameCount);
+ return packet;
+}
+
+Packet_Checksum *
+Packet_Checksum_create(uint32 frameNr, uint32 checksum) {
+ Packet_Checksum *packet =
+ (Packet_Checksum *) Packet_create(PACKET_CHECKSUM, 0);
+ packet->frameNr = hton32(frameNr);
+ packet->checksum = hton32(checksum);
+ return packet;
+}
+
+Packet_Abort *
+Packet_Abort_create(uint16 reason) {
+ Packet_Abort *packet = (Packet_Abort *) Packet_create(PACKET_ABORT, 0);
+ packet->reason = hton16(reason);
+ return packet;
+}
+
+Packet_Reset *
+Packet_Reset_create(uint16 reason) {
+ Packet_Reset *packet = (Packet_Reset *) Packet_create(PACKET_RESET, 0);
+ packet->reason = hton16(reason);
+ return packet;
+}
+
+
+
diff --git a/src/uqm/supermelee/netplay/packet.h b/src/uqm/supermelee/netplay/packet.h
new file mode 100644
index 0000000..f8c9682
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packet.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PACKET_H_
+#define UQM_SUPERMELEE_NETPLAY_PACKET_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct Packet Packet;
+
+typedef enum PacketType {
+ PACKET_INIT,
+ PACKET_PING,
+ PACKET_ACK,
+ PACKET_READY,
+ PACKET_FLEET,
+ PACKET_TEAMNAME,
+ PACKET_HANDSHAKE0,
+ PACKET_HANDSHAKE1,
+ PACKET_HANDSHAKECANCEL,
+ PACKET_HANDSHAKECANCELACK,
+ PACKET_SEEDRANDOM,
+ PACKET_INPUTDELAY,
+ PACKET_SELECTSHIP,
+ PACKET_BATTLEINPUT,
+ PACKET_FRAMECOUNT,
+ PACKET_CHECKSUM,
+ PACKET_ABORT,
+ PACKET_RESET,
+
+ PACKET_NUM, /* Number of packet types */
+} PacketType;
+
+// Sent before aborting the connection.
+typedef enum NetplayAbortReason {
+ AbortReason_unspecified,
+ AbortReason_versionMismatch,
+ AbortReason_invalidHash,
+ AbortReason_protocolError,
+ // Network is in an inconsistent state.
+} NetplayAbortReason;
+
+// Sent before resetting the connection. A game in progress is terminated.
+typedef enum NetplayResetReason {
+ ResetReason_unspecified,
+ ResetReason_syncLoss,
+ ResetReason_manualReset,
+} NetplayResetReason;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifndef PACKET_H_STANDALONE
+#include "netconnection.h"
+
+#include "types.h"
+#include "libs/network/bytesex.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/* NB: These handlers are expected not to modify the state if an
+ * error occurs.
+ * When a handler is called, it has already been validated that the
+ * a complete packet has arrived.
+ */
+typedef int (*PacketHandler)(NetConnection *conn, const void *packet);
+
+typedef struct {
+ size_t len; /* Minimal length of a packet of this type */
+ PacketHandler handler;
+ const char *name;
+} PacketTypeData;
+
+extern PacketTypeData packetTypeData[];
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// When adding new packets, be sure to have all the fields properly aligned,
+// and that the size of a packet is a multiple of 4 bytes in length.
+// Fields should be no longer than 4 bytes in themselves, as larger
+// fields may require a larger alignment.
+
+typedef struct {
+ uint16 len;
+ uint16 type; /* Actually of type PacketType */
+} PacketHeader;
+
+// "Base class" for all packets.
+struct Packet {
+ PacketHeader header;
+};
+
+static inline size_t
+packetLength(const Packet *packet) {
+ return (size_t) ntoh16(packet->header.len);
+}
+
+static inline PacketType
+packetType(const Packet *packet) {
+ return (PacketType) (int) ntoh16(packet->header.type);
+}
+
+static inline bool
+validPacketType(PacketType type) {
+ return type < PACKET_NUM;
+}
+
+typedef struct {
+ PacketHeader header;
+ struct {
+ uint8 major;
+ uint8 minor;
+ } protoVersion; /* Protocol version */
+ uint16 padding0; /* Set to 0 */
+ struct {
+ uint8 major;
+ uint8 minor;
+ uint8 patch;
+ } uqmVersion; /* Protocol version */
+ uint8 padding1; /* Set to 0 */
+} Packet_Init;
+
+typedef struct {
+ PacketHeader header;
+ uint32 id;
+} Packet_Ping;
+
+// Acknowledgement of a Ping.
+typedef struct {
+ PacketHeader header;
+ uint32 id;
+} Packet_Ack;
+
+// Used to signal that a party is ready to continue.
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_Ready;
+
+typedef struct {
+ PacketHeader header;
+ uint32 seed;
+} Packet_SeedRandom;
+
+typedef struct {
+ PacketHeader header;
+ uint32 delay;
+} Packet_InputDelay;
+
+// This enum is used to indicate that a packet containing it relates to
+// either the local or the remote player, from the perspective of the
+// sender of the message;
+typedef enum {
+ NetplaySide_local,
+ NetplaySide_remote
+} NetplaySide;
+
+typedef struct {
+ uint8 index; /* Position in the fleet */
+ uint8 ship; /* Ship type index; actually MeleeShip */
+} FleetEntry;
+// Structure describing an update to a player's fleet.
+// TODO: use strings as ship identifiers, instead of numbers,
+// so that adding of new ships doesn't break this.
+typedef struct {
+ PacketHeader header;
+ uint8 side;
+ uint8 padding;
+ uint16 numShips;
+ FleetEntry ships[];
+ // Be sure to add padding to this structure to make it a multiple of
+ // 4 bytes in length.
+} Packet_Fleet;
+
+typedef struct {
+ PacketHeader header;
+ uint8 side;
+ uint8 padding;
+ uint8 name[];
+ // '\0' terminated.
+ // Be sure to add padding to this structure to make it a multiple of
+ // 4 bytes in length.
+} Packet_TeamName;
+
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_Handshake0;
+
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_Handshake1;
+
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_HandshakeCancel;
+
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_HandshakeCancelAck;
+
+typedef struct {
+ PacketHeader header;
+ uint16 ship;
+ // The value '(uint16) ~0' indicates random selection.
+ uint16 padding;
+} Packet_SelectShip;
+
+typedef struct {
+ PacketHeader header;
+ uint8 state; /* Actually BATTLE_INPUT_STATE */
+ uint8 padding0;
+ uint16 padding1;
+} Packet_BattleInput;
+
+typedef struct {
+ PacketHeader header;
+ uint32 frameCount; /* Actually BattleFrameCounter */
+} Packet_FrameCount;
+
+typedef struct {
+ PacketHeader header;
+ uint32 frameNr; /* Actually BattleFrameCounter */
+ uint32 checksum; /* Actually Checksum */
+} Packet_Checksum;
+
+typedef struct {
+ PacketHeader header;
+ uint16 reason; /* Actually NetplayAbortReason */
+ uint16 padding0;
+} Packet_Abort;
+
+typedef struct {
+ PacketHeader header;
+ uint16 reason; /* Actually NetplayResetReason */
+ uint16 padding0;
+} Packet_Reset;
+
+
+#ifndef PACKET_H_STANDALONE
+void Packet_delete(Packet *packet);
+Packet_Init *Packet_Init_create(void);
+Packet_Ping *Packet_Ping_create(uint32 id);
+Packet_Ack *Packet_Ack_create(uint32 id);
+Packet_Ready *Packet_Ready_create(void);
+Packet_Handshake0 *Packet_Handshake0_create(void);
+Packet_Handshake1 *Packet_Handshake1_create(void);
+Packet_HandshakeCancel *Packet_HandshakeCancel_create(void);
+Packet_HandshakeCancelAck *Packet_HandshakeCancelAck_create(void);
+Packet_SeedRandom *Packet_SeedRandom_create(uint32 seed);
+Packet_InputDelay *Packet_InputDelay_create(uint32 delay);
+Packet_Fleet *Packet_Fleet_create(NetplaySide side, size_t numShips);
+Packet_TeamName *Packet_TeamName_create(NetplaySide side, const char *name,
+ size_t size);
+Packet_SelectShip *Packet_SelectShip_create(uint16 ship);
+Packet_BattleInput *Packet_BattleInput_create(uint8 state);
+Packet_FrameCount *Packet_FrameCount_create(uint32 frameCount);
+Packet_Checksum *Packet_Checksum_create(uint32 frameNr, uint32 checksum);
+Packet_Abort *Packet_Abort_create(uint16 reason);
+Packet_Reset *Packet_Reset_create(uint16 reason);
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PACKET_H_ */
+
diff --git a/src/uqm/supermelee/netplay/packethandlers.c b/src/uqm/supermelee/netplay/packethandlers.c
new file mode 100644
index 0000000..5d2d8f4
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packethandlers.c
@@ -0,0 +1,649 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#include "port.h"
+
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "packethandlers.h"
+
+#include "netinput.h"
+#include "netmisc.h"
+#include "packetsenders.h"
+#include "proto/npconfirm.h"
+#include "proto/ready.h"
+#include "proto/reset.h"
+#include "libs/log.h"
+
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../../init.h"
+ // for NUM_PLAYERS
+#include "../../globdata.h"
+ // for GLOBAL
+#include "../melee.h"
+ // for various update functions.
+#include "../meleeship.h"
+ // for MeleeShip
+#include "../pickmele.h"
+ // for various update functions.
+#include "libs/mathlib.h"
+ // for TFB_SeedRandom
+
+#include <errno.h>
+
+
+static bool
+testNetState(bool condition, PacketType type) {
+ if (!condition) {
+ log_add(log_Error, "Packet of type '%s' received from wrong "
+ "state.", packetTypeData[type].name);
+ errno = EBADMSG;
+ }
+ return condition;
+}
+
+static int
+versionCompare(int major1, int minor1, int patch1,
+ int major2, int minor2, int patch2) {
+ if (major1 < major2)
+ return -1;
+ if (major1 > major2)
+ return 1;
+
+ if (minor1 < minor2)
+ return -1;
+ if (minor1 > minor2)
+ return 1;
+
+ if (patch1 < patch2)
+ return -1;
+ if (patch1 > patch2)
+ return 1;
+
+ return 0;
+}
+
+int
+PacketHandler_Init(NetConnection *conn, const Packet_Init *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_init &&
+ !conn->stateFlags.ready.remoteReady, PACKET_INIT))
+ return -1; // errno is set
+
+ if (packet->protoVersion.major != NETPLAY_PROTOCOL_VERSION_MAJOR ||
+ packet->protoVersion.minor != NETPLAY_PROTOCOL_VERSION_MINOR) {
+ sendAbort (conn, AbortReason_versionMismatch);
+ abortFeedback(conn, AbortReason_versionMismatch);
+ log_add(log_Error, "Protocol version %d.%d not supported.",
+ packet->protoVersion.major, packet->protoVersion.minor);
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (versionCompare(packet->uqmVersion.major, packet->uqmVersion.minor,
+ packet->uqmVersion.patch, NETPLAY_MIN_UQM_VERSION_MAJOR,
+ NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH)
+ < 0) {
+ sendAbort (conn, AbortReason_versionMismatch);
+ abortFeedback(conn, AbortReason_versionMismatch);
+ log_add(log_Error, "Remote side is running a version of UQM that "
+ "is too old (%d.%d.%d; %d.%d.%d is required).",
+ packet->uqmVersion.major, packet->uqmVersion.minor,
+ packet->uqmVersion.patch, NETPLAY_MIN_UQM_VERSION_MAJOR,
+ NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH);
+ errno = ENOSYS;
+ return -1;
+ }
+
+ Netplay_remoteReady(conn);
+
+ return 0;
+}
+
+int
+PacketHandler_Ping(NetConnection *conn, const Packet_Ping *packet) {
+ if (!testNetState(conn->state > NetState_init, PACKET_PING))
+ return -1; // errno is set
+
+ sendAck(conn, packet->id);
+ return 0;
+}
+
+int
+PacketHandler_Ack(NetConnection *conn, const Packet_Ack *packet) {
+ if (!testNetState(conn->state > NetState_init, PACKET_ACK))
+ return -1; // errno is set
+
+ (void) conn;
+ (void) packet;
+ return 0;
+}
+
+// Convert the side indication relative to a remote party to
+// a local player number.
+static inline int
+localSide(NetConnection *conn, NetplaySide side) {
+ if (side == NetplaySide_local) {
+ // "local" relative to the remote party.
+ return conn->player;
+ }
+
+ return 1 - conn->player;
+}
+
+int
+PacketHandler_Ready(NetConnection *conn, const Packet_Ready *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(readyFlagsMeaningful(conn->state) &&
+ !conn->stateFlags.ready.remoteReady, PACKET_READY))
+ return -1; // errno is set
+
+ Netplay_remoteReady(conn);
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_Fleet(NetConnection *conn, const Packet_Fleet *packet) {
+ uint16 numShips = ntoh16(packet->numShips);
+ size_t i;
+ size_t len;
+ int player;
+ BattleStateData *battleStateData;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET))
+ return -1; // errno is set
+
+ player = localSide(conn, (NetplaySide) packet->side);
+
+ len = packetLength((const Packet *) packet);
+ if (sizeof packet + numShips * sizeof(packet->ships[0]) > len) {
+ // There is not enough room in the packet to contain all
+ // the ships it says it contains.
+ log_add(log_Warning, "Invalid fleet size. Specified size is %d, "
+ "actual size = %d",
+ numShips, (int) ((len - sizeof packet) / sizeof(packet->ships[0])));
+ errno = EBADMSG;
+ return -1;
+ }
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+
+ if (conn->stateFlags.handshake.localOk) {
+ Netplay_cancelConfirmation(conn);
+ confirmationCancelled(battleStateData->meleeState, conn->player);
+ }
+
+ for (i = 0; i < numShips; i++) {
+ MeleeShip ship = (MeleeShip) packet->ships[i].ship;
+ FleetShipIndex index = (FleetShipIndex) packet->ships[i].index;
+
+ if (!MeleeShip_valid(ship)) {
+ log_add (log_Warning, "Invalid ship type number %d (max = %d).\n",
+ ship, NUM_MELEE_SHIPS - 1);
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (index >= MELEE_FLEET_SIZE)
+ {
+ log_add (log_Warning, "Invalid ship position number %d "
+ "(max = %d).\n", index, MELEE_FLEET_SIZE - 1);
+ errno = EBADMSG;
+ return -1;
+ }
+
+ Melee_RemoteChange_ship (battleStateData->meleeState, conn,
+ player, index, ship);
+ }
+
+ // Padding data may follow; it is ignored.
+ return 0;
+}
+
+int
+PacketHandler_TeamName(NetConnection *conn, const Packet_TeamName *packet) {
+ size_t nameLen;
+ int side;
+ BattleStateData *battleStateData;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET))
+ return -1; // errno is set
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+
+ if (conn->stateFlags.handshake.localOk) {
+ Netplay_cancelConfirmation(conn);
+ confirmationCancelled(battleStateData->meleeState, conn->player);
+ }
+
+ side = localSide(conn, (NetplaySide) packet->side);
+ nameLen = packetLength((const Packet *) packet)
+ - sizeof (Packet_TeamName) - 1;
+ // The -1 is for not counting the terminating '\0'.
+
+ {
+ char buf[MAX_TEAM_CHARS + 1];
+
+ if (nameLen > MAX_TEAM_CHARS)
+ nameLen = MAX_TEAM_CHARS;
+ memcpy (buf, (const char *) packet->name, nameLen);
+ buf[nameLen] = '\0';
+
+ Melee_RemoteChange_teamName(battleStateData->meleeState, conn,
+ side, buf);
+ }
+
+ // Padding data may follow; it is ignored.
+ return 0;
+}
+
+static void
+handshakeComplete(NetConnection *conn) {
+ assert(!conn->stateFlags.handshake.localOk);
+ assert(!conn->stateFlags.handshake.remoteOk);
+ assert(!conn->stateFlags.handshake.canceling);
+
+ assert(conn->state == NetState_inSetup);
+ NetConnection_setState(conn, NetState_preBattle);
+}
+
+int
+PacketHandler_Handshake0(NetConnection *conn,
+ const Packet_Handshake0 *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(handshakeMeaningful(conn->state)
+ && !conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKE0))
+ return -1; // errno is set
+
+ conn->stateFlags.handshake.remoteOk = true;
+ if (conn->stateFlags.handshake.localOk &&
+ !conn->stateFlags.handshake.canceling)
+ sendHandshake1(conn);
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_Handshake1(NetConnection *conn,
+ const Packet_Handshake1 *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(handshakeMeaningful(conn->state) &&
+ (conn->stateFlags.handshake.localOk ||
+ conn->stateFlags.handshake.canceling), PACKET_HANDSHAKE1))
+ return -1; // errno is set
+
+ if (conn->stateFlags.handshake.canceling) {
+ conn->stateFlags.handshake.remoteOk = true;
+ } else {
+ bool remoteWasOk = conn->stateFlags.handshake.remoteOk;
+
+ conn->stateFlags.handshake.localOk = false;
+ conn->stateFlags.handshake.remoteOk = false;
+
+ if (!remoteWasOk) {
+ // Received Handshake1 without prior Handshake0.
+ // A Handshake0 is implied, but we still need to confirm
+ // it with a Handshake1.
+ sendHandshake1(conn);
+ }
+
+ handshakeComplete(conn);
+ }
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_HandshakeCancel(NetConnection *conn,
+ const Packet_HandshakeCancel *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(handshakeMeaningful(conn->state)
+ && conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKECANCEL))
+ return -1; // errno is set
+
+ conn->stateFlags.handshake.remoteOk = false;
+ sendHandshakeCancelAck(conn);
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_HandshakeCancelAck(NetConnection *conn,
+ const Packet_HandshakeCancelAck *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(handshakeMeaningful(conn->state)
+ && conn->stateFlags.handshake.canceling,
+ PACKET_HANDSHAKECANCELACK))
+ return -1; // errno is set
+
+ conn->stateFlags.handshake.canceling = false;
+ if (conn->stateFlags.handshake.localOk) {
+ if (conn->stateFlags.handshake.remoteOk) {
+ sendHandshake1(conn);
+ } else
+ sendHandshake0(conn);
+ }
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_SeedRandom(NetConnection *conn,
+ const Packet_SeedRandom *packet) {
+ BattleStateData *battleStateData;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_preBattle &&
+ !conn->stateFlags.discriminant, PACKET_SEEDRANDOM))
+ return -1; // errno is set
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ updateRandomSeed (battleStateData->meleeState, conn->player,
+ ntoh32(packet->seed));
+
+ conn->stateFlags.agreement.randomSeed = true;
+ return 0;
+}
+
+int
+PacketHandler_InputDelay(NetConnection *conn,
+ const Packet_InputDelay *packet) {
+ BattleStateData *battleStateData;
+ uint32 delay;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_preBattle, PACKET_INPUTDELAY))
+ return -1; // errno is set
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ delay = ntoh32(packet->delay);
+ if (delay > BATTLE_FRAME_RATE) {
+ log_add(log_Error, "NETPLAY: [%d] Received absurdly large "
+ "input delay value (%d).", conn->player, delay);
+ return -1;
+ }
+ conn->stateFlags.inputDelay = delay;
+
+ return 0;
+}
+
+int
+PacketHandler_SelectShip(NetConnection *conn,
+ const Packet_SelectShip *packet) {
+ bool updateResult;
+ BattleStateData *battleStateData;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_selectShip, PACKET_SELECTSHIP))
+ return -1; // errno is set
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ updateResult = updateMeleeSelection(battleStateData->getMeleeState,
+ conn->player, ntoh16(packet->ship));
+ if (!updateResult)
+ {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+PacketHandler_BattleInput(NetConnection *conn,
+ const Packet_BattleInput *packet) {
+ BATTLE_INPUT_STATE input;
+ BattleInputBuffer *bib;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_inBattle ||
+ conn->state == NetState_endingBattle ||
+ conn->state == NetState_endingBattle2, PACKET_BATTLEINPUT))
+ return -1; // errno is set
+
+ input = (BATTLE_INPUT_STATE) packet->state;
+ bib = getBattleInputBuffer(conn->player);
+ if (!BattleInputBuffer_push(bib, input)) {
+ // errno is set
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+PacketHandler_FrameCount(NetConnection *conn,
+ const Packet_FrameCount *packet) {
+ BattleStateData *battleStateData;
+ BattleFrameCounter frameCount;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_endingBattle,
+ PACKET_FRAMECOUNT))
+ return -1; // errno is set
+
+ frameCount = (BattleFrameCounter) ntoh32(packet->frameCount);
+#ifdef NETPLAY_DEBUG
+ log_add(log_Debug, "NETPLAY: [%d] <== Received battleFrameCount %u.",
+ conn->player, (unsigned int) frameCount);
+#endif
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ if (frameCount > battleStateData->endFrameCount)
+ battleStateData->endFrameCount = frameCount;
+ Netplay_remoteReady(conn);
+
+ return 0;
+}
+
+int
+PacketHandler_Checksum(NetConnection *conn, const Packet_Checksum *packet) {
+#ifdef NETPLAY_CHECKSUM
+ uint32 frameNr;
+ uint32 checksum;
+ size_t delay;
+ size_t interval;
+#endif
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(NetState_battleActive(conn->state), PACKET_CHECKSUM))
+ return -1; // errno is set
+
+#ifdef NETPLAY_CHECKSUM
+ frameNr = ntoh32(packet->frameNr);
+ checksum = ntoh32(packet->checksum);
+ interval = NetConnection_getChecksumInterval(conn);
+ delay = getBattleInputDelay();
+
+ if (frameNr % interval != 0) {
+ log_add(log_Warning, "NETPLAY: [%d] <== Received checksum "
+ "for frame %u, while we only expect checksums on frames "
+ "divisable by %u -- discarding.", conn->player,
+ (unsigned int) frameNr, (unsigned int) interval);
+ return 0;
+ // No need to close the connection; checksums are not
+ // essential.
+ }
+
+ // The checksum is sent at the beginning of a frame.
+ // If we're in frame n and have sent our input already,
+ // the remote side has got enough input to progress delay + 1 frames from
+ // frame n. The next frame is then n + delay + 1, for which we can
+ // receive a checksum.
+ if (frameNr > battleFrameCount + delay + 1) {
+ log_add(log_Warning, "NETPLAY: [%d] <== Received checksum "
+ "for a frame too far in the future (frame %u, current "
+ "is %u, input delay is %u) -- discarding.", conn->player,
+ (unsigned int) frameNr, (unsigned int) battleFrameCount, (unsigned int) delay);
+ return 0;
+ // No need to close the connection; checksums are not
+ // essential.
+ }
+
+ // We can progress delay more frames after the last frame for which we
+ // received input. If we call that frame n, we can complete frames
+ // n through n + delay - 1. While we are waiting for the next input,
+ // in frame n + delay, we will first receive the checksum that the
+ // remote side sent at the start of frame n + 1.
+ // In this situation frameNr is n + 1, and battleFrameCount is
+ // n + delay.
+ if (frameNr + delay < battleFrameCount) {
+ log_add(log_Warning, "NETPLAY: [%d] <== Received checksum "
+ "for a frame too far in the past (frame %u, current "
+ "is %u, input delay is %u) -- discarding.", conn->player,
+ (unsigned int) frameNr, (unsigned int) battleFrameCount, (unsigned int) delay);
+ return 0;
+ // No need to close the connection; checksums are not
+ // essential.
+ }
+
+ addRemoteChecksum(conn, frameNr, checksum);
+#endif
+
+#ifndef NETPLAY_CHECKSUM
+ (void) packet;
+#endif
+ return 0;
+}
+
+int
+PacketHandler_Abort(NetConnection *conn, const Packet_Abort *packet) {
+ abortFeedback(conn, packet->reason);
+
+ return -1;
+ // Close connection.
+}
+
+int
+PacketHandler_Reset(NetConnection *conn, const Packet_Reset *packet) {
+ NetplayResetReason reason;
+
+ if (!testNetState(!conn->stateFlags.reset.remoteReset, PACKET_RESET))
+ return -1; // errno is set
+
+ reason = ntoh16(packet->reason);
+
+ Netplay_remoteReset(conn, reason);
+ return 0;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/packethandlers.h b/src/uqm/supermelee/netplay/packethandlers.h
new file mode 100644
index 0000000..7bd686e
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packethandlers.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_
+#define UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_
+
+#include "packet.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define DECLARE_PACKETHANDLER(type) \
+ int PacketHandler_##type(NetConnection *conn, \
+ const Packet_##type *packet)
+
+DECLARE_PACKETHANDLER(Init);
+DECLARE_PACKETHANDLER(Ping);
+DECLARE_PACKETHANDLER(Ack);
+DECLARE_PACKETHANDLER(Ready);
+DECLARE_PACKETHANDLER(Fleet);
+DECLARE_PACKETHANDLER(TeamName);
+DECLARE_PACKETHANDLER(Handshake0);
+DECLARE_PACKETHANDLER(Handshake1);
+DECLARE_PACKETHANDLER(HandshakeCancel);
+DECLARE_PACKETHANDLER(HandshakeCancelAck);
+DECLARE_PACKETHANDLER(SeedRandom);
+DECLARE_PACKETHANDLER(InputDelay);
+DECLARE_PACKETHANDLER(SelectShip);
+DECLARE_PACKETHANDLER(BattleInput);
+DECLARE_PACKETHANDLER(FrameCount);
+DECLARE_PACKETHANDLER(Checksum);
+DECLARE_PACKETHANDLER(Abort);
+DECLARE_PACKETHANDLER(Reset);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_ */
diff --git a/src/uqm/supermelee/netplay/packetq.c b/src/uqm/supermelee/netplay/packetq.c
new file mode 100644
index 0000000..ee8ec01
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packetq.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "netconnection.h"
+#include "packetq.h"
+#include "netsend.h"
+#include "packetsenders.h"
+#ifdef NETPLAY_DEBUG
+# include "libs/log.h"
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+static inline PacketQueueLink *
+PacketQueueLink_alloc(void) {
+ // XXX: perhaps keep a pool of links?
+ return malloc(sizeof (PacketQueueLink));
+}
+
+static inline void
+PacketQueueLink_delete(PacketQueueLink *link) {
+ free(link);
+}
+
+// 'maxSize' should at least be 1
+void
+PacketQueue_init(PacketQueue *queue) {
+ queue->size = 0;
+ queue->first = NULL;
+ queue->end = &queue->first;
+}
+
+static void
+PacketQueue_deleteLinks(PacketQueueLink *link) {
+ while (link != NULL) {
+ PacketQueueLink *next = link->next;
+ Packet_delete(link->packet);
+ PacketQueueLink_delete(link);
+ link = next;
+ }
+}
+
+void
+PacketQueue_uninit(PacketQueue *queue) {
+ PacketQueue_deleteLinks(queue->first);
+}
+
+void
+queuePacket(NetConnection *conn, Packet *packet) {
+ PacketQueue *queue;
+ PacketQueueLink *link;
+
+ assert(NetConnection_isConnected(conn));
+
+ queue = &conn->queue;
+
+ link = PacketQueueLink_alloc();
+ link->packet = packet;
+ link->next = NULL;
+ *queue->end = link;
+ queue->end = &link->next;
+
+ queue->size++;
+ // XXX: perhaps check that this queue isn't getting too large?
+
+#ifdef NETPLAY_DEBUG
+ if (packetType(packet) != PACKET_BATTLEINPUT &&
+ packetType(packet) != PACKET_CHECKSUM) {
+ // Reporting BattleInput or Checksum would get so spammy that it
+ // would slow down the battle.
+ log_add(log_Debug, "NETPLAY: [%d] ==> Queueing packet of type %s.\n",
+ NetConnection_getPlayerNr(conn),
+ packetTypeData[packetType(packet)].name);
+ }
+#ifdef NETPLAY_DEBUG_FILE
+ if (conn->debugFile != NULL) {
+ uio_fprintf(conn->debugFile,
+ "NETPLAY: [%d] ==> Queueing packet of type %s.\n",
+ NetConnection_getPlayerNr(conn),
+ packetTypeData[packetType(packet)].name);
+ }
+#endif /* NETPLAY_DEBUG_FILE */
+#endif /* NETPLAY_DEBUG */
+}
+
+// If an error occurs during sending, we leave the unsent packets in
+// the queue, and let the caller decide what to do with them.
+// This function may return -1 with errno EAGAIN or EWOULDBLOCK
+// if we're waiting for the other party to act first.
+static int
+flushPacketQueueLinks(NetConnection *conn, PacketQueueLink **first) {
+ PacketQueueLink *link;
+ PacketQueueLink *next;
+ PacketQueue *queue = &conn->queue;
+
+ for (link = *first; link != NULL; link = next) {
+ if (sendPacket(conn, link->packet) == -1) {
+ // Errno is set.
+ *first = link;
+ return -1;
+ }
+
+ next = link->next;
+ Packet_delete(link->packet);
+ PacketQueueLink_delete(link);
+ queue->size--;
+ }
+
+ *first = link;
+ return 0;
+}
+
+int
+flushPacketQueue(NetConnection *conn) {
+ int flushResult;
+ PacketQueue *queue = &conn->queue;
+
+ assert(NetConnection_isConnected(conn));
+
+ flushResult = flushPacketQueueLinks(conn, &queue->first);
+ if (queue->first == NULL)
+ queue->end = &queue->first;
+ if (flushResult == -1) {
+ // errno is set
+ return -1;
+ }
+
+ return 0;
+}
+
diff --git a/src/uqm/supermelee/netplay/packetq.h b/src/uqm/supermelee/netplay/packetq.h
new file mode 100644
index 0000000..71f2347
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packetq.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PACKETQ_H_
+#define UQM_SUPERMELEE_NETPLAY_PACKETQ_H_
+
+typedef struct PacketQueue PacketQueue;
+
+#include "packet.h"
+#include "types.h"
+
+#include <sys/types.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct PacketQueueLink PacketQueueLink;
+struct PacketQueueLink {
+ PacketQueueLink *next;
+ Packet *packet;
+};
+
+struct PacketQueue {
+ size_t size;
+ PacketQueueLink *first;
+ PacketQueueLink **end;
+
+ // first points to the first entry in the queue
+ // end points to the location where the next message should be inserted.
+};
+
+void PacketQueue_init(PacketQueue *queue);
+void PacketQueue_uninit(PacketQueue *queue);
+void queuePacket(NetConnection *conn, Packet *packet);
+int flushPacketQueue(NetConnection *conn);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+
diff --git a/src/uqm/supermelee/netplay/packetsenders.c b/src/uqm/supermelee/netplay/packetsenders.c
new file mode 100644
index 0000000..fb9f232
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packetsenders.c
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+#include "packetsenders.h"
+
+#include "packet.h"
+#include "packetq.h"
+#include "netsend.h"
+
+
+void
+sendInit(NetConnection *conn) {
+ Packet_Init *packet;
+
+ packet = Packet_Init_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendPing(NetConnection *conn, uint32 id) {
+ Packet_Ping *packet;
+
+ packet = Packet_Ping_create(id);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendAck(NetConnection *conn, uint32 id) {
+ Packet_Ack *packet;
+
+ packet = Packet_Ack_create(id);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendReady(NetConnection *conn) {
+ Packet_Ready *packet;
+
+ packet = Packet_Ready_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendHandshake0(NetConnection *conn) {
+ Packet_Handshake0 *packet;
+
+ packet = Packet_Handshake0_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendHandshake1(NetConnection *conn) {
+ Packet_Handshake1 *packet;
+
+ packet = Packet_Handshake1_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendHandshakeCancel(NetConnection *conn) {
+ Packet_HandshakeCancel *packet;
+
+ packet = Packet_HandshakeCancel_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendHandshakeCancelAck(NetConnection *conn) {
+ Packet_HandshakeCancelAck *packet;
+
+ packet = Packet_HandshakeCancelAck_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendTeamName(NetConnection *conn, NetplaySide side, const char *name,
+ size_t len) {
+ Packet_TeamName *packet;
+
+ packet = Packet_TeamName_create(side, name, len);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendFleet(NetConnection *conn, NetplaySide side, const MeleeShip *ships,
+ size_t shipCount) {
+ size_t i;
+ Packet_Fleet *packet;
+
+ packet = Packet_Fleet_create(side, shipCount);
+
+ for (i = 0; i < shipCount; i++) {
+ packet->ships[i].index = (uint8) i;
+ packet->ships[i].ship = (uint8) ships[i];
+ }
+
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendFleetShip(NetConnection *conn, NetplaySide side,
+ FleetShipIndex shipIndex, MeleeShip ship) {
+ Packet_Fleet *packet;
+
+ packet = Packet_Fleet_create(side, 1);
+
+ packet->ships[0].index = (uint8) shipIndex;
+ packet->ships[0].ship = (uint8) ship;
+
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendSeedRandom(NetConnection *conn, uint32 seed) {
+ Packet_SeedRandom *packet;
+
+ packet = Packet_SeedRandom_create(seed);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendInputDelay(NetConnection *conn, uint32 delay) {
+ Packet_InputDelay *packet;
+
+ packet = Packet_InputDelay_create(delay);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendSelectShip(NetConnection *conn, FleetShipIndex index) {
+ Packet_SelectShip *packet;
+
+ packet = Packet_SelectShip_create((uint16) index);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input) {
+ Packet_BattleInput *packet;
+
+ packet = Packet_BattleInput_create((uint8) input);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendFrameCount(NetConnection *conn, BattleFrameCounter frameCount) {
+ Packet_FrameCount *packet;
+
+ packet = Packet_FrameCount_create((uint32) frameCount);
+ queuePacket(conn, (Packet *) packet);
+}
+
+#ifdef NETPLAY_CHECKSUM
+void
+sendChecksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum) {
+ Packet_Checksum *packet;
+
+ packet = Packet_Checksum_create((uint32) frameNr, (uint32) checksum);
+ queuePacket(conn, (Packet *) packet);
+}
+#endif
+
+void
+sendAbort(NetConnection *conn, NetplayAbortReason reason) {
+ Packet_Abort *packet;
+
+ packet = Packet_Abort_create((uint16) reason);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendReset(NetConnection *conn, NetplayResetReason reason) {
+ Packet_Reset *packet;
+
+ packet = Packet_Reset_create((uint16) reason);
+ queuePacket(conn, (Packet *) packet);
+}
+
+
+
diff --git a/src/uqm/supermelee/netplay/packetsenders.h b/src/uqm/supermelee/netplay/packetsenders.h
new file mode 100644
index 0000000..de0bc6d
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packetsenders.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_
+#define UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_
+
+#include "types.h"
+
+#include "netconnection.h"
+#include "packet.h"
+
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../meleeship.h"
+ // for MeleeShip
+#include "../meleesetup.h"
+ // for FleetShipIndex
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void sendInit(NetConnection *conn);
+void sendPing(NetConnection *conn, uint32 id);
+void sendAck(NetConnection *conn, uint32 id);
+void sendReady(NetConnection *conn);
+void sendHandshake0(NetConnection *conn);
+void sendHandshake1(NetConnection *conn);
+void sendHandshakeCancel(NetConnection *conn);
+void sendHandshakeCancelAck(NetConnection *conn);
+void sendTeamName(NetConnection *conn, NetplaySide side,
+ const char *name, size_t len);
+void sendFleet(NetConnection *conn, NetplaySide side,
+ const MeleeShip *ships, size_t numShips);
+void sendFleetShip(NetConnection *conn, NetplaySide player,
+ FleetShipIndex shipIndex, MeleeShip ship);
+void sendSeedRandom(NetConnection *conn, uint32 seed);
+void sendInputDelay(NetConnection *conn, uint32 delay);
+void sendSelectShip(NetConnection *conn, FleetShipIndex index);
+void sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input);
+void sendFrameCount(NetConnection *conn, BattleFrameCounter frameCount);
+void sendChecksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum);
+void sendAbort(NetConnection *conn, NetplayAbortReason reason);
+void sendReset(NetConnection *conn, NetplayResetReason reason);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_ */
diff --git a/src/uqm/supermelee/netplay/proto/Makeinfo b/src/uqm/supermelee/netplay/proto/Makeinfo
new file mode 100644
index 0000000..1d9739c
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="npconfirm.c ready.c reset.c"
+uqm_HFILES="npconfirm.h ready.h reset.h"
diff --git a/src/uqm/supermelee/netplay/proto/npconfirm.c b/src/uqm/supermelee/netplay/proto/npconfirm.c
new file mode 100644
index 0000000..6929219
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/npconfirm.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define NETCONNECTION_INTERNAL
+#include "../netplay.h"
+#include "npconfirm.h"
+
+#include "types.h"
+#include "../netmisc.h"
+#include "../packetsenders.h"
+
+#include <assert.h>
+#include <errno.h>
+
+int
+Netplay_confirm(NetConnection *conn) {
+ assert(handshakeMeaningful(NetConnection_getState(conn)));
+
+ if (conn->stateFlags.handshake.localOk) {
+ // Already confirmed
+ errno = EINVAL;
+ return -1;
+ }
+
+ conn->stateFlags.handshake.localOk = true;
+
+ if (conn->stateFlags.handshake.canceling) {
+ // If a previous confirmation was cancelled, but the cancel
+ // is not acknowledged yet, we don't have to send anything yet.
+ // The handshake0 packet will be sent when the acknowledgement
+ // arrives.
+ } else if (conn->stateFlags.handshake.remoteOk) {
+ // A Handshake0 is implied by the following Handshake1.
+ sendHandshake1(conn);
+ } else {
+ sendHandshake0(conn);
+ }
+
+ return 0;
+}
+
+int
+Netplay_cancelConfirmation(NetConnection *conn) {
+ assert(handshakeMeaningful(NetConnection_getState(conn)));
+
+ if (!conn->stateFlags.handshake.localOk) {
+ // Not confirmed, or already canceling.
+ errno = EINVAL;
+ return -1;
+ }
+
+ conn->stateFlags.handshake.localOk = false;
+ if (conn->stateFlags.handshake.canceling) {
+ // If previous cancellation is still waiting to be acknowledged,
+ // the confirmation we are cancelling here, has not actually been
+ // sent yet. By setting the localOk flag to false, it is
+ // cancelled, without the need for any packets to be sent.
+ } else {
+ conn->stateFlags.handshake.canceling = true;
+ sendHandshakeCancel(conn);
+ }
+
+ return 0;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/proto/npconfirm.h b/src/uqm/supermelee/netplay/proto/npconfirm.h
new file mode 100644
index 0000000..1ae58f5
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/npconfirm.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_
+#define UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_
+
+#include "../netplay.h"
+#include "../netconnection.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+int Netplay_confirm(NetConnection *conn);
+int Netplay_cancelConfirmation(NetConnection *conn);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_ */
diff --git a/src/uqm/supermelee/netplay/proto/ready.c b/src/uqm/supermelee/netplay/proto/ready.c
new file mode 100644
index 0000000..e9f8c58
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/ready.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define NETCONNECTION_INTERNAL
+#include "../netplay.h"
+#include "ready.h"
+
+#include "types.h"
+#include "../netmisc.h"
+#include "../packetsenders.h"
+
+#include <assert.h>
+
+static void
+Netplay_bothReady(NetConnection *conn) {
+ NetConnection_ReadyCallback callback;
+ void *readyArg;
+
+ assert(conn->readyCallback != NULL);
+
+ callback = conn->readyCallback;
+ readyArg = conn->readyCallbackArg;
+
+ NetConnection_setReadyCallback(conn, NULL, NULL);
+ // Clear the readyCallback field before performing the callback,
+ // so that it can be set again from inside the callback
+ // function.
+
+ callback(conn, readyArg);
+}
+
+// If notifyRemote is set, a 'Ready' message will be sent to the other side.
+// returns true iff both sides are ready.
+// Inside the callback function, ready flags may be set for a possible
+// next Ready communication.
+bool
+Netplay_localReady(NetConnection *conn, NetConnection_ReadyCallback callback,
+ void *readyArg, bool notifyRemote) {
+ assert(readyFlagsMeaningful(NetConnection_getState(conn)));
+ assert(!conn->stateFlags.ready.localReady);
+ assert(callback != NULL);
+
+ NetConnection_setReadyCallback(conn, callback, readyArg);
+
+ if (notifyRemote)
+ sendReady(conn);
+ if (!conn->stateFlags.ready.remoteReady) {
+ conn->stateFlags.ready.localReady = true;
+ return false;
+ }
+
+ // Reset ready flags:
+ conn->stateFlags.ready.remoteReady = false;
+
+ // Trigger the callback.
+ Netplay_bothReady(conn);
+ return true;
+}
+
+// returns true iff both sides are ready.
+bool
+Netplay_remoteReady(NetConnection *conn) {
+ assert(readyFlagsMeaningful(NetConnection_getState(conn)));
+ // This is supposed to be already verified by the calling
+ // function.
+ assert(!conn->stateFlags.ready.remoteReady);
+
+ if (!conn->stateFlags.ready.localReady) {
+ conn->stateFlags.ready.remoteReady = true;
+ return false;
+ }
+
+ // Reset ready flags:
+ conn->stateFlags.ready.localReady = false;
+
+ // Trigger the callback.
+ Netplay_bothReady(conn);
+ return true;
+}
+
+bool
+Netplay_isLocalReady(const NetConnection *conn) {
+ return conn->stateFlags.ready.localReady;
+}
+
+bool
+Netplay_isRemoteReady(const NetConnection *conn) {
+ return conn->stateFlags.ready.remoteReady;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/proto/ready.h b/src/uqm/supermelee/netplay/proto/ready.h
new file mode 100644
index 0000000..3521557
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/ready.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_
+#define UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_
+
+#include "../netconnection.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+bool Netplay_localReady(NetConnection *conn,
+ NetConnection_ReadyCallback callback, void *arg, bool notifyRemote);
+bool Netplay_remoteReady(NetConnection *conn);
+bool Netplay_isLocalReady(const NetConnection *conn);
+bool Netplay_isRemoteReady(const NetConnection *conn);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_ */
diff --git a/src/uqm/supermelee/netplay/proto/reset.c b/src/uqm/supermelee/netplay/proto/reset.c
new file mode 100644
index 0000000..82483b1
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/reset.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// See doc/devel/netplay/protocol
+
+#define NETCONNECTION_INTERNAL
+#include "../netplay.h"
+#include "reset.h"
+
+#include "types.h"
+#include "../packetsenders.h"
+#include "../../melee.h"
+ // For resetFeedback.
+
+#include <assert.h>
+
+// Reset packets are sent to indicate that a game is to be reset.
+// i.e. the game is to return to the SuperMelee fleet setup menu.
+// The reset will occur when a reset packet has both been sent and
+// received. When a reset packet is received and the local side had not
+// sent a reset packet itself, the local side will confirm the reset.
+// When both sides initiate a reset simultaneously, the reset packets
+// of each side will act as a confirmation for the other side.
+//
+// When a reset packet has been sent, no further gameplay packets should be
+// sent until the game has been reset. Non-gameplay packets such as 'ping'
+// are allowed.
+// When a reset packet has been received, all further incoming gameplay
+// packets are ignored until the game has been reset.
+//
+// conn->stateFlags.reset.localReset is set when a reset packet is sent.
+// conn->stateFlags.reset.remoteReset is set when a reset packet is
+// received.
+//
+// When either localReset or remoteReset gets set and the other flag isn't
+// set, Netplay_connectionReset() gets called.
+//
+// As soon as the following three conditions are met, the reset callback is
+// called and the localReset and remoteReset flags are cleared.
+// - conn->stateFlags.reset.localReset is set
+// - conn->stateFlags.reset.remoteReset is set
+// - the reset callback is non-NULL.
+//
+// Elsewhere in the UQM source:
+// When the local side causes a reset, it calls Netplay_localReset().
+// When a remote reset packet is received, Netplay_remoteReset() is called
+// (which will sent a reset packet back as confirmation, as required).
+// At the end of melee, the reset callback is set (for each connection),
+// and the game will wait until the reset callback for each connection has
+// been called (when the forementioned conditions have become true)
+// (or until the connection is terminated).
+
+
+// This function is called when one side initiates a reset.
+static void
+Netplay_connectionReset(NetConnection *conn, NetplayResetReason reason,
+ bool byRemote) {
+ switch (NetConnection_getState(conn)) {
+ case NetState_unconnected:
+ case NetState_connecting:
+ case NetState_init:
+ case NetState_inSetup:
+ break;
+ case NetState_preBattle:
+ case NetState_interBattle:
+ case NetState_selectShip:
+ case NetState_inBattle:
+ case NetState_endingBattle:
+ case NetState_endingBattle2:
+ resetFeedback(conn, reason, byRemote);
+ break;
+ }
+}
+
+static void
+Netplay_doConnectionResetCallback(NetConnection *conn) {
+ NetConnection_ResetCallback callback;
+ void *resetArg;
+
+ callback = conn->resetCallback;
+ resetArg = conn->resetCallbackArg;
+
+ NetConnection_setResetCallback(conn, NULL, NULL);
+ // Clear the resetCallback field before performing the callback,
+ // so that it can be set again from inside the callback
+ // function.
+ callback(conn, resetArg);
+}
+
+static void
+Netplay_resetConditionTriggered(NetConnection *conn) {
+ if (conn->resetCallback == NULL)
+ return;
+
+ if (!conn->stateFlags.reset.localReset ||
+ !conn->stateFlags.reset.remoteReset)
+ return;
+
+ conn->stateFlags.reset.localReset = false;
+ conn->stateFlags.reset.remoteReset = false;
+
+ Netplay_doConnectionResetCallback(conn);
+}
+
+void
+Netplay_setResetCallback(NetConnection *conn,
+ NetConnection_ResetCallback callback, void *resetArg) {
+ NetConnection_setResetCallback(conn, callback, resetArg);
+
+ Netplay_resetConditionTriggered(conn);
+}
+
+void
+Netplay_localReset(NetConnection *conn, NetplayResetReason reason) {
+ assert(!conn->stateFlags.reset.localReset);
+
+ conn->stateFlags.reset.localReset = true;
+ if (conn->stateFlags.reset.remoteReset) {
+ // Both sides have initiated/confirmed the reset.
+ Netplay_resetConditionTriggered(conn);
+ } else {
+ sendReset(conn, reason);
+ Netplay_connectionReset(conn, reason, false);
+ }
+}
+
+void
+Netplay_remoteReset(NetConnection *conn, NetplayResetReason reason) {
+ assert(!conn->stateFlags.reset.remoteReset);
+ // Should already be checked when the packet arrives.
+
+ conn->stateFlags.reset.remoteReset = true;
+ if (!conn->stateFlags.reset.localReset) {
+ sendReset(conn, reason);
+ conn->stateFlags.reset.localReset = true;
+ Netplay_connectionReset(conn, reason, true);
+ }
+
+ Netplay_resetConditionTriggered(conn);
+}
+
+bool
+Netplay_isLocalReset(const NetConnection *conn) {
+ return conn->stateFlags.reset.localReset;
+}
+
+bool
+Netplay_isRemoteReset(const NetConnection *conn) {
+ return conn->stateFlags.reset.remoteReset;
+}
+
diff --git a/src/uqm/supermelee/netplay/proto/reset.h b/src/uqm/supermelee/netplay/proto/reset.h
new file mode 100644
index 0000000..e16b1d1
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/reset.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_
+#define UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_
+
+#include "../netconnection.h"
+#include "../packet.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void Netplay_setResetCallback(NetConnection *conn,
+ NetConnection_ResetCallback callback, void *resetArg);
+void Netplay_localReset(NetConnection *conn, NetplayResetReason reason);
+void Netplay_remoteReset(NetConnection *conn, NetplayResetReason reason);
+bool Netplay_isLocalReset(const NetConnection *conn);
+bool Netplay_isRemoteReset(const NetConnection *conn);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_ */
+
diff --git a/src/uqm/supermelee/pickmele.c b/src/uqm/supermelee/pickmele.c
new file mode 100644
index 0000000..0ce6489
--- /dev/null
+++ b/src/uqm/supermelee/pickmele.c
@@ -0,0 +1,948 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define PICKMELE_INTERNAL
+#include "pickmele.h"
+
+#include "../battlecontrols.h"
+#include "../battle.h"
+#include "../build.h"
+#include "../controls.h"
+#include "../flash.h"
+#include "../igfxres.h"
+#include "../intel.h"
+#include "../master.h"
+#include "../nameref.h"
+#include "melee.h"
+#ifdef NETPLAY
+# include "netplay/netmelee.h"
+# include "netplay/netmisc.h"
+# include "netplay/notify.h"
+#endif
+#include "../races.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "libs/async.h"
+#include "libs/log.h"
+#include "libs/mathlib.h"
+
+
+#define NUM_PICKMELEE_ROWS 2
+#define NUM_PICKMELEE_COLUMNS 7
+
+#define PICK_X_OFFS 57
+#define PICK_Y_OFFS 24
+#define PICK_SIDE_OFFS 100
+
+#define NAME_AREA_HEIGHT 7
+#define MELEE_WIDTH 149
+#define MELEE_HEIGHT (48 + NAME_AREA_HEIGHT)
+
+#define PICKSHIP_TEAM_NAME_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+#define PICKSHIP_TEAM_START_VALUE_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x04, 0x05, 0x1F), 0x4B)
+
+
+#ifdef NETPLAY
+static void reportShipSelected (GETMELEE_STATE *gms, COUNT index);
+#endif
+
+
+FRAME PickMeleeFrame;
+
+
+static FleetShipIndex
+PickMelee_GetShipIndex (BYTE row, BYTE col)
+{
+ return row * NUM_PICKMELEE_COLUMNS + col;
+}
+
+static BYTE
+PickMelee_GetShipRow (FleetShipIndex index)
+{
+ return index / NUM_PICKMELEE_COLUMNS;
+}
+
+static BYTE
+PickMelee_GetShipColumn (int index)
+{
+ return index % NUM_PICKMELEE_COLUMNS;
+}
+
+// Returns the <index>th ship in the queue, or 0 if it is not available.
+static HSTARSHIP
+MeleeShipByQueueIndex (const QUEUE *queue, COUNT index)
+{
+ HSTARSHIP hShip;
+ HSTARSHIP hNextShip;
+
+ for (hShip = GetHeadLink (queue); hShip != 0; hShip = hNextShip)
+ {
+ STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
+ if (StarShipPtr->index == index)
+ {
+ hNextShip = hShip;
+ if (StarShipPtr->SpeciesID == NO_ID)
+ hShip = 0;
+ UnlockStarShip (queue, hNextShip);
+ break;
+ }
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockStarShip (queue, hShip);
+ }
+
+ return hShip;
+}
+
+// Returns the <index>th available ship in the queue.
+static HSTARSHIP
+MeleeShipByUsedIndex (const QUEUE *queue, COUNT index)
+{
+ HSTARSHIP hShip;
+ HSTARSHIP hNextShip;
+
+ for (hShip = GetHeadLink (queue); hShip != 0; hShip = hNextShip)
+ {
+ STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
+ if ((StarShipPtr->SpeciesID != NO_ID) && index-- == 0)
+ {
+ UnlockStarShip (queue, hShip);
+ break;
+ }
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockStarShip (queue, hShip);
+ }
+
+ return hShip;
+}
+
+#if 0
+static COUNT
+queueIndexFromShip (HSTARSHIP hShip)
+{
+ COUNT result;
+ STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
+ result = StarShipPtr->index;
+ UnlockStarShip (queue, hShip);
+}
+#endif
+
+// Pre: called does not hold the graphics lock
+static void
+PickMelee_ChangedSelection (GETMELEE_STATE *gms, COUNT playerI)
+{
+ RECT r;
+ r.corner.x = PICK_X_OFFS + ((ICON_WIDTH + 2) * gms->player[playerI].col);
+ r.corner.y = PICK_Y_OFFS + ((ICON_HEIGHT + 2) * gms->player[playerI].row)
+ + ((1 - playerI) * PICK_SIDE_OFFS);
+ r.extent.width = (ICON_WIDTH + 2);
+ r.extent.height = (ICON_HEIGHT + 2);
+ Flash_setRect (gms->player[playerI].flashContext, &r);
+}
+
+// Only returns false when there is no ship for the choice.
+bool
+setShipSelected(GETMELEE_STATE *gms, COUNT playerI, COUNT choice,
+ bool reportNetwork)
+{
+ HSTARSHIP ship;
+
+ assert (!gms->player[playerI].done);
+
+ if (choice == (COUNT) ~0)
+ {
+ // Random ship selection.
+ ship = MeleeShipByUsedIndex (&race_q[playerI],
+ gms->player[playerI].randomIndex);
+ }
+ else
+ {
+ // Explicit ship selection.
+ ship = MeleeShipByQueueIndex (&race_q[playerI], choice);
+ }
+
+ if (ship == 0)
+ return false;
+
+ gms->player[playerI].choice = choice;
+ gms->player[playerI].hBattleShip = ship;
+ PlayMenuSound (MENU_SOUND_SUCCESS);
+#ifdef NETPLAY
+ if (reportNetwork)
+ reportShipSelected (gms, choice);
+#else
+ (void) reportNetwork;
+#endif
+ gms->player[playerI].done = true;
+ return true;
+}
+
+// Returns FALSE if aborted.
+static BOOLEAN
+SelectShip_processInput (GETMELEE_STATE *gms, COUNT playerI,
+ BATTLE_INPUT_STATE inputState)
+{
+ if (inputState & BATTLE_WEAPON)
+ {
+ if (gms->player[playerI].col == NUM_PICKMELEE_COLUMNS &&
+ gms->player[playerI].row == 0)
+ {
+ // Random ship
+ (void) setShipSelected (gms, playerI, (COUNT) ~0, TRUE);
+ }
+ else if (gms->player[playerI].col == NUM_PICKMELEE_COLUMNS &&
+ gms->player[playerI].row == 1)
+ {
+ // Selected exit
+ if (ConfirmExit ())
+ return FALSE;
+ }
+ else
+ {
+ // Selection is on a ship slot.
+ COUNT slotNr = PickMelee_GetShipIndex (gms->player[playerI].row,
+ gms->player[playerI].col);
+ (void) setShipSelected (gms, playerI, slotNr, TRUE);
+ // If the choice is not valid, setShipSelected()
+ // will not set .done.
+ }
+ }
+ else
+ {
+ // Process motion commands.
+ COUNT new_row, new_col;
+
+ new_row = gms->player[playerI].row;
+ new_col = gms->player[playerI].col;
+ if (inputState & BATTLE_LEFT)
+ {
+ if (new_col-- == 0)
+ new_col = NUM_PICKMELEE_COLUMNS;
+ }
+ else if (inputState & BATTLE_RIGHT)
+ {
+ if (new_col++ == NUM_PICKMELEE_COLUMNS)
+ new_col = 0;
+ }
+ if (inputState & BATTLE_THRUST)
+ {
+ if (new_row-- == 0)
+ new_row = NUM_PICKMELEE_ROWS - 1;
+ }
+ else if (inputState & BATTLE_DOWN)
+ {
+ if (++new_row == NUM_PICKMELEE_ROWS)
+ new_row = 0;
+ }
+
+ if (new_row != gms->player[playerI].row ||
+ new_col != gms->player[playerI].col)
+ {
+ gms->player[playerI].row = new_row;
+ gms->player[playerI].col = new_col;
+
+ PlayMenuSound (MENU_SOUND_MOVE);
+ PickMelee_ChangedSelection (gms, playerI);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOLEAN
+selectShipHuman (HumanInputContext *context, GETMELEE_STATE *gms)
+{
+ BATTLE_INPUT_STATE inputState =
+ PulsedInputToBattleInput (context->playerNr);
+
+ return SelectShip_processInput (gms, context->playerNr, inputState);
+}
+
+BOOLEAN
+selectShipComputer (ComputerInputContext *context, GETMELEE_STATE *gms)
+{
+#define COMPUTER_SELECTION_DELAY (ONE_SECOND >> 1)
+ TimeCount now = GetTimeCounter ();
+ if (now < gms->player[context->playerNr].timeIn +
+ COMPUTER_SELECTION_DELAY)
+ return TRUE;
+
+ return SelectShip_processInput (gms, context->playerNr, BATTLE_WEAPON);
+ // Simulate selection of the random choice button.
+}
+
+#ifdef NETPLAY
+BOOLEAN
+selectShipNetwork (NetworkInputContext *context, GETMELEE_STATE *gms)
+{
+ flushPacketQueues ();
+ // Sets gms->player[context->playerNr].remoteSelected if input
+ // is received.
+ if (gms->player[context->playerNr].remoteSelected)
+ gms->player[context->playerNr].done = TRUE;
+
+ return TRUE;
+}
+#endif
+
+// Select a new ship from the fleet for battle.
+// Returns 'TRUE' if no choice has been made yet; this function is to be
+// called again later.
+// Returns 'FALSE' if a choice has been made. gms->hStarShip is set
+// to the chosen (or randomly selected) ship, or to 0 if 'exit' has
+// been chosen.
+/* TODO: Include player timeouts */
+static BOOLEAN
+DoGetMelee (GETMELEE_STATE *gms)
+{
+ BOOLEAN done;
+ COUNT playerI;
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+
+ if (!gms->Initialized)
+ {
+ gms->Initialized = TRUE;
+ return TRUE;
+ }
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ if (!gms->player[playerI].selecting)
+ continue;
+
+ if (!gms->player[playerI].done)
+ Flash_process (gms->player[playerI].flashContext);
+ }
+
+ SleepThread (ONE_SECOND / 120);
+
+#ifdef NETPLAY
+ netInput ();
+
+ if (!allConnected ())
+ goto aborted;
+#endif
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ goto aborted;
+
+ done = TRUE;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ if (!gms->player[playerI].selecting)
+ continue;
+
+ if (!gms->player[playerI].done) {
+ if (!PlayerInput[playerI]->handlers->selectShip (
+ PlayerInput[playerI], gms))
+ goto aborted;
+
+ if (gms->player[playerI].done)
+ {
+ Flash_terminate (gms->player[playerI].flashContext);
+ gms->player[playerI].flashContext = NULL;
+ }
+ else
+ done = FALSE;
+ }
+ }
+
+#ifdef NETPLAY
+ flushPacketQueues ();
+#endif
+ return !done;
+
+aborted:
+#ifdef NETPLAY
+ flushPacketQueues ();
+#endif
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ if (!gms->player[playerI].selecting)
+ continue;
+
+ gms->player[playerI].choice = 0;
+ gms->player[playerI].hBattleShip = 0;
+ }
+ GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
+ return FALSE;
+}
+
+static COUNT
+GetRaceQueueValue (const QUEUE *queue) {
+ COUNT result;
+ HSTARSHIP hBattleShip, hNextShip;
+
+ result = 0;
+ for (hBattleShip = GetHeadLink (queue);
+ hBattleShip != 0; hBattleShip = hNextShip)
+ {
+ STARSHIP *StarShipPtr = LockStarShip (queue, hBattleShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+
+ if (StarShipPtr->SpeciesID == NO_ID)
+ continue; // Not active any more.
+
+ result += StarShipPtr->ship_cost;
+
+ UnlockStarShip (queue, hBattleShip);
+ }
+
+ return result;
+}
+
+// Cross out the icon for the dead ship.
+// 'frame' is the PickMeleeFrame for the player.
+// 'shipI' is the index in the ship list.
+// Pre: caller holds the graphics lock.
+static void
+CrossOutShip (FRAME frame, COUNT shipNr)
+{
+ CONTEXT OldContext;
+ STAMP s;
+ BYTE row = PickMelee_GetShipRow (shipNr);
+ BYTE col = PickMelee_GetShipColumn (shipNr);
+
+ OldContext = SetContext (OffScreenContext);
+
+ SetContextFGFrame (frame);
+
+ s.origin.x = 3 + ((ICON_WIDTH + 2) * col);
+ s.origin.y = 9 + ((ICON_HEIGHT + 2) * row);
+ s.frame = SetAbsFrameIndex (StatusFrame, 3);
+ // Cross for through the ship image.
+ DrawStamp (&s);
+
+ SetContext (OldContext);
+}
+
+// Draw the value of the fleet in the top right of the PickMeleeFrame.
+// Pre: caller holds the graphics lock.
+static void
+UpdatePickMeleeFleetValue (FRAME frame, COUNT which_player)
+{
+ CONTEXT OldContext;
+ COUNT value;
+ RECT r;
+ TEXT t;
+ UNICODE buf[40];
+
+ value = GetRaceQueueValue (&race_q[which_player]);
+
+ OldContext = SetContext (OffScreenContext);
+ SetContextFGFrame (frame);
+
+ // Erase the old value text.
+ GetFrameRect (frame, &r);
+ r.extent.width -= 4;
+ t.baseline.x = r.extent.width;
+ r.corner.x = r.extent.width - (6 * 3);
+ r.corner.y = 2;
+ r.extent.width = (6 * 3);
+ r.extent.height = 7 - 2;
+ SetContextForeGroundColor (PICK_BG_COLOR);
+ DrawFilledRectangle (&r);
+
+ // Draw the new value text.
+ sprintf (buf, "%d", value);
+ t.baseline.y = 7;
+ t.align = ALIGN_RIGHT;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (PICK_VALUE_COLOR);
+ font_DrawText (&t);
+
+ SetContext (OldContext);
+}
+
+// Create a frame for each player to display their current fleet in,
+// to be used when selecting the next ship to fight with.
+void
+BuildPickMeleeFrame (void)
+{
+ STAMP s;
+ CONTEXT OldContext = SetContext (OffScreenContext);
+
+ if (PickMeleeFrame)
+ DestroyDrawable (ReleaseDrawable (PickMeleeFrame));
+
+ PickMeleeFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP, MELEE_WIDTH, MELEE_HEIGHT, 2));
+ s.origin.x = 0;
+ s.origin.y = 0;
+
+ s.frame = CaptureDrawable (LoadGraphic (MELEE_PICK_MASK_PMAP_ANIM));
+ SetContextFGFrame (PickMeleeFrame);
+ DrawStamp (&s);
+
+ s.frame = IncFrameIndex (s.frame);
+ SetContextFGFrame (IncFrameIndex (PickMeleeFrame));
+ DrawStamp (&s);
+
+ DestroyDrawable (ReleaseDrawable (s.frame));
+
+ SetContext (OldContext);
+}
+
+// Put the ship icons in the PickMeleeFrame, and create a queue
+// for each player.
+// XXX TODO: split off creating the queue into a separate function.
+void
+FillPickMeleeFrame (MeleeSetup *setup)
+{
+ COUNT i;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (OffScreenContext);
+
+ for (i = 0; i < NUM_SIDES; ++i)
+ {
+ COUNT side;
+ COUNT sideI;
+ RECT r;
+ TEXT t;
+ STAMP s;
+ UNICODE buf[30];
+ FleetShipIndex index;
+
+ sideI = GetPlayerOrder (i);
+ side = !sideI;
+
+ s.frame = SetAbsFrameIndex (PickMeleeFrame, side);
+ SetContextFGFrame (s.frame);
+
+ GetFrameRect (s.frame, &r);
+ t.baseline.x = r.extent.width >> 1;
+ t.baseline.y = r.extent.height - NAME_AREA_HEIGHT + 4;
+
+ r.corner.x += 2;
+ r.corner.y += 2;
+ r.extent.width -= (2 * 2) + (ICON_WIDTH + 2) + 1;
+ r.extent.height -= (2 * 2) + NAME_AREA_HEIGHT;
+ SetContextForeGroundColor (PICK_BG_COLOR);
+ DrawFilledRectangle (&r);
+
+ r.corner.x += 2;
+ r.extent.width += (ICON_WIDTH + 2) - (2 * 2);
+ r.corner.y += r.extent.height;
+ r.extent.height = NAME_AREA_HEIGHT;
+ DrawFilledRectangle (&r);
+
+ // Team name at the bottom of the frame:
+ t.align = ALIGN_CENTER;
+ t.pStr = MeleeSetup_getTeamName (setup, sideI);
+ t.CharCount = (COUNT) ~0;
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (PICKSHIP_TEAM_NAME_TEXT_COLOR);
+ font_DrawText (&t);
+
+ // Total team value of the starting team:
+ sprintf (buf, "%u", MeleeSetup_getFleetValue (setup, sideI));
+ t.baseline.x = 4;
+ t.baseline.y = 7;
+ t.align = ALIGN_LEFT;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (PICKSHIP_TEAM_START_VALUE_COLOR);
+ font_DrawText (&t);
+
+ assert (CountLinks (&race_q[side]) == 0);
+
+ for (index = 0; index < MELEE_FLEET_SIZE; index++)
+ {
+ MeleeShip StarShip;
+
+ StarShip = MeleeSetup_getShip (setup, sideI, index);
+ if (StarShip == MELEE_NONE)
+ continue;
+
+ {
+ BYTE row, col;
+ BYTE ship_cost;
+ HMASTERSHIP hMasterShip;
+ HSTARSHIP hBuiltShip;
+ MASTER_SHIP_INFO *MasterPtr;
+ STARSHIP *BuiltShipPtr;
+ BYTE captains_name_index;
+
+ hMasterShip = GetStarShipFromIndex (&master_q, StarShip);
+ MasterPtr = LockMasterShip (&master_q, hMasterShip);
+
+ captains_name_index = NameCaptain (&race_q[side],
+ MasterPtr->SpeciesID);
+ hBuiltShip = Build (&race_q[side], MasterPtr->SpeciesID);
+
+ // Draw the icon.
+ row = PickMelee_GetShipRow (index);
+ col = PickMelee_GetShipColumn (index);
+ s.origin.x = 4 + ((ICON_WIDTH + 2) * col);
+ s.origin.y = 10 + ((ICON_HEIGHT + 2) * row);
+ s.frame = MasterPtr->ShipInfo.icons;
+ DrawStamp (&s);
+
+ ship_cost = MasterPtr->ShipInfo.ship_cost;
+ UnlockMasterShip (&master_q, hMasterShip);
+
+ BuiltShipPtr = LockStarShip (&race_q[side], hBuiltShip);
+ BuiltShipPtr->index = index;
+ BuiltShipPtr->ship_cost = ship_cost;
+ BuiltShipPtr->playerNr = side;
+ BuiltShipPtr->captains_name_index = captains_name_index;
+ // The next ones are not used in Melee
+ BuiltShipPtr->crew_level = 0;
+ BuiltShipPtr->max_crew = 0;
+ BuiltShipPtr->race_strings = 0;
+ BuiltShipPtr->icons = 0;
+ BuiltShipPtr->RaceDescPtr = 0;
+ UnlockStarShip (&race_q[side], hBuiltShip);
+ }
+ }
+ }
+
+ SetContext (OldContext);
+}
+
+void
+DestroyPickMeleeFrame (void)
+{
+ DestroyDrawable (ReleaseDrawable (PickMeleeFrame));
+ PickMeleeFrame = 0;
+}
+
+// Pre: caller holds the graphics lock.
+static void
+DrawPickMeleeFrame (COUNT which_player)
+{
+ CONTEXT oldContext;
+ STAMP s;
+
+ oldContext = SetContext (SpaceContext);
+ s.frame = SetAbsFrameIndex (PickMeleeFrame, which_player);
+ s.origin.x = PICK_X_OFFS - 3;
+ s.origin.y = PICK_Y_OFFS - 9 + ((1 - which_player) * PICK_SIDE_OFFS);
+ DrawStamp (&s);
+ // Draw the selection box to screen.
+
+ SetContext (oldContext);
+}
+
+// Pre: caller holds the graphics lock.
+void
+MeleeGameOver (void)
+{
+ COUNT playerI;
+ DWORD TimeOut;
+ BOOLEAN PressState, ButtonState;
+
+ // Show the battle result.
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ DrawPickMeleeFrame (playerI);
+
+
+#ifdef NETPLAY
+ negotiateReadyConnections(true, NetState_inSetup);
+#endif
+
+ TimeOut = GetTimeCounter () + (ONE_SECOND * 4);
+
+ PressState = PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL];
+ do
+ {
+ UpdateInputState ();
+ ButtonState = PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL];
+ if (PressState)
+ {
+ PressState = ButtonState;
+ ButtonState = FALSE;
+ }
+
+ Async_process ();
+ TaskSwitch ();
+ } while (!(GLOBAL (CurrentActivity) & CHECK_ABORT) && (!ButtonState
+ && (!(PlayerControl[0] & PlayerControl[1] & PSYTRON_CONTROL)
+ || GetTimeCounter () < TimeOut)));
+
+}
+
+void
+MeleeShipDeath (STARSHIP *ship)
+{
+ FRAME frame;
+
+ // Deactivate fleet position.
+ ship->SpeciesID = NO_ID;
+
+ frame = SetAbsFrameIndex (PickMeleeFrame, ship->playerNr);
+ CrossOutShip (frame, ship->index);
+ UpdatePickMeleeFleetValue (frame, ship->playerNr);
+}
+
+// Post: the NetState for all players is NetState_interBattle
+static BOOLEAN
+GetMeleeStarShips (COUNT playerMask, HSTARSHIP *ships)
+{
+ COUNT playerI;
+ BOOLEAN ok;
+ GETMELEE_STATE gmstate;
+ TimeCount now;
+ COUNT i;
+
+#ifdef NETPLAY
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn;
+
+ if ((playerMask & (1 << playerI)) == 0)
+ continue;
+
+ // XXX: This does not have to be done per connection.
+ conn = netConnections[playerI];
+ if (conn != NULL) {
+ BattleStateData *battleStateData;
+ battleStateData =
+ (BattleStateData *) NetConnection_getStateData (conn);
+ battleStateData->getMeleeState = &gmstate;
+ }
+ }
+#endif
+
+ ok = true;
+
+ now = GetTimeCounter ();
+ gmstate.InputFunc = DoGetMelee;
+ gmstate.Initialized = FALSE;
+ for (i = 0; i < NUM_PLAYERS; ++i)
+ {
+ // We have to use TFB_Random() results in specific order
+ playerI = GetPlayerOrder (i);
+ gmstate.player[playerI].selecting =
+ (playerMask & (1 << playerI)) != 0;
+ gmstate.player[playerI].ships_left = battle_counter[playerI];
+
+ // We determine in advance which ship would be chosen if the player
+ // wants a random ship, to keep it simple to keep network parties
+ // synchronised.
+ gmstate.player[playerI].randomIndex =
+ (COUNT)TFB_Random () % gmstate.player[playerI].ships_left;
+ gmstate.player[playerI].done = FALSE;
+
+ if (!gmstate.player[playerI].selecting)
+ continue;
+
+ gmstate.player[playerI].timeIn = now;
+ gmstate.player[playerI].row = 0;
+ gmstate.player[playerI].col = NUM_PICKMELEE_COLUMNS;
+#ifdef NETPLAY
+ gmstate.player[playerI].remoteSelected = FALSE;
+#endif
+
+ gmstate.player[playerI].flashContext =
+ Flash_createHighlight (ScreenContext, NULL);
+ Flash_setMergeFactors (gmstate.player[playerI].flashContext,
+ 2, 3, 2);
+ Flash_setFrameTime (gmstate.player[playerI].flashContext,
+ ONE_SECOND / 16);
+#ifdef NETPLAY
+ if (PlayerControl[playerI] & NETWORK_CONTROL)
+ Flash_setSpeed (gmstate.player[playerI].flashContext,
+ ONE_SECOND / 2, 0, ONE_SECOND / 2, 0);
+ else
+#endif
+ {
+ Flash_setSpeed (gmstate.player[playerI].flashContext,
+ 0, ONE_SECOND / 16, 0, ONE_SECOND / 16);
+ }
+ PickMelee_ChangedSelection (&gmstate, playerI);
+ Flash_start (gmstate.player[playerI].flashContext);
+ }
+
+#ifdef NETPLAY
+ {
+ // NB. gmstate.player[].randomIndex and gmstate.player[].done must
+ // be initialised before negotiateReadyConnections is completed, to
+ // ensure that they are initialised when the SelectShip packet
+ // arrives.
+ bool allOk = negotiateReadyConnections (true, NetState_selectShip);
+ if (!allOk)
+ {
+ // Some network connection has been reset.
+ ok = false;
+ }
+ }
+#endif
+ SetDefaultMenuRepeatDelay ();
+
+ SetContext (OffScreenContext);
+
+
+ DoInput (&gmstate, FALSE);
+ WaitForSoundEnd (0);
+
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ if (!gmstate.player[playerI].selecting)
+ continue;
+
+ if (gmstate.player[playerI].done)
+ {
+ // Flash rectangle is already terminated.
+ ships[playerI] = gmstate.player[playerI].hBattleShip;
+ }
+ else
+ {
+ Flash_terminate (gmstate.player[playerI].flashContext);
+ gmstate.player[playerI].flashContext = NULL;
+ ok = false;
+ }
+ }
+
+#ifdef NETPLAY
+ if (ok)
+ {
+ if (!negotiateReadyConnections (true, NetState_interBattle))
+ ok = false;
+ }
+ else
+ setStateConnections (NetState_interBattle);
+#endif
+
+ if (!ok)
+ {
+ // Aborting.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ }
+
+#ifdef NETPLAY
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn;
+
+ if ((playerMask & (1 << playerI)) == 0)
+ continue;
+
+ // XXX: This does not have to be done per connection.
+ conn = netConnections[playerI];
+ if (conn != NULL && NetConnection_isConnected (conn))
+ {
+ BattleStateData *battleStateData;
+ battleStateData =
+ (BattleStateData *) NetConnection_getStateData (conn);
+ battleStateData->getMeleeState = NULL;
+ }
+ }
+#endif
+
+ return ok;
+}
+
+BOOLEAN
+GetInitialMeleeStarShips (HSTARSHIP *result)
+{
+ COUNT playerI;
+ COUNT playerMask;
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ FRAME frame;
+ frame = SetAbsFrameIndex (PickMeleeFrame, playerI);
+ UpdatePickMeleeFleetValue (frame, playerI);
+ DrawPickMeleeFrame (playerI);
+ }
+
+ // Fade in
+ SleepThreadUntil (FadeScreen (FadeAllToColor, ONE_SECOND / 2)
+ + ONE_SECOND / 60);
+ FlushColorXForms ();
+
+ playerMask = 0;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ playerMask |= (1 << playerI);
+
+ return GetMeleeStarShips (playerMask, result);
+}
+
+// Get the next ship to use in SuperMelee.
+BOOLEAN
+GetNextMeleeStarShip (COUNT which_player, HSTARSHIP *result)
+{
+ COUNT playerMask;
+ HSTARSHIP ships[NUM_PLAYERS];
+ BOOLEAN ok;
+
+ DrawPickMeleeFrame (which_player);
+
+ playerMask = 1 << which_player;
+ ok = GetMeleeStarShips (playerMask, ships);
+ if (ok)
+ *result = ships[which_player];
+
+ return ok;
+}
+
+#ifdef NETPLAY
+// Called when a ship selection has arrived from a remote player.
+bool
+updateMeleeSelection (GETMELEE_STATE *gms, COUNT playerI, COUNT ship)
+{
+ if (gms == NULL || !gms->player[playerI].selecting ||
+ gms->player[playerI].done)
+ {
+ // This happens when we get an update message from a connection
+ // for who we are not selecting a ship.
+ log_add (log_Warning, "Unexpected ship selection packet "
+ "received.\n");
+ return false;
+ }
+
+ if (!setShipSelected (gms, playerI, ship, false))
+ {
+ log_add (log_Warning, "Invalid ship selection received from remote "
+ "party.\n");
+ return false;
+ }
+
+ gms->player[playerI].remoteSelected = TRUE;
+ return true;
+}
+
+static void
+reportShipSelected (GETMELEE_STATE *gms, COUNT index)
+{
+ size_t playerI;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn = netConnections[playerI];
+
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ Netplay_Notify_shipSelected (conn, index);
+ }
+ (void) gms;
+}
+#endif
+
diff --git a/src/uqm/supermelee/pickmele.h b/src/uqm/supermelee/pickmele.h
new file mode 100644
index 0000000..3588063
--- /dev/null
+++ b/src/uqm/supermelee/pickmele.h
@@ -0,0 +1,102 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_PICKMELE_H_
+#define UQM_SUPERMELEE_PICKMELE_H_
+
+typedef struct getmelee_struct GETMELEE_STATE;
+
+#include "../races.h"
+#include "../battlecontrols.h"
+#include "meleesetup.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void MeleeShipDeath (STARSHIP *);
+void BuildPickMeleeFrame (void);
+void DestroyPickMeleeFrame (void);
+void FillPickMeleeFrame (MeleeSetup *setup);
+void MeleeGameOver (void);
+BOOLEAN GetInitialMeleeStarShips (HSTARSHIP *result);
+BOOLEAN GetNextMeleeStarShip (COUNT which_player, HSTARSHIP *result);
+
+bool updateMeleeSelection (GETMELEE_STATE *gms, COUNT player, COUNT ship);
+
+BOOLEAN selectShipHuman (HumanInputContext *context, GETMELEE_STATE *gms);
+BOOLEAN selectShipComputer (ComputerInputContext *context,
+ GETMELEE_STATE *gms);
+#ifdef NETPLAY
+BOOLEAN selectShipNetwork (NetworkInputContext *context, GETMELEE_STATE *gms);
+#endif /* NETPLAY */
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef PICKMELE_INTERNAL
+
+#include "../flash.h"
+#include "libs/timelib.h"
+#include "../init.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct getmelee_struct {
+ BOOLEAN (*InputFunc) (struct getmelee_struct *pInputState);
+
+ BOOLEAN Initialized;
+
+ struct {
+ TimeCount timeIn;
+ HSTARSHIP hBattleShip;
+ // Chosen ship.
+ COUNT choice;
+ // Index of chosen ship, or (COUNT) ~0 for random choice.
+
+ COUNT row;
+ COUNT col;
+ COUNT ships_left;
+ // Number of ships still available.
+ COUNT randomIndex;
+ // Pre-generated random number.
+ BOOLEAN selecting;
+ // Is this player selecting a ship?
+ BOOLEAN done;
+ // Has a selection been made for this player?
+ FlashContext *flashContext;
+ // Context for controlling the flash rectangle.
+#ifdef NETPLAY
+ BOOLEAN remoteSelected;
+#endif
+ } player[NUM_PLAYERS];
+};
+
+bool setShipSelected(GETMELEE_STATE *gms, COUNT playerI, COUNT choice,
+ bool reportNetwork);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* PICKMELE_INTERNAL */
+
+#endif /* UQM_SUPERMELEE_PICKMELE_H_ */
+
diff --git a/src/uqm/tactrans.c b/src/uqm/tactrans.c
new file mode 100644
index 0000000..4e2b896
--- /dev/null
+++ b/src/uqm/tactrans.c
@@ -0,0 +1,1032 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "tactrans.h"
+
+#include "battlecontrols.h"
+#include "build.h"
+#include "collide.h"
+#include "globdata.h"
+#include "element.h"
+#include "ship.h"
+#include "status.h"
+#include "battle.h"
+#include "init.h"
+#include "supermelee/pickmele.h"
+#ifdef NETPLAY
+# include "supermelee/netplay/netmelee.h"
+# include "supermelee/netplay/netmisc.h"
+# include "supermelee/netplay/notify.h"
+# include "supermelee/netplay/proto/ready.h"
+# include "supermelee/netplay/packet.h"
+# include "supermelee/netplay/packetq.h"
+#endif
+#include "races.h"
+#include "encount.h"
+#include "settings.h"
+#include "sounds.h"
+#include "libs/mathlib.h"
+
+
+static void cleanup_dead_ship (ELEMENT *ElementPtr);
+
+static BOOLEAN dittyIsPlaying;
+static STARSHIP *winnerStarShip;
+ // Indicates which ship is the winner of the current battle.
+ // The winner will be last to pick the next ship.
+
+
+BOOLEAN
+OpponentAlive (STARSHIP *TestStarShipPtr)
+{
+ HELEMENT hElement, hSuccElement;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hSuccElement)
+ {
+ ELEMENT *ElementPtr;
+ STARSHIP *StarShipPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hSuccElement = GetSuccElement (ElementPtr);
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ UnlockElement (hElement);
+
+ if (StarShipPtr && StarShipPtr != TestStarShipPtr
+ && StarShipPtr->RaceDescPtr->ship_info.crew_level == 0)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+PlayDitty (STARSHIP *ship)
+{
+ PlayMusic (ship->RaceDescPtr->ship_data.victory_ditty, FALSE, 3);
+ dittyIsPlaying = TRUE;
+}
+
+void
+StopDitty (void)
+{
+ if (dittyIsPlaying)
+ StopMusic ();
+ dittyIsPlaying = FALSE;
+}
+
+static BOOLEAN
+DittyPlaying (void)
+{
+ if (!dittyIsPlaying)
+ return FALSE;
+
+ dittyIsPlaying = PLRPlaying ((MUSIC_REF)~0);
+ return dittyIsPlaying;
+}
+
+void
+ResetWinnerStarShip (void)
+{
+ winnerStarShip = NULL;
+}
+
+#ifdef NETPLAY
+static void
+readyToEnd2Callback (NetConnection *conn, void *arg)
+{
+ NetConnection_setState (conn, NetState_endingBattle2);
+ (void) arg;
+}
+
+static void
+readyToEndCallback (NetConnection *conn, void *arg)
+{
+ // This callback function gets called from inside the function that
+ // updates the frame counter, but this is not a problem as the
+ // ending frame count will at least be 1 greater than the current
+ // frame count.
+
+ BattleStateData *battleStateData;
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+
+#ifdef NETPLAY_DEBUG
+ fprintf (stderr, "Both sides are ready to end the battle; starting "
+ "end-of-battle synchronisation.\n");
+#endif
+ NetConnection_setState (conn, NetState_endingBattle);
+ if (battleFrameCount + 1 > battleStateData->endFrameCount)
+ battleStateData->endFrameCount = battleFrameCount + 1;
+ Netplay_Notify_frameCount (conn, battleFrameCount + 1);
+ // The +1 is to ensure that after the remote side receives the
+ // frame count it will still receive one more frame data packet,
+ // so it will know in advance when the last frame data packet
+ // will come so it won't block. It also ensures that the
+ // local frame counter won't go past the sent number, which
+ // could happen when the function triggering the call to this
+ // function is the frame update function which might update
+ // the frame counter one more time.
+ flushPacketQueue (conn);
+#ifdef NETPLAY_DEBUG
+ fprintf (stderr, "NETPLAY: [%d] ==> Sent battleFrameCount %d.\n",
+ NetConnection_getPlayerNr(conn), battleFrameCount + 1);
+#endif
+ Netplay_localReady(conn, readyToEnd2Callback, NULL, false);
+ (void) arg;
+}
+
+/*
+ * When one player's ship dies, there's a delay before the next ship
+ * can be chosen. This time depends on the time the ditty is playing
+ * and may differ for each side.
+ * To synchronise the time, the following protocol is followed:
+ * 1. (NetState_inBattle) The Ready protocol is used to let either
+ * party know when they're ready to stop the battle.
+ * 2. (NetState_endingBattle) Each party sends the frame number of when
+ * it wants to end the battle, and continues until that point, where
+ * it waits until it has received the frame number of the other party.
+ * 3. After a player has both sent and received a frame count, the
+ * simulation continues for each party, until the maximum of both
+ * frame counts has been achieved.
+ * 4. The Ready protocol is used to let each side signal that it has
+ * reached the target frame count.
+ * 5. The battle ends.
+ */
+static bool
+readyForBattleEndPlayer (NetConnection *conn)
+{
+ BattleStateData *battleStateData;
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+
+ if (NetConnection_getState (conn) == NetState_interBattle ||
+ NetConnection_getState (conn) == NetState_inSetup)
+ {
+ // This connection is already ready. The entire synchronisation
+ // protocol has already been done for this connection.
+ return true;
+ }
+
+ if (NetConnection_getState (conn) == NetState_inBattle)
+ {
+ if (Netplay_isLocalReady(conn))
+ {
+ // We've already sent notice that we are ready, but we're
+ // still waiting for the other side to say it's ready too.
+ return false;
+ }
+
+ // We haven't yet told the other side we're ready. We do so now.
+ Netplay_localReady (conn, readyToEndCallback, NULL, true);
+ // This may set the state to endingBattle.
+
+ if (NetConnection_getState (conn) == NetState_inBattle)
+ return false;
+ }
+
+ assert (NetConnection_getState (conn) == NetState_endingBattle ||
+ NetConnection_getState (conn) == NetState_endingBattle2);
+
+ // Keep the simulation going as long as the target frame count
+ // hasn't been reached yet. Note that if the connection state is
+ // NetState_endingBattle, then we haven't yet received the
+ // remote frame count, so the target frame count may still rise.
+ if (battleFrameCount < battleStateData->endFrameCount)
+ return false;
+
+ if (NetConnection_getState (conn) == NetState_endingBattle)
+ {
+ // We have reached the target frame count, but we don't know
+ // the remote target frame count yet. So we wait until it has
+ // come in.
+ waitReady (conn);
+ // TODO: check whether all connections are still connected.
+ assert (NetConnection_getState (conn) == NetState_endingBattle2);
+
+ // Continue the simulation if the battleFrameCount has gone up.
+ if (battleFrameCount < battleStateData->endFrameCount)
+ return false;
+ }
+
+ // We are ready and wait for the other party to become ready too.
+ negotiateReady (conn, true, NetState_interBattle);
+
+ return true;
+}
+#endif
+
+bool
+battleEndReadyHuman (HumanInputContext *context)
+{
+ (void) context;
+ return true;
+}
+
+bool
+battleEndReadyComputer (ComputerInputContext *context)
+{
+ (void) context;
+ return true;
+}
+
+#ifdef NETPLAY
+bool
+battleEndReadyNetwork (NetworkInputContext *context)
+{
+ return readyForBattleEndPlayer (netConnections[context->playerNr]);
+}
+#endif
+
+// Returns true iff this side is ready to end the battle.
+static inline bool
+readyForBattleEnd (void)
+{
+#ifndef NETPLAY
+#if DEMO_MODE
+ // In Demo mode, the saved journal should be replayed with frame
+ // accuracy. PLRPlaying () isn't consistent enough.
+ return true;
+#else /* !DEMO_MODE */
+ return !DittyPlaying ();
+#endif /* !DEMO_MODE */
+#else /* defined (NETPLAY) */
+ int playerI;
+
+ if (DittyPlaying ())
+ return false;
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ if (!PlayerInput[playerI]->handlers->battleEndReady (
+ PlayerInput[playerI]))
+ return false;
+
+ return true;
+#endif /* defined (NETPLAY) */
+}
+
+static void
+preprocess_dead_ship (ELEMENT *DeadShipPtr)
+{
+ ProcessSound ((SOUND)~0, NULL);
+ (void)DeadShipPtr; // unused argument
+}
+
+void
+cleanup_dead_ship (ELEMENT *DeadShipPtr)
+{
+ STARSHIP *DeadStarShipPtr;
+
+ ProcessSound ((SOUND)~0, NULL);
+
+ GetElementStarShip (DeadShipPtr, &DeadStarShipPtr);
+ {
+ // Ship explosion has finished, or ship has just warped out
+ // if DeadStarShipPtr->crew_level != 0
+ BOOLEAN MusicStarted;
+ HELEMENT hElement, hSuccElement;
+
+ /* Record crew left after the battle */
+ DeadStarShipPtr->crew_level =
+ DeadStarShipPtr->RaceDescPtr->ship_info.crew_level;
+
+ MusicStarted = FALSE;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hSuccElement)
+ {
+ ELEMENT *ElementPtr;
+ STARSHIP *StarShipPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hSuccElement = GetSuccElement (ElementPtr);
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ // Get the STARSHIP that this ELEMENT belongs to.
+
+ if (StarShipPtr == DeadStarShipPtr)
+ {
+ // This element belongs to the dead ship; it may be the
+ // ship's own element.
+ SetElementStarShip (ElementPtr, 0);
+
+ if (!(ElementPtr->state_flags & CREW_OBJECT)
+ || ElementPtr->preprocess_func != crew_preprocess)
+ {
+ // Set the element up for deletion.
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex],
+ NO_PRIM);
+ ElementPtr->life_span = 0;
+ ElementPtr->state_flags =
+ NONSOLID | DISAPPEARING | FINITE_LIFE;
+ ElementPtr->preprocess_func = 0;
+ ElementPtr->postprocess_func = 0;
+ ElementPtr->death_func = 0;
+ ElementPtr->collision_func = 0;
+ }
+ }
+
+ if (StarShipPtr
+ && (StarShipPtr->cur_status_flags & PLAY_VICTORY_DITTY))
+ {
+ // StarShipPtr points to the remaining ship.
+ MusicStarted = TRUE;
+ PlayDitty (StarShipPtr);
+ StarShipPtr->cur_status_flags &= ~PLAY_VICTORY_DITTY;
+ }
+
+ UnlockElement (hElement);
+ }
+
+#define MIN_DITTY_FRAME_COUNT ((ONE_SECOND * 3) / BATTLE_FRAME_RATE)
+ // The ship will be "alive" for at least 2 more frames to make sure
+ // the elements it owns (set up for deletion above) expire first.
+ // Ditty does NOT play in the following circumstances:
+ // * The winning ship dies before the loser finishes exploding
+ // * At the moment the losing ship dies, the winner has started
+ // the warp out sequence
+ DeadShipPtr->life_span = MusicStarted ? MIN_DITTY_FRAME_COUNT : 1;
+ if (DeadStarShipPtr == winnerStarShip)
+ { // This ship died but won the battle. We need to keep it alive
+ // longer than the dead opponent ship so that the winning player
+ // picks last.
+ DeadShipPtr->life_span = MIN_DITTY_FRAME_COUNT + 1;
+ }
+ DeadShipPtr->death_func = new_ship;
+ DeadShipPtr->preprocess_func = preprocess_dead_ship;
+ DeadShipPtr->state_flags &= ~DISAPPEARING;
+ // XXX: this increment was originally done by another piece of code
+ // just below this one. I am almost sure it is not needed, but it
+ // keeps the original framecount.
+ ++DeadShipPtr->life_span;
+ SetElementStarShip (DeadShipPtr, DeadStarShipPtr);
+ }
+}
+
+static void
+setMinShipLifeSpan (ELEMENT *ship, COUNT life_span)
+{
+ if (ship->death_func == new_ship)
+ { // The ship has finished exploding or warping out, and now
+ // we can work with the remaining element
+ assert (ship->state_flags & FINITE_LIFE);
+ assert (!(ship->state_flags & DISAPPEARING));
+ if (ship->life_span < life_span)
+ ship->life_span = life_span;
+ }
+}
+
+static void
+setMinStarShipLifeSpan (STARSHIP *starShip, COUNT life_span)
+{
+ ELEMENT *ship;
+
+ LockElement (starShip->hShip, &ship);
+ setMinShipLifeSpan (ship, life_span);
+ UnlockElement (starShip->hShip);
+}
+
+static void
+checkOtherShipLifeSpan (ELEMENT *deadShip)
+{
+ STARSHIP *deadStarShip;
+
+ GetElementStarShip (deadShip, &deadStarShip);
+
+ if (winnerStarShip != NULL && deadStarShip != winnerStarShip
+ && winnerStarShip->RaceDescPtr->ship_info.crew_level == 0)
+ { // The opponent ship also died but won anyway (e.g. Glory device)
+ // We need to keep the opponent ship alive longer so that the
+ // winning player picks last.
+ setMinStarShipLifeSpan (winnerStarShip, deadShip->life_span + 1);
+ }
+ else if (winnerStarShip == NULL)
+ { // Both died at the same time, or the loser has already expired
+ HELEMENT hElement, hNextElement;
+
+ // Find the other dead ship(s) and keep them alive for at least as
+ // long as this ship.
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *element;
+ STARSHIP *starShip;
+
+ LockElement (hElement, &element);
+ hNextElement = GetSuccElement (element);
+ GetElementStarShip (element, &starShip);
+
+ if (starShip != NULL && element != deadShip
+ && starShip->RaceDescPtr->ship_info.crew_level == 0)
+ { // This is another dead ship
+ setMinShipLifeSpan (element, deadShip->life_span);
+ }
+
+ UnlockElement (hElement);
+ }
+ }
+}
+
+// This function is called when dead ship element's life_span reaches 0
+void
+new_ship (ELEMENT *DeadShipPtr)
+{
+ STARSHIP *DeadStarShipPtr;
+
+ GetElementStarShip (DeadShipPtr, &DeadStarShipPtr);
+
+ if (!readyForBattleEnd ())
+ {
+ DeadShipPtr->state_flags &= ~DISAPPEARING;
+ ++DeadShipPtr->life_span;
+
+ // Keep the winner alive longer, or in a simultaneous destruction
+ // tie, keep the other dead ship alive so that readyForBattleEnd()
+ // is called for only one ship at a time.
+ // When a ship has been destroyed, each side of a network
+ // connection waits until the other side is ready.
+ // When two ships die at the same time, this is handled for one
+ // ship after the other.
+ checkOtherShipLifeSpan (DeadShipPtr);
+ return;
+ }
+
+ // Once a ship is being picked, we do not care about the winner anymore
+ winnerStarShip = NULL;
+
+ {
+ BOOLEAN RestartMusic;
+
+ StopDitty ();
+ StopMusic ();
+ StopSound ();
+
+ SetElementStarShip (DeadShipPtr, 0);
+ RestartMusic = OpponentAlive (DeadStarShipPtr);
+
+ free_ship (DeadStarShipPtr->RaceDescPtr, TRUE, TRUE);
+ DeadStarShipPtr->RaceDescPtr = 0;
+
+ // Graphics are batched while the draw queue is processed,
+ // but we are going to draw the ship selection box now
+ UnbatchGraphics ();
+
+#ifdef NETPLAY
+ initBattleStateDataConnections ();
+ {
+ bool allOk =
+ negotiateReadyConnections (true, NetState_interBattle);
+ // We are already in NetState_interBattle, but all
+ // sides just need to pass this checkpoint before
+ // going on.
+ if (!allOk)
+ {
+ // Some network connection has been reset.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ BatchGraphics ();
+ return;
+ }
+ }
+#endif /* NETPLAY */
+
+ if (!FleetIsInfinite (DeadStarShipPtr->playerNr))
+ { // This may be a dead ship (crew_level == 0) or a warped out ship
+ UpdateShipFragCrew (DeadStarShipPtr);
+ // Deactivate the ship (cannot be selected)
+ DeadStarShipPtr->SpeciesID = NO_ID;
+ }
+
+ if (GetNextStarShip (DeadStarShipPtr, DeadStarShipPtr->playerNr))
+ {
+#ifdef NETPLAY
+ {
+ bool allOk =
+ negotiateReadyConnections (true, NetState_inBattle);
+ if (!allOk)
+ {
+ // Some network connection has been reset.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ BatchGraphics ();
+ return;
+ }
+ }
+#endif
+ if (RestartMusic)
+ BattleSong (TRUE);
+ }
+ else if (battle_counter[0] == 0 || battle_counter[1] == 0)
+ {
+ // One player is out of ships. The battle is over.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ }
+#ifdef NETPLAY
+ else
+ {
+ // Battle has been aborted.
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ }
+#endif
+ BatchGraphics ();
+ }
+}
+
+static void
+explosion_preprocess (ELEMENT *ShipPtr)
+{
+ BYTE i;
+
+ i = (NUM_EXPLOSION_FRAMES * 3) - ShipPtr->life_span;
+ switch (i)
+ {
+ case 25:
+ ShipPtr->preprocess_func = NULL;
+ case 0:
+ case 1:
+ case 2:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ i = 1;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 18:
+ case 19:
+ i = 2;
+ break;
+ case 15:
+ SetPrimType (&DisplayArray[ShipPtr->PrimIndex], NO_PRIM);
+ ShipPtr->state_flags |= CHANGING;
+ default:
+ i = 3;
+ break;
+ }
+
+ do
+ {
+ HELEMENT hElement;
+
+ hElement = AllocElement ();
+ if (hElement)
+ {
+ COUNT angle, dist;
+ DWORD rand_val;
+ ELEMENT *ElementPtr;
+ extern FRAME explosion[];
+
+ PutElement (hElement);
+ LockElement (hElement, &ElementPtr);
+ ElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ ElementPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ ElementPtr->life_span = 9;
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMP_PRIM);
+ ElementPtr->current.image.farray = explosion;
+ ElementPtr->current.image.frame = explosion[0];
+ rand_val = TFB_Random ();
+ angle = LOBYTE (HIWORD (rand_val));
+ dist = DISPLAY_TO_WORLD (LOBYTE (LOWORD (rand_val)) % 8);
+ if (HIBYTE (LOWORD (rand_val)) < 256 * 1 / 3)
+ dist += DISPLAY_TO_WORLD (8);
+ ElementPtr->current.location.x =
+ ShipPtr->current.location.x + COSINE (angle, dist);
+ ElementPtr->current.location.y =
+ ShipPtr->current.location.y + SINE (angle, dist);
+ ElementPtr->preprocess_func = animation_preprocess;
+ rand_val = TFB_Random ();
+ angle = LOBYTE (LOWORD (rand_val));
+ dist = WORLD_TO_VELOCITY (
+ DISPLAY_TO_WORLD (HIBYTE (LOWORD (rand_val)) % 5));
+ SetVelocityComponents (&ElementPtr->velocity,
+ COSINE (angle, dist), SINE (angle, dist));
+ UnlockElement (hElement);
+ }
+ } while (--i);
+}
+
+void
+StopAllBattleMusic (void)
+{
+ StopDitty ();
+ StopMusic ();
+}
+
+STARSHIP *
+FindAliveStarShip (ELEMENT *deadShip)
+{
+ STARSHIP *aliveShip = NULL;
+ HELEMENT hElement, hNextElement;
+
+ // Find the remaining ship, if any, and see if it is still alive.
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ if ((ElementPtr->state_flags & PLAYER_SHIP)
+ && ElementPtr != deadShip
+ /* and not running away */
+ && ElementPtr->mass_points <= MAX_SHIP_MASS + 1)
+ {
+ GetElementStarShip (ElementPtr, &aliveShip);
+ assert (aliveShip != NULL);
+ if (aliveShip->RaceDescPtr->ship_info.crew_level == 0
+ /* reincarnating Pkunk is not actually dead */
+ && ElementPtr->mass_points != MAX_SHIP_MASS + 1)
+ {
+ aliveShip = NULL;
+ }
+
+ UnlockElement (hElement);
+ break;
+ }
+ hNextElement = GetSuccElement (ElementPtr);
+ UnlockElement (hElement);
+ }
+
+ return aliveShip;
+}
+
+STARSHIP *
+GetWinnerStarShip (void)
+{
+ return winnerStarShip;
+}
+
+void
+SetWinnerStarShip (STARSHIP *winner)
+{
+ if (winner == NULL)
+ return; // nothing to do
+
+ winner->cur_status_flags |= PLAY_VICTORY_DITTY;
+
+ // The winner is set once per battle. If both ships die, this function is
+ // called twice, once for each ship. We need to preserve the winner
+ // determined on the first call.
+ if (winnerStarShip == NULL)
+ winnerStarShip = winner;
+}
+
+void
+RecordShipDeath (ELEMENT *deadShip)
+{
+ STARSHIP *deadStarShip;
+
+ GetElementStarShip (deadShip, &deadStarShip);
+ assert (deadStarShip != NULL);
+
+ if (deadShip->mass_points <= MAX_SHIP_MASS)
+ { // Not running away.
+ // When a ship tries to run away, it is (dis)counted in DoRunAway(),
+ // so when it dies while running away, we will not count it again
+ assert (deadStarShip->playerNr >= 0);
+ battle_counter[deadStarShip->playerNr]--;
+ }
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ MeleeShipDeath (deadStarShip);
+}
+
+void
+StartShipExplosion (ELEMENT *ShipPtr, bool playSound)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ ZeroVelocityComponents (&ShipPtr->velocity);
+
+ DeltaEnergy (ShipPtr,
+ -(SIZE)StarShipPtr->RaceDescPtr->ship_info.energy_level);
+
+ ShipPtr->life_span = NUM_EXPLOSION_FRAMES * 3;
+ ShipPtr->state_flags &= ~DISAPPEARING;
+ ShipPtr->state_flags |= FINITE_LIFE | NONSOLID;
+ ShipPtr->preprocess_func = explosion_preprocess;
+ ShipPtr->postprocess_func = PostProcessStatus;
+ ShipPtr->death_func = cleanup_dead_ship;
+ ShipPtr->hTarget = 0;
+
+ if (playSound)
+ {
+ PlaySound (SetAbsSoundIndex (GameSounds, SHIP_EXPLODES),
+ CalcSoundPosition (ShipPtr), ShipPtr, GAME_SOUND_PRIORITY + 1);
+ }
+}
+
+void
+ship_death (ELEMENT *ShipPtr)
+{
+ STARSHIP *StarShipPtr;
+ STARSHIP *winner;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ StopAllBattleMusic ();
+
+ // If the winning ship dies before the ditty starts, do not play it.
+ // e.g. a ship can die after the opponent begins exploding but
+ // before the explosion is over.
+ StarShipPtr->cur_status_flags &= ~PLAY_VICTORY_DITTY;
+
+ StartShipExplosion (ShipPtr, true);
+
+ winner = FindAliveStarShip (ShipPtr);
+ SetWinnerStarShip (winner);
+ RecordShipDeath (ShipPtr);
+}
+
+#define START_ION_COLOR BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7A)
+
+// Called from the death_func of an element for an ion trail pixel, or a
+// ship shadow (when warping in/out).
+static void
+cycle_ion_trail (ELEMENT *ElementPtr)
+{
+ static const Color colorTab[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7a),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7c),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7d),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7e),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7f),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x2a),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2c),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2d),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2e),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0B, 0x00, 0x00), 0x2f),
+ };
+ const size_t colorTabCount = sizeof colorTab / sizeof colorTab[0];
+
+ assert (!(ElementPtr->state_flags & PLAYER_SHIP));
+
+ ElementPtr->colorCycleIndex++;
+ if (ElementPtr->colorCycleIndex != colorTabCount)
+ {
+ ElementPtr->life_span = ElementPtr->thrust_wait;
+ // Reset the life span.
+
+ SetPrimColor (&DisplayArray[ElementPtr->PrimIndex],
+ colorTab[ElementPtr->colorCycleIndex]);
+
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->state_flags |= CHANGING;
+ } // else, the element disappears.
+}
+
+void
+spawn_ion_trail (ELEMENT *ElementPtr)
+{
+ HELEMENT hIonElement;
+
+ assert (ElementPtr->state_flags & PLAYER_SHIP);
+
+ hIonElement = AllocElement ();
+ if (hIonElement)
+ {
+#define ION_LIFE 1
+ COUNT angle;
+ RECT r;
+ ELEMENT *IonElementPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing) + HALF_CIRCLE;
+ GetFrameRect (StarShipPtr->RaceDescPtr->ship_data.ship[0], &r);
+ r.extent.height = DISPLAY_TO_WORLD (r.extent.height + r.corner.y);
+
+ InsertElement (hIonElement, GetHeadElement ());
+ LockElement (hIonElement, &IonElementPtr);
+ IonElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ IonElementPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ IonElementPtr->thrust_wait = ION_LIFE;
+ IonElementPtr->life_span = IonElementPtr->thrust_wait;
+ // When the element "dies", in the death_func
+ // 'cycle_ion_trail', it is given new life a number of
+ // times, by setting life_span to thrust_wait.
+ SetPrimType (&DisplayArray[IonElementPtr->PrimIndex], POINT_PRIM);
+ SetPrimColor (&DisplayArray[IonElementPtr->PrimIndex],
+ START_ION_COLOR);
+ IonElementPtr->colorCycleIndex = 0;
+ IonElementPtr->current.image.frame =
+ DecFrameIndex (stars_in_space);
+ IonElementPtr->current.image.farray = &stars_in_space;
+ IonElementPtr->current.location = ElementPtr->current.location;
+ IonElementPtr->current.location.x +=
+ (COORD)COSINE (angle, r.extent.height);
+ IonElementPtr->current.location.y +=
+ (COORD)SINE (angle, r.extent.height);
+ IonElementPtr->death_func = cycle_ion_trail;
+
+ SetElementStarShip (IonElementPtr, StarShipPtr);
+
+ {
+ /* normally done during preprocess, but because
+ * object is being inserted at head rather than
+ * appended after tail it may never get preprocessed.
+ */
+ IonElementPtr->next = IonElementPtr->current;
+ --IonElementPtr->life_span;
+ IonElementPtr->state_flags |= PRE_PROCESS;
+ }
+
+ UnlockElement (hIonElement);
+ }
+}
+
+// Preprocess function for spawning a ship into or out of battle.
+// Used when a new ship warps in, or a ship escapes by warping out, but not
+// when a Pkunk ship is reborn.
+void
+ship_transition (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ {
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ ElementPtr->life_span = HYPERJUMP_LIFE;
+ ElementPtr->preprocess_func = ship_transition;
+ ElementPtr->postprocess_func = NULL;
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ ElementPtr->state_flags |= NONSOLID | FINITE_LIFE | CHANGING;
+ }
+ else if (ElementPtr->life_span < HYPERJUMP_LIFE)
+ {
+ if (ElementPtr->life_span == NORMAL_LIFE
+ && ElementPtr->crew_level)
+ {
+ ElementPtr->current.image.frame =
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (
+ ElementPtr->current.image.farray[0],
+ ElementPtr->current.image.frame);
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMP_PRIM);
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectFrame (ElementPtr);
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ ElementPtr->state_flags &= ~(NONSOLID | FINITE_LIFE);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->preprocess_func = ship_preprocess;
+ ElementPtr->postprocess_func = ship_postprocess;
+ }
+
+ return;
+ }
+ }
+
+ {
+ HELEMENT hShipImage;
+ ELEMENT *ShipImagePtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LockElement (StarShipPtr->hShip, &ShipImagePtr);
+
+ if (!(ShipImagePtr->state_flags & NONSOLID))
+ {
+ ElementPtr->preprocess_func = NULL;
+ }
+ else if ((hShipImage = AllocElement ()))
+ {
+#define TRANSITION_SPEED DISPLAY_TO_WORLD (40)
+#define TRANSITION_LIFE 1
+ COUNT angle;
+
+ PutElement (hShipImage);
+
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+
+ LockElement (hShipImage, &ShipImagePtr);
+ ShipImagePtr->playerNr = NEUTRAL_PLAYER_NUM;
+ ShipImagePtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ ShipImagePtr->thrust_wait = TRANSITION_LIFE;
+ ShipImagePtr->life_span = ShipImagePtr->thrust_wait;
+ // When the element "dies", in the death_func
+ // 'cycle_ion_trail', it is given new life a number of
+ // times, by setting life_span to thrust_wait.
+ SetPrimType (&DisplayArray[ShipImagePtr->PrimIndex],
+ STAMPFILL_PRIM);
+ SetPrimColor (&DisplayArray[ShipImagePtr->PrimIndex],
+ START_ION_COLOR);
+ ShipImagePtr->colorCycleIndex = 0;
+ ShipImagePtr->current.image = ElementPtr->current.image;
+ ShipImagePtr->current.location = ElementPtr->current.location;
+ if (!(ElementPtr->state_flags & PLAYER_SHIP))
+ {
+ ShipImagePtr->current.location.x +=
+ COSINE (angle, TRANSITION_SPEED);
+ ShipImagePtr->current.location.y +=
+ SINE (angle, TRANSITION_SPEED);
+ ElementPtr->preprocess_func = NULL;
+ }
+ else if (ElementPtr->crew_level)
+ {
+ ShipImagePtr->current.location.x -=
+ COSINE (angle, TRANSITION_SPEED)
+ * (ElementPtr->life_span - 1);
+ ShipImagePtr->current.location.y -=
+ SINE (angle, TRANSITION_SPEED)
+ * (ElementPtr->life_span - 1);
+
+ ShipImagePtr->current.location.x =
+ WRAP_X (ShipImagePtr->current.location.x);
+ ShipImagePtr->current.location.y =
+ WRAP_Y (ShipImagePtr->current.location.y);
+ }
+ ShipImagePtr->preprocess_func = ship_transition;
+ ShipImagePtr->death_func = cycle_ion_trail;
+ SetElementStarShip (ShipImagePtr, StarShipPtr);
+
+ UnlockElement (hShipImage);
+ }
+
+ UnlockElement (StarShipPtr->hShip);
+ }
+}
+
+void
+flee_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ if (--ElementPtr->turn_wait == 0)
+ {
+ static const Color colorTab[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0A, 0x00, 0x00), 0x2E),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0E, 0x00, 0x00), 0x2D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2C),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x29),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x04, 0x04), 0x28),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x0A), 0x27),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0F, 0x0F), 0x26),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x13, 0x13), 0x25),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x19, 0x19), 0x24),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x13, 0x13), 0x25),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0F, 0x0F), 0x26),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x0A), 0x27),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x04, 0x04), 0x28),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x29),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2C),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0E, 0x00, 0x00), 0x2D),
+ };
+ const size_t colorTabCount = sizeof colorTab / sizeof colorTab[0];
+
+ ElementPtr->colorCycleIndex++;
+ if (ElementPtr->colorCycleIndex == colorTabCount)
+ ElementPtr->colorCycleIndex = 0;
+
+ SetPrimColor (&DisplayArray[ElementPtr->PrimIndex],
+ colorTab[ElementPtr->colorCycleIndex]);
+
+ if (ElementPtr->colorCycleIndex == 0)
+ --ElementPtr->thrust_wait;
+
+ ElementPtr->turn_wait = ElementPtr->thrust_wait;
+ if (ElementPtr->turn_wait)
+ {
+ ElementPtr->turn_wait = ((ElementPtr->turn_wait - 1) >> 1) + 1;
+ }
+ else if (ElementPtr->colorCycleIndex != (colorTabCount / 2))
+ {
+ ElementPtr->turn_wait = 1;
+ }
+ else
+ {
+ ElementPtr->death_func = cleanup_dead_ship;
+ ElementPtr->crew_level = 0;
+
+ ElementPtr->life_span = HYPERJUMP_LIFE + 1;
+ ElementPtr->preprocess_func = ship_transition;
+ ElementPtr->postprocess_func = NULL;
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ ElementPtr->state_flags |= NONSOLID | FINITE_LIFE | CHANGING;
+ }
+ }
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(LEFT | RIGHT | THRUST | WEAPON | SPECIAL);
+ // Ignore control input when fleeing.
+ PreProcessStatus (ElementPtr);
+}
diff --git a/src/uqm/tactrans.h b/src/uqm/tactrans.h
new file mode 100644
index 0000000..c0f8479
--- /dev/null
+++ b/src/uqm/tactrans.h
@@ -0,0 +1,59 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_TACTRANS_H_
+#define UQM_TACTRANS_H_
+
+#include "libs/compiler.h"
+#include "races.h"
+#include "element.h"
+#include "battlecontrols.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+bool battleEndReadyHuman (HumanInputContext *context);
+bool battleEndReadyComputer (ComputerInputContext *context);
+#ifdef NETPLAY
+bool battleEndReadyNetwork (NetworkInputContext *context);
+#endif
+
+extern void ship_transition (ELEMENT *ElementPtr);
+extern BOOLEAN OpponentAlive (STARSHIP *TestStarShipPtr);
+extern void new_ship (ELEMENT *ElementPtr);
+extern void ship_death (ELEMENT *ShipPtr);
+extern void spawn_ion_trail (ELEMENT *ElementPtr);
+extern void flee_preprocess (ELEMENT *ElementPtr);
+
+extern void StopDitty (void);
+extern void ResetWinnerStarShip (void);
+extern void StopAllBattleMusic (void);
+extern STARSHIP* FindAliveStarShip (ELEMENT *deadShip);
+extern STARSHIP* GetWinnerStarShip (void);
+extern void SetWinnerStarShip (STARSHIP *winner);
+extern void RecordShipDeath (ELEMENT *deadShip);
+extern void StartShipExplosion (ELEMENT *ShipPtr, bool playSound);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_TACTRANS_H_ */
+
+
diff --git a/src/uqm/trans.c b/src/uqm/trans.c
new file mode 100644
index 0000000..a6eb6d5
--- /dev/null
+++ b/src/uqm/trans.c
@@ -0,0 +1,154 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "units.h"
+#include "libs/compiler.h"
+
+
+SIZE sinetab[] =
+{
+ -FLT_ADJUST (1.000000),
+ -FLT_ADJUST (0.995185),
+ -FLT_ADJUST (0.980785),
+ -FLT_ADJUST (0.956940),
+ -FLT_ADJUST (0.923880),
+ -FLT_ADJUST (0.881921),
+ -FLT_ADJUST (0.831470),
+ -FLT_ADJUST (0.773010),
+ -FLT_ADJUST (0.707107),
+ -FLT_ADJUST (0.634393),
+ -FLT_ADJUST (0.555570),
+ -FLT_ADJUST (0.471397),
+ -FLT_ADJUST (0.382683),
+ -FLT_ADJUST (0.290285),
+ -FLT_ADJUST (0.195090),
+ -FLT_ADJUST (0.098017),
+ FLT_ADJUST (0.000000),
+ FLT_ADJUST (0.098017),
+ FLT_ADJUST (0.195090),
+ FLT_ADJUST (0.290285),
+ FLT_ADJUST (0.382683),
+ FLT_ADJUST (0.471397),
+ FLT_ADJUST (0.555570),
+ FLT_ADJUST (0.634393),
+ FLT_ADJUST (0.707107),
+ FLT_ADJUST (0.773010),
+ FLT_ADJUST (0.831470),
+ FLT_ADJUST (0.881921),
+ FLT_ADJUST (0.923880),
+ FLT_ADJUST (0.956940),
+ FLT_ADJUST (0.980785),
+ FLT_ADJUST (0.995185),
+ FLT_ADJUST (1.000000),
+ FLT_ADJUST (0.995185),
+ FLT_ADJUST (0.980785),
+ FLT_ADJUST (0.956940),
+ FLT_ADJUST (0.923880),
+ FLT_ADJUST (0.881921),
+ FLT_ADJUST (0.831470),
+ FLT_ADJUST (0.773010),
+ FLT_ADJUST (0.707107),
+ FLT_ADJUST (0.634393),
+ FLT_ADJUST (0.555570),
+ FLT_ADJUST (0.471397),
+ FLT_ADJUST (0.382683),
+ FLT_ADJUST (0.290285),
+ FLT_ADJUST (0.195090),
+ FLT_ADJUST (0.098017),
+ FLT_ADJUST (0.000000),
+ -FLT_ADJUST (0.098017),
+ -FLT_ADJUST (0.195090),
+ -FLT_ADJUST (0.290285),
+ -FLT_ADJUST (0.382683),
+ -FLT_ADJUST (0.471397),
+ -FLT_ADJUST (0.555570),
+ -FLT_ADJUST (0.634393),
+ -FLT_ADJUST (0.707107),
+ -FLT_ADJUST (0.773010),
+ -FLT_ADJUST (0.831470),
+ -FLT_ADJUST (0.881921),
+ -FLT_ADJUST (0.923880),
+ -FLT_ADJUST (0.956940),
+ -FLT_ADJUST (0.980785),
+ -FLT_ADJUST (0.995185),
+};
+
+COUNT
+ARCTAN (SIZE delta_x, SIZE delta_y)
+{
+ SIZE v1, v2;
+ static COUNT atantab[] =
+ {
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 2,
+ 2,
+ 2,
+ 2,
+ 3,
+ 3,
+ 3,
+ 4,
+ 4,
+ 4,
+ 4,
+ 5,
+ 5,
+ 5,
+ 5,
+ 6,
+ 6,
+ 6,
+ 6,
+ 7,
+ 7,
+ 7,
+ 7,
+ 7,
+ 7,
+ 8,
+ 8,
+ 8,
+ };
+
+ v1 = delta_x;
+ v2 = delta_y;
+ if (v1 == 0 && v2 == 0)
+ return (FULL_CIRCLE);
+
+ if (v1 < 0)
+ v1 = -v1;
+ if (v2 < 0)
+ v2 = -v2;
+ if (v1 > v2)
+ v1 = QUADRANT
+ - atantab[(((DWORD)v2 << (CIRCLE_SHIFT - 1)) + (v1 >> 1)) / v1];
+ else
+ v1 = atantab[(((DWORD)v1 << (CIRCLE_SHIFT - 1)) + (v2 >> 1)) / v2];
+
+ if (delta_x < 0)
+ v1 = FULL_CIRCLE - v1;
+ if (delta_y > 0)
+ v1 = HALF_CIRCLE - v1;
+
+ return (NORMALIZE_ANGLE (v1));
+}
+
diff --git a/src/uqm/units.h b/src/uqm/units.h
new file mode 100644
index 0000000..93d903c
--- /dev/null
+++ b/src/uqm/units.h
@@ -0,0 +1,227 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_UNITS_H_
+#define UQM_UNITS_H_
+
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern int ScreenWidth;
+extern int ScreenHeight;
+
+#define SCREEN_WIDTH ScreenWidth
+#define SCREEN_HEIGHT ScreenHeight
+#define SAFE_X 0
+ /* Left and right screen margin to be left unused */
+#define SAFE_Y 0
+ /* Top and bottom screen margin to be left unused */
+#define SIS_ORG_X (7 + SAFE_X)
+#define SIS_ORG_Y (10 + SAFE_Y)
+#define STATUS_WIDTH 64
+ /* Width of the status "window" (the right part of the screen) */
+#define STATUS_HEIGHT (SCREEN_HEIGHT - (SAFE_Y * 2))
+ /* Height of the status "window" (the right part of the screen) */
+#define SPACE_WIDTH (SCREEN_WIDTH - STATUS_WIDTH - (SAFE_X * 2))
+ /* Width of the space "window" (the left part of the screen) */
+#define SPACE_HEIGHT (SCREEN_HEIGHT - (SAFE_Y * 2))
+ /* Height of the space "window" (the left part of the screen) */
+#define SIS_SCREEN_WIDTH (SPACE_WIDTH - 14)
+ /* Width of the usable part of the space "window" */
+#define SIS_SCREEN_HEIGHT (SPACE_HEIGHT - 13)
+ /* Height of the usable part of the space "window" */
+#define RADAR_X (4 + (SCREEN_WIDTH - STATUS_WIDTH - SAFE_X))
+#define RADAR_WIDTH (STATUS_WIDTH - 8)
+#define RADAR_HEIGHT 53
+#define RADAR_Y (SIS_ORG_Y + SIS_SCREEN_HEIGHT - RADAR_HEIGHT)
+
+#define SIS_TITLE_BOX_WIDTH 57
+#define SIS_TITLE_WIDTH (SIS_TITLE_BOX_WIDTH - 2)
+#define SIS_TITLE_HEIGHT 8
+#define SIS_SPACER_BOX_WIDTH 12
+#define SIS_MESSAGE_BOX_WIDTH (SIS_SCREEN_WIDTH - SIS_TITLE_BOX_WIDTH \
+ - SIS_SPACER_BOX_WIDTH)
+#define SIS_MESSAGE_WIDTH (SIS_MESSAGE_BOX_WIDTH - 2)
+#define SIS_MESSAGE_HEIGHT SIS_TITLE_HEIGHT
+
+#define STATUS_MESSAGE_WIDTH (STATUS_WIDTH - 4)
+#define STATUS_MESSAGE_HEIGHT 7
+
+#define SHIP_NAME_WIDTH (STATUS_WIDTH - 4)
+#define SHIP_NAME_HEIGHT 7
+
+#define MAX_REDUCTION 3
+#define MAX_VIS_REDUCTION 2
+#define REDUCTION_SHIFT 1
+#define NUM_VIEWS (MAX_VIS_REDUCTION + 1)
+
+#define ZOOM_SHIFT 8
+#define MAX_ZOOM_OUT (1 << (ZOOM_SHIFT + MAX_REDUCTION - 1))
+
+#define ONE_SHIFT 2
+#define BACKGROUND_SHIFT 3
+#define SCALED_ONE (1 << ONE_SHIFT)
+#define DISPLAY_TO_WORLD(x) ((x)<<ONE_SHIFT)
+#define WORLD_TO_DISPLAY(x) ((x)>>ONE_SHIFT)
+#define DISPLAY_ALIGN(x) ((COORD)(x)&~(SCALED_ONE-1))
+#define DISPLAY_ALIGN_X(x) ((COORD)((COUNT)(x)%LOG_SPACE_WIDTH)&~(SCALED_ONE-1))
+#define DISPLAY_ALIGN_Y(y) ((COORD)((COUNT)(y)%LOG_SPACE_HEIGHT)&~(SCALED_ONE-1))
+
+#define LOG_SPACE_WIDTH \
+ (DISPLAY_TO_WORLD (SPACE_WIDTH) << MAX_REDUCTION)
+#define LOG_SPACE_HEIGHT \
+ (DISPLAY_TO_WORLD (SPACE_HEIGHT) << MAX_REDUCTION)
+#define TRANSITION_WIDTH \
+ (DISPLAY_TO_WORLD (SPACE_WIDTH) << MAX_VIS_REDUCTION)
+#define TRANSITION_HEIGHT \
+ (DISPLAY_TO_WORLD (SPACE_HEIGHT) << MAX_VIS_REDUCTION)
+
+#define MAX_X_UNIVERSE 9999
+#define MAX_Y_UNIVERSE 9999
+// Due to the added rounding error correction, the maximum logical X and Y
+// in Hyperspace cannot go past 999.94999, otherwise the values will be
+// rounded up to 1000.0. We do not want that so we subtract half a unit.
+#define MAX_X_LOGICAL \
+ (UNIVERSE_TO_LOGX (MAX_X_UNIVERSE + 1) - (UNIVERSE_TO_LOGX (1) >> 1) \
+ - 1L)
+// The Y axis is inverted with respect to the screen Y axis.
+// (MAX_Y_UNIVERSE - 1) is really 1 for our purposes.
+#define MAX_Y_LOGICAL \
+ (UNIVERSE_TO_LOGY (-1) - (UNIVERSE_TO_LOGY (MAX_Y_UNIVERSE - 1) >> 1) \
+ - 1L)
+
+#define SPHERE_RADIUS_INCREMENT 11
+
+#define MAX_FLEET_STRENGTH (254 * SPHERE_RADIUS_INCREMENT)
+
+// XXX: These corrected for the weird screen aspect ratio on DOS
+// In part because of them, hyperflight is slower vertically
+#define UNIT_SCREEN_WIDTH 63
+#define UNIT_SCREEN_HEIGHT 50
+
+// Bug #945: Simplified, these set the speed of SIS in Hyperspace and
+// Quasispace. The ratio between UNIVERSE_UNITS_ and LOG_UNITS_ is
+// what sets the speed, and it should be 1:16 to match the original.
+// The unit factors are reduced to keep the translation math within
+// 32 bits. The original math is unnecessarily complex and depends
+// on the screen resolution when it should not.
+// Using the new math will break old savegames.
+#ifdef NORMALIZED_HYPERSPACE_SPEED
+#define LOG_UNITS_X ((SDWORD)(UNIVERSE_UNITS_X * 16))
+#define LOG_UNITS_Y ((SDWORD)(UNIVERSE_UNITS_Y * 16))
+#define UNIVERSE_UNITS_X (((MAX_X_UNIVERSE + 1) >> 4))
+#define UNIVERSE_UNITS_Y (((MAX_Y_UNIVERSE + 1) >> 4))
+#else
+// Original (and now broken) Hyperspace speed factors
+#define SECTOR_WIDTH 195
+#define SECTOR_HEIGHT 25
+
+#define LOG_UNITS_X ((SDWORD)(LOG_SPACE_WIDTH >> 4) * SECTOR_WIDTH)
+#define LOG_UNITS_Y ((SDWORD)(LOG_SPACE_HEIGHT >> 4) * SECTOR_HEIGHT)
+#define UNIVERSE_UNITS_X (((MAX_X_UNIVERSE + 1) >> 4) * 10)
+#define UNIVERSE_UNITS_Y (((MAX_Y_UNIVERSE + 1) >> 4))
+#endif
+
+#define ROUNDING_ERROR(div) ((div) >> 1)
+
+static inline COORD
+logxToUniverse (SDWORD lx)
+{
+ return (COORD) ((lx * UNIVERSE_UNITS_X + ROUNDING_ERROR(LOG_UNITS_X))
+ / LOG_UNITS_X);
+}
+#define LOGX_TO_UNIVERSE(lx) \
+ logxToUniverse (lx)
+static inline COORD
+logyToUniverse (SDWORD ly)
+{
+ return (COORD) (MAX_Y_UNIVERSE -
+ ((ly * UNIVERSE_UNITS_Y + ROUNDING_ERROR(LOG_UNITS_Y))
+ / LOG_UNITS_Y));
+}
+#define LOGY_TO_UNIVERSE(ly) \
+ logyToUniverse (ly)
+static inline SDWORD
+universeToLogx (COORD ux)
+{
+ return (ux * LOG_UNITS_X + ROUNDING_ERROR(UNIVERSE_UNITS_X))
+ / UNIVERSE_UNITS_X;
+}
+#define UNIVERSE_TO_LOGX(ux) \
+ universeToLogx (ux)
+static inline SDWORD
+universeToLogy (COORD uy)
+{
+ return ((MAX_Y_UNIVERSE - uy) * LOG_UNITS_Y
+ + ROUNDING_ERROR(UNIVERSE_UNITS_Y))
+ / UNIVERSE_UNITS_Y;
+}
+#define UNIVERSE_TO_LOGY(uy) \
+ universeToLogy (uy)
+
+#define CIRCLE_SHIFT 6
+#define FULL_CIRCLE (1 << CIRCLE_SHIFT)
+#define OCTANT_SHIFT (CIRCLE_SHIFT - 3) /* (1 << 3) == 8 */
+#define HALF_CIRCLE (FULL_CIRCLE >> 1)
+#define QUADRANT (FULL_CIRCLE >> 2)
+#define OCTANT (FULL_CIRCLE >> 3)
+
+#define FACING_SHIFT 4
+
+#define ANGLE_TO_FACING(a) (((a)+(1<<(CIRCLE_SHIFT-FACING_SHIFT-1))) \
+ >>(CIRCLE_SHIFT-FACING_SHIFT))
+#define FACING_TO_ANGLE(f) ((f)<<(CIRCLE_SHIFT-FACING_SHIFT))
+
+#define NORMALIZE_ANGLE(a) ((COUNT)((a)&(FULL_CIRCLE-1)))
+#define NORMALIZE_FACING(f) ((COUNT)((f)&((1 << FACING_SHIFT)-1)))
+
+#define DEGREES_TO_ANGLE(d) NORMALIZE_ANGLE((((d) % 360) * FULL_CIRCLE \
+ + HALF_CIRCLE) / 360)
+#define ANGLE_TO_DEGREES(d) (NORMALIZE_ANGLE(d) * 360 / FULL_CIRCLE)
+
+#define SIN_SHIFT 14
+#define SIN_SCALE (1 << SIN_SHIFT)
+#define INT_ADJUST(x) ((x)<<SIN_SHIFT)
+#define FLT_ADJUST(x) (SIZE)((x)*SIN_SCALE)
+#define UNADJUST(x) (SIZE)((x)>>SIN_SHIFT)
+#define ROUND(x,y) ((x)+((x)>=0?((y)>>1):-((y)>>1)))
+
+extern SIZE sinetab[];
+#define SINVAL(a) sinetab[NORMALIZE_ANGLE(a)]
+#define COSVAL(a) SINVAL((a)+QUADRANT)
+#define SINE(a,m) ((SIZE)((((long)SINVAL(a))*(long)(m))>>SIN_SHIFT))
+#define COSINE(a,m) SINE((a)+QUADRANT,m)
+extern COUNT ARCTAN (SIZE delta_x, SIZE delta_y);
+
+#define WRAP_VAL(v,w) ((COUNT)((v)<0?((v)+(w)):((v)>=(w)?((v)-(w)):(v))))
+#define WRAP_X(x) WRAP_VAL(x,LOG_SPACE_WIDTH)
+#define WRAP_Y(y) WRAP_VAL(y,LOG_SPACE_HEIGHT)
+#define WRAP_DELTA_X(dx) ((dx)<0 ? \
+ ((-(dx)<=LOG_SPACE_WIDTH>>1)?(dx):(LOG_SPACE_WIDTH+(dx))) : \
+ (((dx)<=LOG_SPACE_WIDTH>>1)?(dx):((dx)-LOG_SPACE_WIDTH)))
+#define WRAP_DELTA_Y(dy) ((dy)<0 ? \
+ ((-(dy)<=LOG_SPACE_HEIGHT>>1)?(dy):(LOG_SPACE_HEIGHT+(dy))) : \
+ (((dy)<=LOG_SPACE_HEIGHT>>1)?(dy):((dy)-LOG_SPACE_HEIGHT)))
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_UNITS_H_ */
diff --git a/src/uqm/uqmdebug.c b/src/uqm/uqmdebug.c
new file mode 100644
index 0000000..4113a5d
--- /dev/null
+++ b/src/uqm/uqmdebug.c
@@ -0,0 +1,1926 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#if defined(DEBUG) || defined(USE_DEBUG_KEY)
+
+#include "uqmdebug.h"
+
+#include "build.h"
+#include "colors.h"
+#include "controls.h"
+#include "clock.h"
+#include "starmap.h"
+#include "element.h"
+#include "sis.h"
+#include "status.h"
+#include "gamestr.h"
+#include "gameev.h"
+#include "gendef.h"
+#include "globdata.h"
+#include "planets/lifeform.h"
+#include "planets/scan.h"
+#include "races.h"
+#include "setup.h"
+#include "state.h"
+#include "libs/mathlib.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+
+static void dumpEventCallback (const EVENT *eventPtr, void *arg);
+
+static void starRecurse (STAR_DESC *star, void *arg);
+static void planetRecurse (STAR_DESC *star, SOLARSYS_STATE *system,
+ PLANET_DESC *planet, void *arg);
+static void moonRecurse (STAR_DESC *star, SOLARSYS_STATE *system,
+ PLANET_DESC *planet, PLANET_DESC *moon, void *arg);
+
+static void dumpSystemCallback (const STAR_DESC *star,
+ const SOLARSYS_STATE *system, void *arg);
+static void dumpPlanetCallback (const PLANET_DESC *planet, void *arg);
+static void dumpMoonCallback (const PLANET_DESC *moon, void *arg);
+static void dumpWorld (FILE *out, const PLANET_DESC *world);
+
+typedef struct TallyResourcesArg TallyResourcesArg;
+static void tallySystemPreCallback (const STAR_DESC *star, const
+ SOLARSYS_STATE *system, void *arg);
+static void tallySystemPostCallback (const STAR_DESC *star, const
+ SOLARSYS_STATE *system, void *arg);
+static void tallyPlanetCallback (const PLANET_DESC *planet, void *arg);
+static void tallyMoonCallback (const PLANET_DESC *moon, void *arg);
+static void tallyResourcesWorld (TallyResourcesArg *arg,
+ const PLANET_DESC *world);
+
+static void dumpPlanetTypeCallback (int index, const PlanetFrame *planet,
+ void *arg);
+
+
+BOOLEAN instantMove = FALSE;
+BOOLEAN disableInteractivity = FALSE;
+void (* volatile debugHook) (void) = NULL;
+
+
+// Must be called on the Starcon2Main thread.
+// This function is called synchronously wrt the game logic thread.
+void
+debugKeyPressedSynchronous (void)
+{
+ // State modifying:
+ equipShip ();
+ giveDevices ();
+
+ // Give the player the ships you can't ally with under normal
+ // conditions.
+ clearEscorts ();
+ AddEscortShips (ARILOU_SHIP, 1);
+ AddEscortShips (PKUNK_SHIP, 1);
+ AddEscortShips (VUX_SHIP, 1);
+ AddEscortShips (YEHAT_SHIP, 1);
+ AddEscortShips (MELNORME_SHIP, 1);
+ AddEscortShips (DRUUGE_SHIP, 1);
+ AddEscortShips (ILWRATH_SHIP, 1);
+ AddEscortShips (MYCON_SHIP, 1);
+ AddEscortShips (SLYLANDRO_SHIP, 1);
+ AddEscortShips (UMGAH_SHIP, 1);
+ AddEscortShips (URQUAN_SHIP, 1);
+ AddEscortShips (BLACK_URQUAN_SHIP, 1);
+
+ resetCrewBattle ();
+ resetEnergyBattle ();
+ instantMove = !instantMove;
+ showSpheres ();
+ activateAllShips ();
+// forwardToNextEvent (TRUE);
+// SET_GAME_STATE (MELNORME_CREDIT1, 100);
+// GLOBAL_SIS (ResUnits) = 100000;
+
+ // Informational:
+// dumpEvents (stderr);
+
+ // Graphical and textual:
+// debugContexts();
+}
+
+// Can be called on any thread, but usually on main()
+// This function is called asynchronously wrt the game logic thread,
+// which means locking applies. Use carefully.
+// TODO: Once game logic thread is purged of graphics and clock locks,
+// this function may not call graphics and game clock functions at all.
+void
+debugKeyPressed (void)
+{
+ // Tests
+// Scale_PerfTest ();
+
+ // Informational:
+// dumpStrings (stdout);
+// dumpPlanetTypes(stderr);
+// debugHook = dumpUniverseToFile;
+ // This will cause dumpUniverseToFile to be called from the
+ // Starcon2Main loop. Calling it from here would give threading
+ // problems.
+// debugHook = tallyResourcesToFile;
+ // This will cause tallyResourcesToFile to be called from the
+ // Starcon2Main loop. Calling it from here would give threading
+ // problems.
+
+ // Interactive:
+// uio_debugInteractive(stdin, stdout, stderr);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// Fast forwards to the next event.
+// If skipHEE is set, HYPERSPACE_ENCOUNTER_EVENTs are skipped.
+// Must be called from the Starcon2Main thread.
+// TODO: LockGameClock may be removed since it is only
+// supposed to be called synchronously wrt the game logic thread.
+void
+forwardToNextEvent (BOOLEAN skipHEE)
+{
+ HEVENT hEvent;
+ EVENT *EventPtr;
+ COUNT year, month, day;
+ // time of next event
+ BOOLEAN done;
+
+ if (!GameClockRunning ())
+ return;
+
+ LockGameClock ();
+
+ done = !skipHEE;
+ do {
+ hEvent = GetHeadEvent ();
+ if (hEvent == 0)
+ return;
+ LockEvent (hEvent, &EventPtr);
+ if (EventPtr->func_index != HYPERSPACE_ENCOUNTER_EVENT)
+ done = TRUE;
+ year = EventPtr->year_index;
+ month = EventPtr->month_index;
+ day = EventPtr->day_index;
+ UnlockEvent (hEvent);
+
+ for (;;) {
+ if (GLOBAL (GameClock.year_index) > year ||
+ (GLOBAL (GameClock.year_index) == year &&
+ (GLOBAL (GameClock.month_index) > month ||
+ (GLOBAL (GameClock.month_index) == month &&
+ GLOBAL (GameClock.day_index) >= day))))
+ break;
+
+ MoveGameClockDays (1);
+ }
+ } while (!done);
+
+ UnlockGameClock ();
+}
+
+const char *
+eventName (BYTE func_index)
+{
+ switch (func_index) {
+ case ARILOU_ENTRANCE_EVENT:
+ return "ARILOU_ENTRANCE_EVENT";
+ case ARILOU_EXIT_EVENT:
+ return "ARILOU_EXIT_EVENT";
+ case HYPERSPACE_ENCOUNTER_EVENT:
+ return "HYPERSPACE_ENCOUNTER_EVENT";
+ case KOHR_AH_VICTORIOUS_EVENT:
+ return "KOHR_AH_VICTORIOUS_EVENT";
+ case ADVANCE_PKUNK_MISSION:
+ return "ADVANCE_PKUNK_MISSION";
+ case ADVANCE_THRADD_MISSION:
+ return "ADVANCE_THRADD_MISSION";
+ case ZOQFOT_DISTRESS_EVENT:
+ return "ZOQFOT_DISTRESS";
+ case ZOQFOT_DEATH_EVENT:
+ return "ZOQFOT_DEATH_EVENT";
+ case SHOFIXTI_RETURN_EVENT:
+ return "SHOFIXTI_RETURN_EVENT";
+ case ADVANCE_UTWIG_SUPOX_MISSION:
+ return "ADVANCE_UTWIG_SUPOX_MISSION";
+ case KOHR_AH_GENOCIDE_EVENT:
+ return "KOHR_AH_GENOCIDE_EVENT";
+ case SPATHI_SHIELD_EVENT:
+ return "SPATHI_SHIELD_EVENT";
+ case ADVANCE_ILWRATH_MISSION:
+ return "ADVANCE_ILWRATH_MISSION";
+ case ADVANCE_MYCON_MISSION:
+ return "ADVANCE_MYCON_MISSION";
+ case ARILOU_UMGAH_CHECK:
+ return "ARILOU_UMGAH_CHECK";
+ case YEHAT_REBEL_EVENT:
+ return "YEHAT_REBEL_EVENT";
+ case SLYLANDRO_RAMP_UP:
+ return "SLYLANDRO_RAMP_UP";
+ case SLYLANDRO_RAMP_DOWN:
+ return "SLYLANDRO_RAMP_DOWN";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+static void
+dumpEventCallback (const EVENT *eventPtr, void *arg)
+{
+ FILE *out = (FILE *) arg;
+ dumpEvent (out, eventPtr);
+}
+
+void
+dumpEvent (FILE *out, const EVENT *eventPtr)
+{
+ fprintf (out, "%4u/%02u/%02u: %s\n",
+ eventPtr->year_index,
+ eventPtr->month_index,
+ eventPtr->day_index,
+ eventName (eventPtr->func_index));
+}
+
+void
+dumpEvents (FILE *out)
+{
+ LockGameClock ();
+ ForAllEvents (dumpEventCallback, out);
+ UnlockGameClock ();
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// NB: Ship maximum speed and turning rate aren't updated in
+// HyperSpace/QuasiSpace or in melee.
+void
+equipShip (void)
+{
+ int i;
+
+ // Don't do anything unless in the full game.
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ return;
+
+ // Thrusters:
+ for (i = 0; i < NUM_DRIVE_SLOTS; i++)
+ GLOBAL_SIS (DriveSlots[i]) = FUSION_THRUSTER;
+
+ // Turning jets:
+ for (i = 0; i < NUM_JET_SLOTS; i++)
+ GLOBAL_SIS (JetSlots[i]) = TURNING_JETS;
+
+ // Shields:
+ SET_GAME_STATE (LANDER_SHIELDS,
+ (1 << EARTHQUAKE_DISASTER) |
+ (1 << BIOLOGICAL_DISASTER) |
+ (1 << LIGHTNING_DISASTER) |
+ (1 << LAVASPOT_DISASTER));
+ // Lander upgrades:
+ SET_GAME_STATE (IMPROVED_LANDER_SPEED, 1);
+ SET_GAME_STATE (IMPROVED_LANDER_CARGO, 1);
+ SET_GAME_STATE (IMPROVED_LANDER_SHOT, 1);
+
+ // Modules:
+ if (GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
+ {
+ // The Precursor bomb has not been installed.
+ // This is the original TFB testing layout.
+ i = 0;
+ GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
+ GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = STORAGE_BAY;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = DYNAMO_UNIT;
+ GLOBAL_SIS (ModuleSlots[i++]) = TRACKING_SYSTEM;
+ GLOBAL_SIS (ModuleSlots[i++]) = TRACKING_SYSTEM;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
+ GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
+
+ // Landers:
+ GLOBAL_SIS (NumLanders) = MAX_LANDERS;
+ }
+ else
+ {
+ // The Precursor bomb has been installed.
+ i = NUM_BOMB_MODULES;
+ GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
+ GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
+ GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
+ }
+
+ assert (i <= NUM_MODULE_SLOTS);
+
+ // Fill the fuel and crew compartments to the maximum.
+ GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE;
+ GLOBAL_SIS (CrewEnlisted) = 0;
+ for (i = 0; i < NUM_MODULE_SLOTS; i++)
+ {
+ switch (GLOBAL_SIS (ModuleSlots[i])) {
+ case CREW_POD:
+ GLOBAL_SIS (CrewEnlisted) += CREW_POD_CAPACITY;
+ break;
+ case FUEL_TANK:
+ GLOBAL_SIS (FuelOnBoard) += FUEL_TANK_CAPACITY;
+ break;
+ case HIGHEFF_FUELSYS:
+ GLOBAL_SIS (FuelOnBoard) += HEFUEL_TANK_CAPACITY;
+ break;
+ }
+ }
+
+ // Update the maximum speed and turning rate when in interplanetary.
+ if (pSolarSysState != NULL)
+ {
+ // Thrusters:
+ pSolarSysState->max_ship_speed = 5 * IP_SHIP_THRUST_INCREMENT;
+ for (i = 0; i < NUM_DRIVE_SLOTS; i++)
+ if (GLOBAL_SIS (DriveSlots[i] == FUSION_THRUSTER))
+ pSolarSysState->max_ship_speed += IP_SHIP_THRUST_INCREMENT;
+
+ // Turning jets:
+ pSolarSysState->turn_wait = IP_SHIP_TURN_WAIT;
+ for (i = 0; i < NUM_JET_SLOTS; i++)
+ if (GLOBAL_SIS (JetSlots[i]) == TURNING_JETS)
+ pSolarSysState->turn_wait -= IP_SHIP_TURN_DECREMENT;
+ }
+
+ // Make sure everything is redrawn:
+ if (inHQSpace () ||
+ LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY)
+ {
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+giveDevices (void) {
+ SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 1);
+ SET_GAME_STATE (ARTIFACT_2_ON_SHIP, 1);
+ SET_GAME_STATE (ARTIFACT_3_ON_SHIP, 1);
+ SET_GAME_STATE (SUN_DEVICE_ON_SHIP, 1);
+ SET_GAME_STATE (UTWIG_BOMB_ON_SHIP, 1);
+ SET_GAME_STATE (ULTRON_CONDITION, 1);
+ //SET_GAME_STATE (ULTRON_CONDITION, 2);
+ //SET_GAME_STATE (ULTRON_CONDITION, 3);
+ //SET_GAME_STATE (ULTRON_CONDITION, 4);
+ SET_GAME_STATE (MAIDENS_ON_SHIP, 1);
+ SET_GAME_STATE (TALKING_PET_ON_SHIP, 1);
+ SET_GAME_STATE (AQUA_HELIX_ON_SHIP, 1);
+ SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 1);
+ SET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP, 1);
+ SET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP, 1);
+ SET_GAME_STATE (EGG_CASE0_ON_SHIP, 1);
+ SET_GAME_STATE (EGG_CASE1_ON_SHIP, 1);
+ SET_GAME_STATE (EGG_CASE2_ON_SHIP, 1);
+ SET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP, 1);
+ SET_GAME_STATE (VUX_BEAST_ON_SHIP, 1);
+ SET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP, 1);
+ SET_GAME_STATE (PORTAL_KEY_ON_SHIP, 1);
+ SET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP, 1);
+ SET_GAME_STATE (MOONBASE_ON_SHIP, 1);
+
+ // Not strictly a device (although it originally was one).
+ SET_GAME_STATE (DESTRUCT_CODE_ON_SHIP, 1);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+clearEscorts (void)
+{
+ HSHIPFRAG hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q));
+ hStarShip; hStarShip = hNextShip)
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+
+ RemoveQueue (&GLOBAL (built_ship_q), hStarShip);
+ FreeShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+
+ DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if 0
+// Not needed anymore, but could be useful in the future.
+
+// Find the HELEMENT belonging to the Flagship.
+static HELEMENT
+findFlagshipElement (void)
+{
+ HELEMENT hElement, hNextElement;
+ ELEMENT *ElementPtr;
+
+ // Find the ship element.
+ for (hElement = GetTailElement (); hElement != 0;
+ hElement = hNextElement)
+ {
+ LockElement (hElement, &ElementPtr);
+
+ if ((ElementPtr->state_flags & PLAYER_SHIP) != 0)
+ {
+ UnlockElement (hElement);
+ return hElement;
+ }
+
+ hNextElement = GetPredElement (ElementPtr);
+ UnlockElement (hElement);
+ }
+ return 0;
+}
+#endif
+
+// Move the Flagship to the destination of the autopilot.
+// Should only be called from HyperSpace/QuasiSpace.
+// It can be called from debugHook directly after entering HS/QS though.
+void
+doInstantMove (void)
+{
+ // Move to the new location:
+ if ((GLOBAL (autopilot)).x == ~0 || (GLOBAL (autopilot)).y == ~0)
+ {
+ // If no destination has been selected, use the current location
+ // as the destination.
+ (GLOBAL (autopilot)).x = LOGX_TO_UNIVERSE(GLOBAL_SIS (log_x));
+ (GLOBAL (autopilot)).y = LOGY_TO_UNIVERSE(GLOBAL_SIS (log_y));
+ }
+ else
+ {
+ // A new destination has been selected.
+ GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX((GLOBAL (autopilot)).x);
+ GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY((GLOBAL (autopilot)).y);
+ }
+
+ // Check for a solar systems at the destination.
+ if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
+ {
+ // If there's a solar system at the destination, enter it.
+ CurStarDescPtr = FindStar (0, &(GLOBAL (autopilot)), 0, 0);
+ if (CurStarDescPtr)
+ {
+ // Leave HyperSpace/QuasiSpace if we're there:
+ SET_GAME_STATE (USED_BROADCASTER, 0);
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+
+ // Enter IP:
+ GLOBAL (ShipFacing) = 0;
+ GLOBAL (ip_planet) = 0;
+ GLOBAL (in_orbit) = 0;
+ // This causes the ship position in IP to be reset.
+ GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
+ }
+ }
+
+ // Turn off the autopilot:
+ (GLOBAL (autopilot)).x = ~0;
+ (GLOBAL (autopilot)).y = ~0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+showSpheres (void)
+{
+ HFLEETINFO hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip != NULL; hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ if ((FleetPtr->actual_strength != INFINITE_RADIUS) &&
+ (FleetPtr->known_strength != FleetPtr->actual_strength))
+ {
+ FleetPtr->known_strength = FleetPtr->actual_strength;
+ FleetPtr->known_loc = FleetPtr->loc;
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+activateAllShips (void)
+{
+ HFLEETINFO hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
+ hStarShip != NULL; hStarShip = hNextShip)
+ {
+ FLEET_INFO *FleetPtr;
+
+ FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ hNextShip = _GetSuccLink (FleetPtr);
+
+ if (FleetPtr->icons != NULL)
+ // Skip the Ur-Quan probe.
+ {
+ FleetPtr->allied_state = GOOD_GUY;
+ }
+
+ UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+forAllStars (void (*callback) (STAR_DESC *, void *), void *arg)
+{
+ int i;
+ extern STAR_DESC starmap_array[];
+
+ for (i = 0; i < NUM_SOLAR_SYSTEMS; i++)
+ callback (&starmap_array[i], arg);
+}
+
+void
+forAllPlanets (STAR_DESC *star, SOLARSYS_STATE *system, void (*callback) (
+ STAR_DESC *, SOLARSYS_STATE *, PLANET_DESC *, void *), void *arg)
+{
+ COUNT i;
+
+ assert(CurStarDescPtr == star);
+ assert(pSolarSysState == system);
+
+ for (i = 0; i < system->SunDesc[0].NumPlanets; i++)
+ callback (star, system, &system->PlanetDesc[i], arg);
+}
+
+void
+forAllMoons (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
+ void (*callback) (STAR_DESC *, SOLARSYS_STATE *, PLANET_DESC *,
+ PLANET_DESC *, void *), void *arg)
+{
+ COUNT i;
+
+ assert(pSolarSysState == system);
+
+ for (i = 0; i < planet->NumPlanets; i++)
+ callback (star, system, planet, &system->MoonDesc[i], arg);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// Must be called from the Starcon2Main thread.
+// TODO: LockGameClock may be removed
+void
+UniverseRecurse (UniverseRecurseArg *universeRecurseArg)
+{
+ ACTIVITY savedActivity;
+
+ if (universeRecurseArg->systemFuncPre == NULL
+ && universeRecurseArg->systemFuncPost == NULL
+ && universeRecurseArg->planetFuncPre == NULL
+ && universeRecurseArg->planetFuncPost == NULL
+ && universeRecurseArg->moonFunc == NULL)
+ return;
+
+ LockGameClock ();
+ //TFB_DEBUG_HALT = 1;
+ savedActivity = GLOBAL (CurrentActivity);
+ disableInteractivity = TRUE;
+
+ forAllStars (starRecurse, (void *) universeRecurseArg);
+
+ disableInteractivity = FALSE;
+ GLOBAL (CurrentActivity) = savedActivity;
+ UnlockGameClock ();
+}
+
+static void
+starRecurse (STAR_DESC *star, void *arg)
+{
+ UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;
+
+ SOLARSYS_STATE SolarSysState;
+ SOLARSYS_STATE *oldPSolarSysState = pSolarSysState;
+ STAR_DESC *oldStarDescPtr = CurStarDescPtr;
+ CurStarDescPtr = star;
+
+ RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (star));
+
+ memset (&SolarSysState, 0, sizeof (SolarSysState));
+ SolarSysState.SunDesc[0].pPrevDesc = 0;
+ SolarSysState.SunDesc[0].rand_seed = RandomContext_Random (SysGenRNG);
+ SolarSysState.SunDesc[0].data_index = STAR_TYPE (star->Type);
+ SolarSysState.SunDesc[0].location.x = 0;
+ SolarSysState.SunDesc[0].location.y = 0;
+ //SolarSysState.SunDesc[0].radius = MIN_ZOOM_RADIUS;
+ SolarSysState.genFuncs = getGenerateFunctions (star->Index);
+
+ pSolarSysState = &SolarSysState;
+ (*SolarSysState.genFuncs->generatePlanets) (&SolarSysState);
+
+ if (universeRecurseArg->systemFuncPre != NULL)
+ {
+ (*universeRecurseArg->systemFuncPre) (
+ star, &SolarSysState, universeRecurseArg->arg);
+ }
+
+ if (universeRecurseArg->planetFuncPre != NULL
+ || universeRecurseArg->planetFuncPost != NULL
+ || universeRecurseArg->moonFunc != NULL)
+ {
+ forAllPlanets (star, &SolarSysState, planetRecurse,
+ (void *) universeRecurseArg);
+ }
+
+ if (universeRecurseArg->systemFuncPost != NULL)
+ {
+ (*universeRecurseArg->systemFuncPost) (
+ star, &SolarSysState, universeRecurseArg->arg);
+ }
+
+ pSolarSysState = oldPSolarSysState;
+ CurStarDescPtr = oldStarDescPtr;
+}
+
+static void
+planetRecurse (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
+ void *arg)
+{
+ UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;
+
+ assert(CurStarDescPtr == star);
+ assert(pSolarSysState == system);
+
+ planet->pPrevDesc = &system->SunDesc[0];
+
+ if (universeRecurseArg->planetFuncPre != NULL)
+ {
+ system->pOrbitalDesc = planet;
+ DoPlanetaryAnalysis (&system->SysInfo, planet);
+ // When GenerateDefaultFunctions is used as genFuncs,
+ // generateOrbital will also call DoPlanetaryAnalysis,
+ // but with other GenerateFunctions this is not guaranteed.
+ (*system->genFuncs->generateOrbital) (system, planet);
+ (*universeRecurseArg->planetFuncPre) (
+ planet, universeRecurseArg->arg);
+ }
+
+ if (universeRecurseArg->moonFunc != NULL)
+ {
+ RandomContext_SeedRandom (SysGenRNG, planet->rand_seed);
+
+ (*system->genFuncs->generateMoons) (system, planet);
+
+ forAllMoons (star, system, planet, moonRecurse,
+ (void *) universeRecurseArg);
+ }
+
+ if (universeRecurseArg->planetFuncPost != NULL)
+ {
+ system->pOrbitalDesc = planet;
+ DoPlanetaryAnalysis (&system->SysInfo, planet);
+ // When GenerateDefaultFunctions is used as genFuncs,
+ // generateOrbital will also call DoPlanetaryAnalysis,
+ // but with other GenerateFunctions this is not guaranteed.
+ (*system->genFuncs->generateOrbital) (system, planet);
+ (*universeRecurseArg->planetFuncPost) (
+ planet, universeRecurseArg->arg);
+ }
+}
+
+static void
+moonRecurse (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
+ PLANET_DESC *moon, void *arg)
+{
+ UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;
+
+ assert(CurStarDescPtr == star);
+ assert(pSolarSysState == system);
+
+ moon->pPrevDesc = planet;
+
+ if (universeRecurseArg->moonFunc != NULL)
+ {
+ system->pOrbitalDesc = moon;
+ if (moon->data_index != HIERARCHY_STARBASE && moon->data_index != SA_MATRA)
+ {
+ DoPlanetaryAnalysis (&system->SysInfo, moon);
+ // When GenerateDefaultFunctions is used as genFuncs,
+ // generateOrbital will also call DoPlanetaryAnalysis,
+ // but with other GenerateFunctions this is not guaranteed.
+ }
+ (*system->genFuncs->generateOrbital) (system, moon);
+ (*universeRecurseArg->moonFunc) (
+ moon, universeRecurseArg->arg);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+typedef struct
+{
+ FILE *out;
+} DumpUniverseArg;
+
+// Must be called from the Starcon2Main thread.
+void
+dumpUniverse (FILE *out)
+{
+ DumpUniverseArg dumpUniverseArg;
+ UniverseRecurseArg universeRecurseArg;
+
+ dumpUniverseArg.out = out;
+
+ universeRecurseArg.systemFuncPre = dumpSystemCallback;
+ universeRecurseArg.systemFuncPost = NULL;
+ universeRecurseArg.planetFuncPre = dumpPlanetCallback;
+ universeRecurseArg.planetFuncPost = NULL;
+ universeRecurseArg.moonFunc = dumpMoonCallback;
+ universeRecurseArg.arg = (void *) &dumpUniverseArg;
+
+ UniverseRecurse (&universeRecurseArg);
+}
+
+// Must be called from the Starcon2Main thread.
+void
+dumpUniverseToFile (void)
+{
+ FILE *out;
+
+# define UNIVERSE_DUMP_FILE "PlanetInfo"
+ out = fopen(UNIVERSE_DUMP_FILE, "w");
+ if (out == NULL)
+ {
+ fprintf(stderr, "Error: Could not open file '%s' for "
+ "writing: %s\n", UNIVERSE_DUMP_FILE, strerror(errno));
+ return;
+ }
+
+ dumpUniverse (out);
+
+ fclose(out);
+
+ fprintf(stdout, "*** Star dump complete. The game may be in an "
+ "undefined state.\n");
+ // Data generation may have changed the game state,
+ // in particular for special planet generation.
+}
+
+static void
+dumpSystemCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
+ void *arg)
+{
+ FILE *out = ((DumpUniverseArg *) arg)->out;
+ dumpSystem (out, star, system);
+}
+
+void
+dumpSystem (FILE *out, const STAR_DESC *star, const SOLARSYS_STATE *system)
+{
+ UNICODE name[256];
+ UNICODE buf[40];
+
+ GetClusterName (star, name);
+ snprintf (buf, sizeof buf, "%s %s",
+ bodyColorString (STAR_COLOR(star->Type)),
+ starTypeString (STAR_TYPE(star->Type)));
+ fprintf (out, "%-22s (%3d.%1d, %3d.%1d) %-19s %s\n",
+ name,
+ star->star_pt.x / 10, star->star_pt.x % 10,
+ star->star_pt.y / 10, star->star_pt.y % 10,
+ buf,
+ starPresenceString (star->Index));
+
+ (void) system; /* satisfy compiler */
+}
+
+const char *
+bodyColorString (BYTE col)
+{
+ switch (col) {
+ case BLUE_BODY:
+ return "blue";
+ case GREEN_BODY:
+ return "green";
+ case ORANGE_BODY:
+ return "orange";
+ case RED_BODY:
+ return "red";
+ case WHITE_BODY:
+ return "white";
+ case YELLOW_BODY:
+ return "yellow";
+ case CYAN_BODY:
+ return "cyan";
+ case PURPLE_BODY:
+ return "purple";
+ case VIOLET_BODY:
+ return "violet";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+const char *
+starTypeString (BYTE type)
+{
+ switch (type) {
+ case DWARF_STAR:
+ return "dwarf";
+ case GIANT_STAR:
+ return "giant";
+ case SUPER_GIANT_STAR:
+ return "super giant";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+const char *
+starPresenceString (BYTE index)
+{
+ switch (index) {
+ case 0:
+ // nothing
+ return "";
+ case SOL_DEFINED:
+ return "Sol";
+ case SHOFIXTI_DEFINED:
+ return "Shofixti male";
+ case MAIDENS_DEFINED:
+ return "Shofixti maidens";
+ case START_COLONY_DEFINED:
+ return "Starting colony";
+ case SPATHI_DEFINED:
+ return "Spathi home";
+ case ZOQFOT_DEFINED:
+ return "Zoq-Fot-Pik home";
+ case MELNORME0_DEFINED:
+ return "Melnorme trader #0";
+ case MELNORME1_DEFINED:
+ return "Melnorme trader #1";
+ case MELNORME2_DEFINED:
+ return "Melnorme trader #2";
+ case MELNORME3_DEFINED:
+ return "Melnorme trader #3";
+ case MELNORME4_DEFINED:
+ return "Melnorme trader #4";
+ case MELNORME5_DEFINED:
+ return "Melnorme trader #5";
+ case MELNORME6_DEFINED:
+ return "Melnorme trader #6";
+ case MELNORME7_DEFINED:
+ return "Melnorme trader #7";
+ case MELNORME8_DEFINED:
+ return "Melnorme trader #8";
+ case TALKING_PET_DEFINED:
+ return "Talking Pet";
+ case CHMMR_DEFINED:
+ return "Chmmr home";
+ case SYREEN_DEFINED:
+ return "Syreen home";
+ case BURVIXESE_DEFINED:
+ return "Burvixese ruins";
+ case SLYLANDRO_DEFINED:
+ return "Slylandro home";
+ case DRUUGE_DEFINED:
+ return "Druuge home";
+ case BOMB_DEFINED:
+ return "Precursor bomb";
+ case AQUA_HELIX_DEFINED:
+ return "Aqua Helix";
+ case SUN_DEVICE_DEFINED:
+ return "Sun Device";
+ case TAALO_PROTECTOR_DEFINED:
+ return "Taalo Shield";
+ case SHIP_VAULT_DEFINED:
+ return "Syreen ship vault";
+ case URQUAN_WRECK_DEFINED:
+ return "Ur-Quan ship wreck";
+ case VUX_BEAST_DEFINED:
+ return "Zex' beauty";
+ case SAMATRA_DEFINED:
+ return "Sa-Matra";
+ case ZOQ_SCOUT_DEFINED:
+ return "Zoq-Fot-Pik scout";
+ case MYCON_DEFINED:
+ return "Mycon home";
+ case EGG_CASE0_DEFINED:
+ return "Mycon egg shell #0";
+ case EGG_CASE1_DEFINED:
+ return "Mycon egg shell #1";
+ case EGG_CASE2_DEFINED:
+ return "Mycon egg shell #2";
+ case PKUNK_DEFINED:
+ return "Pkunk home";
+ case UTWIG_DEFINED:
+ return "Utwig home";
+ case SUPOX_DEFINED:
+ return "Supox home";
+ case YEHAT_DEFINED:
+ return "Yehat home";
+ case VUX_DEFINED:
+ return "Vux home";
+ case ORZ_DEFINED:
+ return "Orz home";
+ case THRADD_DEFINED:
+ return "Thraddash home";
+ case RAINBOW_DEFINED:
+ return "Rainbow world";
+ case ILWRATH_DEFINED:
+ return "Ilwrath home";
+ case ANDROSYNTH_DEFINED:
+ return "Androsynth ruins";
+ case MYCON_TRAP_DEFINED:
+ return "Mycon trap";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+static void
+dumpPlanetCallback (const PLANET_DESC *planet, void *arg)
+{
+ FILE *out = ((DumpUniverseArg *) arg)->out;
+ dumpPlanet (out, planet);
+}
+
+void
+dumpPlanet (FILE *out, const PLANET_DESC *planet)
+{
+ (*pSolarSysState->genFuncs->generateName) (pSolarSysState, planet);
+ fprintf (out, "- %-37s %s\n", GLOBAL_SIS (PlanetName),
+ planetTypeString (planet->data_index & ~PLANET_SHIELDED));
+ dumpWorld (out, planet);
+}
+
+static void
+dumpMoonCallback (const PLANET_DESC *moon, void *arg)
+{
+ FILE *out = ((DumpUniverseArg *) arg)->out;
+ dumpMoon (out, moon);
+}
+
+void
+dumpMoon (FILE *out, const PLANET_DESC *moon)
+{
+ const char *typeStr;
+
+ if (moon->data_index == HIERARCHY_STARBASE)
+ {
+ typeStr = "StarBase";
+ }
+ else if (moon->data_index == SA_MATRA)
+ {
+ typeStr = "Sa-Matra";
+ }
+ else
+ {
+ typeStr = planetTypeString (moon->data_index & ~PLANET_SHIELDED);
+ }
+ fprintf (out, " - Moon %-30c %s\n",
+ 'a' + (moon - &pSolarSysState->MoonDesc[0]), typeStr);
+
+ dumpWorld (out, moon);
+}
+
+static void
+dumpWorld (FILE *out, const PLANET_DESC *world)
+{
+ PLANET_INFO *info;
+
+ if (world->data_index == HIERARCHY_STARBASE) {
+ return;
+ }
+
+ if (world->data_index == SA_MATRA) {
+ return;
+ }
+
+ info = &pSolarSysState->SysInfo.PlanetInfo;
+ fprintf(out, " AxialTilt: %d\n", info->AxialTilt);
+ fprintf(out, " Tectonics: %d\n", info->Tectonics);
+ fprintf(out, " Weather: %d\n", info->Weather);
+ fprintf(out, " Density: %d\n", info->PlanetDensity);
+ fprintf(out, " Radius: %d\n", info->PlanetRadius);
+ fprintf(out, " Gravity: %d\n", info->SurfaceGravity);
+ fprintf(out, " Temp: %d\n", info->SurfaceTemperature);
+ fprintf(out, " Day: %d\n", info->RotationPeriod);
+ fprintf(out, " Atmosphere: %d\n", info->AtmoDensity);
+ fprintf(out, " LifeChance: %d\n", info->LifeChance);
+ fprintf(out, " DistToSun: %d\n", info->PlanetToSunDist);
+
+ if (world->data_index & PLANET_SHIELDED) {
+ // Slave-shielded planet
+ return;
+ }
+
+ fprintf (out, " Bio: %4d Min: %4d\n",
+ calculateBioValue (pSolarSysState, world),
+ calculateMineralValue (pSolarSysState, world));
+}
+
+COUNT
+calculateBioValue (const SOLARSYS_STATE *system, const PLANET_DESC *world)
+{
+ COUNT result;
+ COUNT numBio;
+ COUNT i;
+
+ assert (system->pOrbitalDesc == world);
+
+ numBio = callGenerateForScanType (system, world, GENERATE_ALL,
+ BIOLOGICAL_SCAN, NULL);
+
+ result = 0;
+ for (i = 0; i < numBio; i++)
+ {
+ NODE_INFO info;
+ callGenerateForScanType (system, world, i, BIOLOGICAL_SCAN, &info);
+ result += BIO_CREDIT_VALUE *
+ LONIBBLE (CreatureData[info.type].ValueAndHitPoints);
+ }
+ return result;
+}
+
+void
+generateBioIndex(const SOLARSYS_STATE *system, const PLANET_DESC *world,
+ COUNT bio[])
+{
+ COUNT numBio;
+ COUNT i;
+
+ assert (system->pOrbitalDesc == world);
+
+ numBio = callGenerateForScanType (system, world, GENERATE_ALL,
+ BIOLOGICAL_SCAN, NULL);
+
+ for (i = 0; i < NUM_CREATURE_TYPES + NUM_SPECIAL_CREATURE_TYPES; i++)
+ bio[i] = 0;
+
+ for (i = 0; i < numBio; i++)
+ {
+ NODE_INFO info;
+ callGenerateForScanType (system, world, i, BIOLOGICAL_SCAN, &info);
+ bio[info.type]++;
+ }
+}
+
+COUNT
+calculateMineralValue (const SOLARSYS_STATE *system, const PLANET_DESC *world)
+{
+ COUNT result;
+ COUNT numDeposits;
+ COUNT i;
+
+ assert (system->pOrbitalDesc == world);
+
+ numDeposits = callGenerateForScanType (system, world, GENERATE_ALL,
+ MINERAL_SCAN, NULL);
+
+ result = 0;
+ for (i = 0; i < numDeposits; i++)
+ {
+ NODE_INFO info;
+ callGenerateForScanType (system, world, i, MINERAL_SCAN, &info);
+ result += HIBYTE (info.density) *
+ GLOBAL (ElementWorth[ElementCategory (info.type)]);
+ }
+ return result;
+}
+
+void
+generateMineralIndex(const SOLARSYS_STATE *system, const PLANET_DESC *world,
+ COUNT minerals[])
+{
+ COUNT numDeposits;
+ COUNT i;
+
+ assert (system->pOrbitalDesc == world);
+
+ numDeposits = callGenerateForScanType (system, world, GENERATE_ALL,
+ MINERAL_SCAN, NULL);
+
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; i++)
+ minerals[i] = 0;
+
+ for (i = 0; i < numDeposits; i++)
+ {
+ NODE_INFO info;
+ callGenerateForScanType (system, world, i, MINERAL_SCAN, &info);
+ minerals[ElementCategory (info.type)] += HIBYTE (info.density);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+struct TallyResourcesArg
+{
+ FILE *out;
+ COUNT mineralCount;
+ COUNT bioCount;
+};
+
+// Must be called from the Starcon2Main thread.
+void
+tallyResources (FILE *out)
+{
+ TallyResourcesArg tallyResourcesArg;
+ UniverseRecurseArg universeRecurseArg;
+
+ tallyResourcesArg.out = out;
+
+ universeRecurseArg.systemFuncPre = tallySystemPreCallback;
+ universeRecurseArg.systemFuncPost = tallySystemPostCallback;
+ universeRecurseArg.planetFuncPre = tallyPlanetCallback;
+ universeRecurseArg.planetFuncPost = NULL;
+ universeRecurseArg.moonFunc = tallyMoonCallback;
+ universeRecurseArg.arg = (void *) &tallyResourcesArg;
+
+ UniverseRecurse (&universeRecurseArg);
+}
+
+// Must be called from the Starcon2Main thread.
+void
+tallyResourcesToFile (void)
+{
+ FILE *out;
+
+# define RESOURCE_TALLY_FILE "ResourceTally"
+ out = fopen(RESOURCE_TALLY_FILE, "w");
+ if (out == NULL)
+ {
+ fprintf(stderr, "Error: Could not open file '%s' for "
+ "writing: %s\n", RESOURCE_TALLY_FILE, strerror(errno));
+ return;
+ }
+
+ tallyResources (out);
+
+ fclose(out);
+
+ fprintf(stdout, "*** Resource tally complete. The game may be in an "
+ "undefined state.\n");
+ // Data generation may have changed the game state,
+ // in particular for special planet generation.
+}
+
+static void
+tallySystemPreCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
+ void *arg)
+{
+ TallyResourcesArg *tallyResourcesArg = (TallyResourcesArg *) arg;
+ tallyResourcesArg->mineralCount = 0;
+ tallyResourcesArg->bioCount = 0;
+
+ (void) star; /* satisfy compiler */
+ (void) system; /* satisfy compiler */
+}
+
+static void
+tallySystemPostCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
+ void *arg)
+{
+ UNICODE name[256];
+ TallyResourcesArg *tallyResourcesArg = (TallyResourcesArg *) arg;
+ FILE *out = tallyResourcesArg->out;
+
+ GetClusterName (star, name);
+ fprintf (out, "%s\t%d\t%d\n", name, tallyResourcesArg->mineralCount,
+ tallyResourcesArg->bioCount);
+
+ (void) star; /* satisfy compiler */
+ (void) system; /* satisfy compiler */
+}
+
+static void
+tallyPlanetCallback (const PLANET_DESC *planet, void *arg)
+{
+ tallyResourcesWorld ((TallyResourcesArg *) arg, planet);
+}
+
+static void
+tallyMoonCallback (const PLANET_DESC *moon, void *arg)
+{
+ tallyResourcesWorld ((TallyResourcesArg *) arg, moon);
+}
+
+static void
+tallyResourcesWorld (TallyResourcesArg *arg, const PLANET_DESC *world)
+{
+ if (world->data_index == HIERARCHY_STARBASE) {
+ return;
+ }
+
+ if (world->data_index == SA_MATRA) {
+ return;
+ }
+
+ arg->bioCount += calculateBioValue (pSolarSysState, world),
+ arg->mineralCount += calculateMineralValue (pSolarSysState, world);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+forAllPlanetTypes (void (*callback) (int, const PlanetFrame *, void *),
+ void *arg)
+{
+ int i;
+ extern const PlanetFrame planet_array[];
+
+ for (i = 0; i < NUMBER_OF_PLANET_TYPES; i++)
+ callback (i, &planet_array[i], arg);
+}
+
+typedef struct
+{
+ FILE *out;
+} DumpPlanetTypesArg;
+
+void
+dumpPlanetTypes (FILE *out)
+{
+ DumpPlanetTypesArg dumpPlanetTypesArg;
+ dumpPlanetTypesArg.out = out;
+
+ forAllPlanetTypes (dumpPlanetTypeCallback, (void *) &dumpPlanetTypesArg);
+}
+
+static void
+dumpPlanetTypeCallback (int index, const PlanetFrame *planetType, void *arg)
+{
+ DumpPlanetTypesArg *dumpPlanetTypesArg = (DumpPlanetTypesArg *) arg;
+
+ dumpPlanetType(dumpPlanetTypesArg->out, index, planetType);
+}
+
+void
+dumpPlanetType (FILE *out, int index, const PlanetFrame *planetType)
+{
+ int i;
+ fprintf (out,
+ "%s\n"
+ "\tType: %s\n"
+ "\tColor: %s\n"
+ "\tSurface generation algoritm: %s\n"
+ "\tTectonics: %s\n"
+ "\tAtmosphere: %s\n"
+ "\tDensity: %s\n"
+ "\tElements:\n",
+ planetTypeString (index),
+ worldSizeString (PLANSIZE (planetType->Type)),
+ bodyColorString (PLANCOLOR (planetType->Type)),
+ worldGenAlgoString (PLANALGO (planetType->Type)),
+ tectonicsString (planetType->BaseTectonics),
+ atmosphereString (HINIBBLE (planetType->AtmoAndDensity)),
+ densityString (LONIBBLE (planetType->AtmoAndDensity))
+ );
+ for (i = 0; i < NUM_USEFUL_ELEMENTS; i++)
+ {
+ const ELEMENT_ENTRY *entry;
+ entry = &planetType->UsefulElements[i];
+ if (entry->Density == 0)
+ continue;
+ fprintf(out, "\t\t0 to %d %s-quality (+%d) deposits of %s (%s)\n",
+ DEPOSIT_QUANTITY (entry->Density),
+ depositQualityString (DEPOSIT_QUALITY (entry->Density)),
+ DEPOSIT_QUALITY (entry->Density) * 5,
+ GAME_STRING (ELEMENTS_STRING_BASE + entry->ElementType),
+ GAME_STRING (CARGO_STRING_BASE + 2 + ElementCategory (
+ entry->ElementType))
+ );
+ }
+ fprintf (out, "\n");
+}
+
+const char *
+planetTypeString (int typeIndex)
+{
+ static UNICODE typeStr[40];
+
+ if (typeIndex >= FIRST_GAS_GIANT)
+ {
+ // "Gas Giant"
+ snprintf(typeStr, sizeof typeStr, "%s",
+ GAME_STRING (SCAN_STRING_BASE + 4 + 51));
+ }
+ else
+ {
+ // "<type> World" (eg. "Water World")
+ snprintf(typeStr, sizeof typeStr, "%s %s",
+ GAME_STRING (SCAN_STRING_BASE + 4 + typeIndex),
+ GAME_STRING (SCAN_STRING_BASE + 4 + 50));
+ }
+ return typeStr;
+}
+
+// size is what you get from PLANSIZE (planetFrame.Type)
+const char *
+worldSizeString (BYTE size)
+{
+ switch (size)
+ {
+ case SMALL_ROCKY_WORLD:
+ return "small rocky world";
+ case LARGE_ROCKY_WORLD:
+ return "large rocky world";
+ case GAS_GIANT:
+ return "gas giant";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+// algo is what you get from PLANALGO (planetFrame.Type)
+const char *
+worldGenAlgoString (BYTE algo)
+{
+ switch (algo)
+ {
+ case TOPO_ALGO:
+ return "TOPO_ALGO";
+ case CRATERED_ALGO:
+ return "CRATERED_ALGO";
+ case GAS_GIANT_ALGO:
+ return "GAS_GIANT_ALGO";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+// tectonics is what you get from planetFrame.BaseTechtonics
+// not reentrant
+const char *
+tectonicsString (BYTE tectonics)
+{
+ static char buf[sizeof "-127"];
+ switch (tectonics)
+ {
+ case NO_TECTONICS:
+ return "none";
+ case LOW_TECTONICS:
+ return "low";
+ case MED_TECTONICS:
+ return "medium";
+ case HIGH_TECTONICS:
+ return "high";
+ case SUPER_TECTONICS:
+ return "super";
+ default:
+ snprintf (buf, sizeof buf, "%d", tectonics);
+ return buf;
+ }
+}
+
+// atmosphere is what you get from HINIBBLE (planetFrame.AtmoAndDensity)
+const char *
+atmosphereString (BYTE atmosphere)
+{
+ switch (atmosphere)
+ {
+ case LIGHT:
+ return "thin";
+ case MEDIUM:
+ return "normal";
+ case HEAVY:
+ return "thick";
+ default:
+ return "super thick";
+ }
+}
+
+// density is what you get from LONIBBLE (planetFrame.AtmoAndDensity)
+const char *
+densityString (BYTE density)
+{
+ switch (density)
+ {
+ case GAS_DENSITY:
+ return "gaseous";
+ case LIGHT_DENSITY:
+ return "light";
+ case LOW_DENSITY:
+ return "low";
+ case NORMAL_DENSITY:
+ return "normal";
+ case HIGH_DENSITY:
+ return "high";
+ case SUPER_DENSITY:
+ return "super high";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+// quality is what you get from DEPOSIT_QUALITY (elementEntry.Density)
+const char *
+depositQualityString (BYTE quality)
+{
+ switch (quality)
+ {
+ case LIGHT:
+ return "low";
+ case MEDIUM:
+ return "medium";
+ case HEAVY:
+ return "high";
+ default:
+ // Should not happen
+ return "???";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// playerNr should be 0 or 1
+STARSHIP*
+findPlayerShip (SIZE playerNr)
+{
+ HELEMENT hElement, hNextElement;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hNextElement = GetSuccElement (ElementPtr);
+
+ if ((ElementPtr->state_flags & PLAYER_SHIP) &&
+ ElementPtr->playerNr == playerNr)
+ {
+ STARSHIP *StarShipPtr;
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ UnlockElement (hElement);
+ return StarShipPtr;
+ }
+
+ UnlockElement (hElement);
+ }
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+resetCrewBattle (void)
+{
+ STARSHIP *StarShipPtr;
+ COUNT delta;
+ CONTEXT OldContext;
+
+ if (!(GLOBAL (CurrentActivity) & IN_BATTLE) ||
+ (inHQSpace ()))
+ return;
+
+ StarShipPtr = findPlayerShip (RPG_PLAYER_NUM);
+ if (StarShipPtr == NULL || StarShipPtr->RaceDescPtr == NULL)
+ return;
+
+ delta = StarShipPtr->RaceDescPtr->ship_info.max_crew -
+ StarShipPtr->RaceDescPtr->ship_info.crew_level;
+
+ OldContext = SetContext (StatusContext);
+ DeltaCrew (StarShipPtr->hShip, delta);
+ SetContext (OldContext);
+}
+
+void
+resetEnergyBattle (void)
+{
+ STARSHIP *StarShipPtr;
+ COUNT delta;
+ CONTEXT OldContext;
+
+ if (!(GLOBAL (CurrentActivity) & IN_BATTLE) ||
+ (inHQSpace ()))
+ return;
+
+ StarShipPtr = findPlayerShip (RPG_PLAYER_NUM);
+ if (StarShipPtr == NULL || StarShipPtr->RaceDescPtr == NULL)
+ return;
+
+ delta = StarShipPtr->RaceDescPtr->ship_info.max_energy -
+ StarShipPtr->RaceDescPtr->ship_info.energy_level;
+
+ OldContext = SetContext (StatusContext);
+ DeltaEnergy (StarShipPtr->hShip, delta);
+ SetContext (OldContext);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// This function should help in making sure that gamestr.h matches
+// gamestrings.txt.
+void
+dumpStrings (FILE *out)
+{
+#define STRINGIZE(a) #a
+#define MAKE_STRING_CATEGORY(prefix) \
+ { \
+ /* .name = */ STRINGIZE(prefix ## _BASE), \
+ /* .base = */ prefix ## _BASE, \
+ /* .count = */ prefix ## _COUNT \
+ }
+ struct {
+ const char *name;
+ size_t base;
+ size_t count;
+ } categories[] = {
+ { "0", 0, 0 },
+ MAKE_STRING_CATEGORY(STAR_STRING),
+ MAKE_STRING_CATEGORY(DEVICE_STRING),
+ MAKE_STRING_CATEGORY(CARGO_STRING),
+ MAKE_STRING_CATEGORY(ELEMENTS_STRING),
+ MAKE_STRING_CATEGORY(SCAN_STRING),
+ MAKE_STRING_CATEGORY(STAR_NUMBER),
+ MAKE_STRING_CATEGORY(PLANET_NUMBER),
+ MAKE_STRING_CATEGORY(MONTHS_STRING),
+ MAKE_STRING_CATEGORY(FEEDBACK_STRING),
+ MAKE_STRING_CATEGORY(STARBASE_STRING),
+ MAKE_STRING_CATEGORY(ENCOUNTER_STRING),
+ MAKE_STRING_CATEGORY(NAVIGATION_STRING),
+ MAKE_STRING_CATEGORY(NAMING_STRING),
+ MAKE_STRING_CATEGORY(MELEE_STRING),
+ MAKE_STRING_CATEGORY(SAVEGAME_STRING),
+ MAKE_STRING_CATEGORY(OPTION_STRING),
+ MAKE_STRING_CATEGORY(QUITMENU_STRING),
+ MAKE_STRING_CATEGORY(STATUS_STRING),
+ MAKE_STRING_CATEGORY(FLAGSHIP_STRING),
+ MAKE_STRING_CATEGORY(ORBITSCAN_STRING),
+ MAKE_STRING_CATEGORY(MAINMENU_STRING),
+ MAKE_STRING_CATEGORY(NETMELEE_STRING),
+ { "GAMESTR_COUNT", GAMESTR_COUNT, (size_t) -1 }
+ };
+ size_t numCategories = sizeof categories / sizeof categories[0];
+ size_t numStrings = GetStringTableCount (GameStrings);
+ size_t stringI;
+ size_t categoryI = 0;
+
+ // Start with a sanity check to see if gamestr.h has been changed but
+ // not this file.
+ for (categoryI = 0; categoryI < numCategories - 1; categoryI++) {
+ if (categories[categoryI].base + categories[categoryI].count !=
+ categories[categoryI + 1].base) {
+ fprintf(stderr, "Error: String category list in dumpStrings() is "
+ "not up to date.\n");
+ return;
+ }
+ }
+
+ if (GAMESTR_COUNT != numStrings) {
+ fprintf(stderr, "Warning: GAMESTR_COUNT is %d, but GameStrings "
+ "contains %d strings.\n", GAMESTR_COUNT, numStrings);
+ }
+
+ categoryI = 0;
+ for (stringI = 0; stringI < numStrings; stringI++) {
+ while (categoryI < numCategories &&
+ stringI >= categories[categoryI + 1].base)
+ categoryI++;
+ fprintf(out, "[ %s + %d ] %s\n", categories[categoryI].name,
+ stringI - categories[categoryI].base, GAME_STRING(stringI));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+
+static Color
+hsvaToRgba (double hue, double sat, double val, BYTE alpha)
+{
+ unsigned int hi = (int) (hue / 60.0);
+ double f = (hue / 60.0) - ((int) (hue / 60.0));
+ double p = val * (1.0 - sat);
+ double q = val * (1.0 - f * sat);
+ double t = val * (1.0 - (1.0 - f * sat));
+
+ // Convert p, q, t, and v from [0..1] to [0..255]
+ BYTE pb = (BYTE) (p * 255.0 + 0.5);
+ BYTE qb = (BYTE) (q * 255.0 + 0.5);
+ BYTE tb = (BYTE) (t * 255.0 + 0.5);
+ BYTE vb = (BYTE) (val * 255.0 + 0.5);
+
+ assert (hue >= 0.0 && hue < 360.0);
+ assert (sat >= 0 && sat <= 1.0);
+ assert (val >= 0 && val <= 1.0);
+ /*fprintf(stderr, "hsva = (%.1f, %.2f, %.2f, %.2d)\n",
+ hue, sat, val, alpha);*/
+
+ assert (hi < 6);
+ switch (hi) {
+ case 0: return BUILD_COLOR_RGBA (vb, tb, pb, alpha);
+ case 1: return BUILD_COLOR_RGBA (qb, vb, pb, alpha);
+ case 2: return BUILD_COLOR_RGBA (pb, vb, tb, alpha);
+ case 3: return BUILD_COLOR_RGBA (pb, qb, vb, alpha);
+ case 4: return BUILD_COLOR_RGBA (tb, pb, vb, alpha);
+ case 5: return BUILD_COLOR_RGBA (vb, pb, qb, alpha);
+ }
+
+ // Should not happen.
+ return BUILD_COLOR_RGBA (0, 0, 0, alpha);
+}
+
+// Returns true iff this context has a visible FRAME.
+static bool
+isContextVisible (CONTEXT context)
+{
+ FRAME contextFrame;
+
+ // Save the original context.
+ CONTEXT oldContext = SetContext (context);
+
+ // Get the frame of the specified context.
+ contextFrame = GetContextFGFrame ();
+
+ // Restore the original context.
+ SetContext (oldContext);
+
+ return contextFrame == Screen;
+}
+
+static size_t
+countVisibleContexts (void)
+{
+ size_t contextCount;
+ CONTEXT context;
+
+ contextCount = 0;
+ for (context = GetFirstContext (); context != NULL;
+ context = GetNextContext (context))
+ {
+ if (!isContextVisible (context))
+ continue;
+
+ contextCount++;
+ }
+
+ return contextCount;
+}
+
+static void
+drawContext (CONTEXT context, double hue /* no pun intended */)
+{
+ FRAME drawFrame;
+ CONTEXT oldContext;
+ FONT oldFont;
+ DrawMode oldMode;
+ Color oldFgCol;
+ Color rectCol;
+ Color lineCol;
+ Color textCol;
+ bool haveClippingRect;
+ RECT rect;
+ LINE line;
+ TEXT text;
+ POINT p1, p2, p3, p4;
+
+ drawFrame = GetContextFGFrame ();
+ rectCol = hsvaToRgba (hue, 1.0, 0.5, 100);
+ lineCol = hsvaToRgba (hue, 1.0, 1.0, 90);
+ textCol = lineCol;
+
+ // Save the original context.
+ oldContext = SetContext (context);
+
+ // Get the clipping rectangle of the specified context.
+ haveClippingRect = GetContextClipRect (&rect);
+
+ // Switch back the old context; we're going to draw in it.
+ (void) SetContext (oldContext);
+
+ p1 = rect.corner;
+ p2.x = rect.corner.x + rect.extent.width - 1;
+ p2.y = rect.corner.y;
+ p3.x = rect.corner.x;
+ p3.y = rect.corner.y + rect.extent.height - 1;
+ p4.x = rect.corner.x + rect.extent.width - 1;
+ p4.y = rect.corner.y + rect.extent.height - 1;
+
+ oldFgCol = SetContextForeGroundColor (rectCol);
+ DrawFilledRectangle (&rect);
+
+ SetContextForeGroundColor (lineCol);
+ line.first = p1; line.second = p2; DrawLine (&line);
+ line.first = p2; line.second = p4; DrawLine (&line);
+ line.first = p1; line.second = p3; DrawLine (&line);
+ line.first = p3; line.second = p4; DrawLine (&line);
+ line.first = p1; line.second = p4; DrawLine (&line);
+ line.first = p2; line.second = p3; DrawLine (&line);
+ // Gimme C'99! So I can do:
+ // DrawLine ((LINE) { .first = p1, .second = p2 })
+
+ oldFont = SetContextFont (TinyFont);
+ SetContextForeGroundColor (textCol);
+ // Text prim does not yet support alpha via Color.a
+ oldMode = SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ALPHA, textCol.a));
+ text.baseline.x = (p1.x + (p2.x + 1)) / 2;
+ text.baseline.y = p1.y + 8;
+ text.pStr = GetContextName (context);
+ text.align = ALIGN_CENTER;
+ text.CharCount = (COUNT) ~0;
+ font_DrawText (&text);
+ (void) SetContextDrawMode (oldMode);
+
+ (void) SetContextForeGroundColor (oldFgCol);
+ (void) SetContextFont (oldFont);
+}
+
+static void
+describeContext (FILE *out, const CONTEXT context) {
+ RECT rect;
+ CONTEXT oldContext = SetContext (context);
+
+ GetContextClipRect (&rect);
+ fprintf(out, "Context '%s':\n"
+ "\tClipRect = (%d, %d)-(%d, %d) (%d x %d)\n",
+ GetContextName (context),
+ rect.corner.x, rect.corner.y,
+ rect.corner.x + rect.extent.width,
+ rect.corner.y + rect.extent.height,
+ rect.extent.width, rect.extent.height);
+
+ SetContext (oldContext);
+}
+
+
+typedef struct wait_state
+{
+ // standard state required by DoInput
+ BOOLEAN (*InputFunc) (struct wait_state *self);
+} WAIT_STATE;
+
+
+// Maybe move to elsewhere, where it can be reused?
+static BOOLEAN
+waitForKey (struct wait_state *self) {
+ if (PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL])
+ return FALSE;
+
+ SleepThread (ONE_SECOND / 20);
+
+ (void) self;
+ return TRUE;
+}
+
+// Maybe move to elsewhere, where it can be reused?
+static FRAME
+getScreen (void)
+{
+ CONTEXT oldContext = SetContext (ScreenContext);
+ FRAME savedFrame;
+ RECT screenRect;
+
+ screenRect.corner.x = 0;
+ screenRect.corner.y = 0;
+ screenRect.extent.width = ScreenWidth;
+ screenRect.extent.height = ScreenHeight;
+ savedFrame = CaptureDrawable (LoadDisplayPixmap (&screenRect, (FRAME) 0));
+
+ (void) SetContext (oldContext);
+ return savedFrame;
+}
+
+static void
+putScreen (FRAME savedFrame) {
+ STAMP stamp;
+
+ CONTEXT oldContext = SetContext (ScreenContext);
+
+ stamp.origin.x = 0;
+ stamp.origin.y = 0;
+ stamp.frame = savedFrame;
+ DrawStamp (&stamp);
+
+ (void) SetContext (oldContext);
+}
+
+// Show the contexts on the screen.
+// Must be called from the main thread.
+void
+debugContexts (void)
+{
+ static volatile bool inDebugContexts = false;
+ // Prevent this function from being called from within itself.
+
+ CONTEXT orgContext;
+ CONTEXT debugDrawContext;
+ // We're going to use this context to draw in.
+ FRAME debugDrawFrame;
+ double hueIncrement;
+ size_t visibleContextI;
+ CONTEXT context;
+ size_t contextCount;
+ FRAME savedScreen;
+
+ // Prevent this function from being called from within itself.
+ if (inDebugContexts)
+ return;
+ inDebugContexts = true;
+
+ contextCount = countVisibleContexts ();
+ if (contextCount == 0)
+ {
+ goto out;
+ }
+
+ savedScreen = getScreen ();
+ FlushGraphics ();
+ // Make sure that the screen has actually been captured,
+ // before we use the frame.
+
+ // Create a new frame to draw on.
+ debugDrawContext = CreateContext ("debugDrawContext");
+ // New work frame is a copy of the original.
+ debugDrawFrame = CaptureDrawable (CloneFrame (savedScreen));
+ orgContext = SetContext (debugDrawContext);
+ SetContextFGFrame (debugDrawFrame);
+
+ hueIncrement = 360.0 / contextCount;
+
+ visibleContextI = 0;
+ for (context = GetFirstContext (); context != NULL;
+ context = GetNextContext (context))
+ {
+ if (context == debugDrawContext) {
+ // Skip our own context.
+ continue;
+ }
+
+ if (isContextVisible (context))
+ {
+ // Only draw the visible contexts.
+ drawContext (context, visibleContextI * hueIncrement);
+ visibleContextI++;
+ }
+
+ describeContext (stderr, context);
+ }
+
+ // Blit the final debugging frame to the screen.
+ putScreen (debugDrawFrame);
+
+ // Wait for a key:
+ {
+ WAIT_STATE state;
+ state.InputFunc = waitForKey;
+ DoInput(&state, TRUE);
+ }
+
+ SetContext (orgContext);
+
+ // Destroy the debugging frame and context.
+ DestroyContext (debugDrawContext);
+ // This does nothing with the drawable set with
+ // SetContextFGFrame().
+ DestroyDrawable (ReleaseDrawable (debugDrawFrame));
+
+ putScreen (savedScreen);
+
+ DestroyDrawable (ReleaseDrawable (savedScreen));
+
+out:
+ inDebugContexts = false;
+}
+
+#endif /* DEBUG */
+
diff --git a/src/uqm/uqmdebug.h b/src/uqm/uqmdebug.h
new file mode 100644
index 0000000..423841e
--- /dev/null
+++ b/src/uqm/uqmdebug.h
@@ -0,0 +1,200 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#if !defined(UQMDEBUG_H) && (defined(DEBUG) || defined(USE_DEBUG_KEY))
+#define UQMDEBUG_H
+
+#include "clock.h"
+#include "planets/planets.h"
+#include "races.h"
+#include "libs/compiler.h"
+
+#include <stdio.h>
+
+
+// If set to true, interactive routines that are called (indirectly) in debug
+// functions are a no-op.
+extern BOOLEAN disableInteractivity;
+
+// If a function is assigned to this, it will be called from the
+// Starcon2Main thread, in the main game loop.
+extern void (* volatile debugHook) (void);
+
+// Called on the main() thread when the debug key (symbol 'Debug' in the
+// keys.cfg) is pressed
+void debugKeyPressed (void);
+// Called on the Starcon2Main() thread when the debug key (symbol 'Debug'
+// in the keys.cfg) is pressed.
+void debugKeyPressedSynchronous (void);
+
+// Forward time to the next event. If skipHEE is set, the event named
+// HYPERSPACE_ENCOUNTER_EVENT, which normally occurs every game day,
+// is skipped. Must be called on the Starcon2Main thread.
+void forwardToNextEvent (BOOLEAN skipHEE);
+// Generate a list of all events in the event queue.
+// Must be called on the Starcon2Main thread.
+void dumpEvents (FILE *out);
+// Describe one event.
+void dumpEvent (FILE *out, const EVENT *eventPtr);
+// Get the name of one event.
+const char *eventName (BYTE func_index);
+
+// Give the flagship a decent equipment for debugging.
+void equipShip (void);
+// Give the player all devices.
+void giveDevices (void);
+
+// Remove all escort ships.
+void clearEscorts (void);
+
+// Show all active spheres of influence.
+void showSpheres (void);
+
+// Make the ships of all races available for building at the shipyard.
+void activateAllShips (void);
+
+// Move the Flagship to the destination of the autopilot.
+// Should only be called from HS/QS.
+// It can be called from debugHook directly after entering HS/QS though.
+void doInstantMove (void);
+
+
+// Call a function for all stars.
+void forAllStars (void (*callback) (STAR_DESC *, void *), void *arg);
+// Call a function for all planets in a star system.
+void forAllPlanets (STAR_DESC *star, SOLARSYS_STATE *system,
+ void (*callback) (STAR_DESC *, SOLARSYS_STATE *, PLANET_DESC *,
+ void *), void *arg);
+// Call a function for all moons of a planet.
+void forAllMoons (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
+ void (*callback) (STAR_DESC *, SOLARSYS_STATE *, PLANET_DESC *,
+ PLANET_DESC *, void *), void *arg);
+
+// Argument to UniverseRecurse()
+typedef struct
+{
+ void (*systemFuncPre) (const STAR_DESC *star,
+ const SOLARSYS_STATE *system, void *arg);
+ // Called for each system prior to recursing to its planets.
+ void (*systemFuncPost) (const STAR_DESC *star,
+ const SOLARSYS_STATE *system, void *arg);
+ // Called for each system after recursing to its planets.
+ void (*planetFuncPre) (const PLANET_DESC *planet, void *arg);
+ // Called for each planet prior to recursing to its moons.
+ void (*planetFuncPost) (const PLANET_DESC *planet, void *arg);
+ // Called for each planet after recursing to its moons.
+ void (*moonFunc) (const PLANET_DESC *moon, void *arg);
+ // Called for each moon.
+ void *arg;
+ // User data.
+} UniverseRecurseArg;
+// Recurse through all systems, planets, and moons in the universe.
+// Must be called on the Starcon2Main thread.
+void UniverseRecurse (UniverseRecurseArg *universeRecurseArg);
+
+// Describe the entire universe. Must be called on the Starcon2Main thread.
+void dumpUniverse (FILE *out);
+// Describe the entire universe, output to a file "./PlanetInfo".
+// Must be called on the Starcon2Main thread.
+void dumpUniverseToFile (void);
+// Describe one star system.
+void dumpSystem (FILE *out, const STAR_DESC *star,
+ const SOLARSYS_STATE *system);
+// Get a star color as a string.
+const char *bodyColorString (BYTE col);
+// Get a star type as a string.
+const char *starTypeString (BYTE type);
+// Get a string describing special presence in the star system.
+const char *starPresenceString (BYTE index);
+// Get a list describing all planets in a star.
+void dumpPlanets (FILE *out, const STAR_DESC *star);
+// Describe one planet.
+void dumpPlanet(FILE *out, const PLANET_DESC *planet);
+// Describe one moon.
+void dumpMoon (FILE *out, const PLANET_DESC *moon);
+// Calculate the total value of all minerals on a world.
+COUNT calculateMineralValue (const SOLARSYS_STATE *system,
+ const PLANET_DESC *world);
+// Determine how much of each mineral type is present on a world
+void generateMineralIndex(const SOLARSYS_STATE *system,
+ const PLANET_DESC *world, COUNT minerals[]);
+// Calculate the total value of all bio on a world.
+COUNT calculateBioValue (const SOLARSYS_STATE *system,
+ const PLANET_DESC *world);
+// Determine how much of each mineral type is present on a world
+void generateBioIndex(const SOLARSYS_STATE *system,
+ const PLANET_DESC *world, COUNT bio[]);
+
+// Tally the resources for each star system.
+// Must be called on the Starcon2Main thread.
+void tallyResources (FILE *out);
+// Tally the resources for each star system, output to a file
+// "./ResourceTally". Must be called on the Starcon2Main thread.
+void tallyResourcesToFile (void);
+
+
+// Call a function for all planet types.
+void forAllPlanetTypes (void (*callBack) (int, const PlanetFrame *,
+ void *), void *arg);
+// Describe one planet type.
+void dumpPlanetType(FILE *out, int index, const PlanetFrame *planetFrame);
+// Generate a list of all planet types.
+void dumpPlanetTypes(FILE *out);
+// Get a string describing a planet type.
+const char *planetTypeString (int typeIndex);
+// Get a string describing the size of a type of planet.
+const char *worldSizeString (BYTE size);
+// Get a string describing a planet type map generation algoritm.
+const char *worldGenAlgoString (BYTE algo);
+// Get a string describing the severity of a tectonics on a type of planet.
+const char *tectonicsString (BYTE tectonics);
+// Get a string describing the atmospheric pressure on a type of planet.
+const char *atmosphereString (BYTE atmosphere);
+// Get a string describing the density of a type of planet.
+const char *densityString (BYTE density);
+
+// Get a string describing the quality of a deposit.
+const char *depositQualityString (BYTE quality);
+
+
+// Find a player ship. Setting playerNr to non-0 is only meaningful in battle.
+STARSHIP* findPlayerShip (SIZE playerNr);
+
+// Resets the crew of the first player (the bottom one) to its maximum.
+void resetCrewBattle(void);
+
+// Resets the energy of the first player (the bottom one) to its maximum.
+void resetEnergyBattle(void);
+
+
+// Move instantly across hyperspace/quasispace.
+extern BOOLEAN instantMove;
+
+
+// Dump all game strings.
+void dumpStrings(FILE *out);
+
+
+// Graphically and textually show all the contexts.
+// Must be called on the Starcon2Main thread.
+void debugContexts (void);
+
+
+// To add some day:
+// - a function to fast forward the game clock to a specifiable time.
+
+#endif /* _DEBUG_H */
+
diff --git a/src/uqm/util.c b/src/uqm/util.c
new file mode 100644
index 0000000..aee73a6
--- /dev/null
+++ b/src/uqm/util.c
@@ -0,0 +1,312 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "controls.h"
+#include "util.h"
+#include "setup.h"
+#include "units.h"
+#include "settings.h"
+#include "libs/inplib.h"
+#include "libs/sound/trackplayer.h"
+#include "libs/mathlib.h"
+#include "libs/log.h"
+
+
+void
+DrawStarConBox (RECT *pRect, SIZE BorderWidth, Color TopLeftColor,
+ Color BottomRightColor, BOOLEAN FillInterior, Color InteriorColor)
+{
+ RECT locRect;
+
+ if (BorderWidth == 0)
+ BorderWidth = 2;
+ else
+ {
+ SetContextForeGroundColor (TopLeftColor);
+ locRect.corner = pRect->corner;
+ locRect.extent.width = pRect->extent.width;
+ locRect.extent.height = 1;
+ DrawFilledRectangle (&locRect);
+ if (BorderWidth == 2)
+ {
+ ++locRect.corner.x;
+ ++locRect.corner.y;
+ locRect.extent.width -= 2;
+ DrawFilledRectangle (&locRect);
+ }
+
+ locRect.corner = pRect->corner;
+ locRect.extent.width = 1;
+ locRect.extent.height = pRect->extent.height;
+ DrawFilledRectangle (&locRect);
+ if (BorderWidth == 2)
+ {
+ ++locRect.corner.x;
+ ++locRect.corner.y;
+ locRect.extent.height -= 2;
+ DrawFilledRectangle (&locRect);
+ }
+
+ SetContextForeGroundColor (BottomRightColor);
+ locRect.corner.x = pRect->corner.x + pRect->extent.width - 1;
+ locRect.corner.y = pRect->corner.y + 1;
+ locRect.extent.height = pRect->extent.height - 1;
+ DrawFilledRectangle (&locRect);
+ if (BorderWidth == 2)
+ {
+ --locRect.corner.x;
+ ++locRect.corner.y;
+ locRect.extent.height -= 2;
+ DrawFilledRectangle (&locRect);
+ }
+
+ locRect.corner.x = pRect->corner.x;
+ locRect.extent.width = pRect->extent.width;
+ locRect.corner.y = pRect->corner.y + pRect->extent.height - 1;
+ locRect.extent.height = 1;
+ DrawFilledRectangle (&locRect);
+ if (BorderWidth == 2)
+ {
+ ++locRect.corner.x;
+ --locRect.corner.y;
+ locRect.extent.width -= 2;
+ DrawFilledRectangle (&locRect);
+ }
+ }
+
+ if (FillInterior)
+ {
+ SetContextForeGroundColor (InteriorColor);
+ locRect.corner.x = pRect->corner.x + BorderWidth;
+ locRect.corner.y = pRect->corner.y + BorderWidth;
+ locRect.extent.width = pRect->extent.width - (BorderWidth << 1);
+ locRect.extent.height = pRect->extent.height - (BorderWidth << 1);
+ DrawFilledRectangle (&locRect);
+ }
+}
+
+DWORD
+SeedRandomNumbers (void)
+{
+ DWORD cur_time;
+
+ cur_time = GetTimeCounter ();
+ TFB_SeedRandom (cur_time);
+
+ return (cur_time);
+}
+
+STAMP
+SaveContextFrame (const RECT *saveRect)
+{
+ STAMP s;
+
+ if (saveRect)
+ { // a portion of the context
+ s.origin = saveRect->corner;
+ }
+ else
+ { // the entire context
+ s.origin.x = 0;
+ s.origin.y = 0;
+ }
+
+ s.frame = CaptureDrawable (CopyContextRect (saveRect));
+
+ return s;
+}
+
+BOOLEAN
+PauseGame (void)
+{
+ RECT r;
+ STAMP s;
+ CONTEXT OldContext;
+ STAMP saveStamp;
+ RECT ctxRect;
+ POINT oldOrigin;
+ RECT OldRect;
+
+ if (ActivityFrame == 0
+ || (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_PAUSE))
+ || (LastActivity & (CHECK_LOAD | CHECK_RESTART)))
+ return (FALSE);
+
+ GLOBAL (CurrentActivity) |= CHECK_PAUSE;
+
+ if (PlayingTrack ())
+ PauseTrack ();
+
+ OldContext = SetContext (ScreenContext);
+ oldOrigin = SetContextOrigin (MAKE_POINT (0, 0));
+ GetContextClipRect (&OldRect);
+ SetContextClipRect (NULL);
+
+ GetContextClipRect (&ctxRect);
+ GetFrameRect (ActivityFrame, &r);
+ r.corner.x = (ctxRect.extent.width - r.extent.width) >> 1;
+ r.corner.y = (ctxRect.extent.height - r.extent.height) >> 1;
+ saveStamp = SaveContextFrame (&r);
+
+ // TODO: This should draw a localizable text message instead
+ s.origin = r.corner;
+ s.frame = ActivityFrame;
+ SetSystemRect (&r);
+ DrawStamp (&s);
+
+ FlushGraphics ();
+
+ while (ImmediateInputState.menu[KEY_PAUSE] && GamePaused)
+ {
+ BeginInputFrame ();
+ TaskSwitch ();
+ }
+
+ while (!ImmediateInputState.menu[KEY_PAUSE] && GamePaused)
+ {
+ BeginInputFrame ();
+ TaskSwitch ();
+ }
+
+ while (ImmediateInputState.menu[KEY_PAUSE] && GamePaused)
+ {
+ BeginInputFrame ();
+ TaskSwitch ();
+ }
+
+ GamePaused = FALSE;
+
+ DrawStamp (&saveStamp);
+ DestroyDrawable (ReleaseDrawable (saveStamp.frame));
+ ClearSystemRect ();
+
+ SetContextClipRect (&OldRect);
+ SetContextOrigin (oldOrigin);
+ SetContext (OldContext);
+
+ WaitForNoInput (ONE_SECOND / 4, TRUE);
+
+ if (PlayingTrack ())
+ ResumeTrack ();
+
+
+ TaskSwitch ();
+ GLOBAL (CurrentActivity) &= ~CHECK_PAUSE;
+ return (TRUE);
+}
+
+// Waits for a button to be pressed
+// Returns TRUE if the wait succeeded (found input)
+// FALSE if timed out or game aborted
+BOOLEAN
+WaitForAnyButtonUntil (BOOLEAN newButton, TimeCount timeOut,
+ BOOLEAN resetInput)
+{
+ BOOLEAN buttonPressed;
+
+ if (newButton && !WaitForNoInputUntil (timeOut, FALSE))
+ return FALSE;
+
+ buttonPressed = AnyButtonPress (TRUE);
+ while (!buttonPressed
+ && (timeOut == WAIT_INFINITE || GetTimeCounter () < timeOut)
+ && !(GLOBAL (CurrentActivity) & CHECK_ABORT)
+ && !QuitPosted)
+ {
+ SleepThread (ONE_SECOND / 40);
+ buttonPressed = AnyButtonPress (TRUE);
+ }
+
+ if (resetInput)
+ FlushInput ();
+
+ return buttonPressed;
+}
+
+BOOLEAN
+WaitForAnyButton (BOOLEAN newButton, TimePeriod duration, BOOLEAN resetInput)
+{
+ TimeCount timeOut = duration;
+ if (duration != WAIT_INFINITE)
+ timeOut += GetTimeCounter ();
+ return WaitForAnyButtonUntil (newButton, timeOut, resetInput);
+}
+
+// Returns TRUE if the wait succeeded (found no input)
+// FALSE if timed out or game aborted
+BOOLEAN
+WaitForNoInputUntil (TimeCount timeOut, BOOLEAN resetInput)
+{
+ BOOLEAN buttonPressed;
+
+ buttonPressed = AnyButtonPress (TRUE);
+ while (buttonPressed
+ && (timeOut == WAIT_INFINITE || GetTimeCounter () < timeOut)
+ && !(GLOBAL (CurrentActivity) & CHECK_ABORT)
+ && !QuitPosted)
+ {
+ SleepThread (ONE_SECOND / 40);
+ buttonPressed = AnyButtonPress (TRUE);
+ }
+
+ if (resetInput)
+ FlushInput ();
+
+ return !buttonPressed;
+}
+
+BOOLEAN
+WaitForNoInput (TimePeriod duration, BOOLEAN resetInput)
+{
+ TimeCount timeOut = duration;
+ if (duration != WAIT_INFINITE)
+ timeOut += GetTimeCounter ();
+ return WaitForNoInputUntil (timeOut, resetInput);
+}
+
+// Stops game clock and music thread and minimizes interrupts/cycles
+// based on value of global GameActive variable
+// See similar sleep state for main thread in uqm.c:main()
+void
+SleepGame (void)
+{
+ if (QuitPosted)
+ return; // Do not sleep the game when already asked to quit
+
+ log_add (log_Debug, "Game is going to sleep");
+
+ if (PlayingTrack ())
+ PauseTrack ();
+ PauseMusic ();
+
+
+ while (!GameActive && !QuitPosted)
+ SleepThread (ONE_SECOND / 2);
+
+ log_add (log_Debug, "Game is waking up");
+
+ WaitForNoInput (ONE_SECOND / 10, TRUE);
+
+ ResumeMusic ();
+
+ if (PlayingTrack ())
+ ResumeTrack ();
+
+
+ TaskSwitch ();
+}
diff --git a/src/uqm/util.h b/src/uqm/util.h
new file mode 100644
index 0000000..0949809
--- /dev/null
+++ b/src/uqm/util.h
@@ -0,0 +1,39 @@
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_UTIL_H_
+#define UQM_UTIL_H_
+
+#include "libs/compiler.h"
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern void DrawStarConBox (RECT *pRect, SIZE BorderWidth,
+ Color TopLeftColor, Color BottomRightColor, BOOLEAN FillInterior,
+ Color InteriorColor);
+extern DWORD SeedRandomNumbers (void);
+
+// saveRect can be NULL to save the entire context frame
+extern STAMP SaveContextFrame (const RECT *saveRect);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_UTIL_H_ */
diff --git a/src/uqm/velocity.c b/src/uqm/velocity.c
new file mode 100644
index 0000000..5e39f02
--- /dev/null
+++ b/src/uqm/velocity.c
@@ -0,0 +1,153 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "velocity.h"
+
+#include "units.h"
+#include "libs/compiler.h"
+
+
+#define VELOCITY_REMAINDER(v) ((v) & (VELOCITY_SCALE - 1))
+
+void
+GetCurrentVelocityComponents (VELOCITY_DESC *velocityptr, SIZE *pdx, SIZE *pdy)
+{
+ *pdx = WORLD_TO_VELOCITY (velocityptr->vector.width)
+ + (velocityptr->fract.width - (SIZE)HIBYTE (velocityptr->incr.width));
+ *pdy = WORLD_TO_VELOCITY (velocityptr->vector.height)
+ + (velocityptr->fract.height - (SIZE)HIBYTE (velocityptr->incr.height));
+}
+
+void
+GetNextVelocityComponents (VELOCITY_DESC *velocityptr, SIZE *pdx, SIZE *pdy,
+ COUNT num_frames)
+{
+ COUNT e;
+
+ e = (COUNT)((COUNT)velocityptr->error.width +
+ ((COUNT)velocityptr->fract.width * num_frames));
+ *pdx = (velocityptr->vector.width * num_frames)
+ + ((SIZE)((SBYTE)LOBYTE (velocityptr->incr.width))
+ * (e >> VELOCITY_SHIFT));
+ velocityptr->error.width = VELOCITY_REMAINDER (e);
+
+ e = (COUNT)((COUNT)velocityptr->error.height +
+ ((COUNT)velocityptr->fract.height * num_frames));
+ *pdy = (velocityptr->vector.height * num_frames)
+ + ((SIZE)((SBYTE)LOBYTE (velocityptr->incr.height))
+ * (e >> VELOCITY_SHIFT));
+ velocityptr->error.height = VELOCITY_REMAINDER (e);
+}
+
+void
+SetVelocityVector (VELOCITY_DESC *velocityptr, SIZE magnitude, COUNT facing)
+{
+ COUNT angle;
+ SIZE dx, dy;
+
+ angle = velocityptr->TravelAngle =
+ FACING_TO_ANGLE (NORMALIZE_FACING (facing));
+ magnitude = WORLD_TO_VELOCITY (magnitude);
+ dx = COSINE (angle, magnitude);
+ dy = SINE (angle, magnitude);
+ if (dx >= 0)
+ {
+ velocityptr->vector.width = VELOCITY_TO_WORLD (dx);
+ velocityptr->incr.width = MAKE_WORD ((BYTE)1, (BYTE)0);
+ }
+ else
+ {
+ dx = -dx;
+ velocityptr->vector.width = -VELOCITY_TO_WORLD (dx);
+ velocityptr->incr.width =
+ MAKE_WORD ((BYTE)0xFF, (BYTE)(VELOCITY_REMAINDER (dx) << 1));
+ }
+ if (dy >= 0)
+ {
+ velocityptr->vector.height = VELOCITY_TO_WORLD (dy);
+ velocityptr->incr.height = MAKE_WORD ((BYTE)1, (BYTE)0);
+ }
+ else
+ {
+ dy = -dy;
+ velocityptr->vector.height = -VELOCITY_TO_WORLD (dy);
+ velocityptr->incr.height =
+ MAKE_WORD ((BYTE)0xFF, (BYTE)(VELOCITY_REMAINDER (dy) << 1));
+ }
+
+ velocityptr->fract.width = VELOCITY_REMAINDER (dx);
+ velocityptr->fract.height = VELOCITY_REMAINDER (dy);
+ velocityptr->error.width = velocityptr->error.height = 0;
+}
+
+void
+SetVelocityComponents (VELOCITY_DESC *velocityptr, SIZE dx, SIZE dy)
+{
+ COUNT angle;
+
+ if ((angle = ARCTAN (dx, dy)) == FULL_CIRCLE)
+ {
+ ZeroVelocityComponents (velocityptr);
+ }
+ else
+ {
+ if (dx >= 0)
+ {
+ velocityptr->vector.width = VELOCITY_TO_WORLD (dx);
+ velocityptr->incr.width = MAKE_WORD ((BYTE)1, (BYTE)0);
+ }
+ else
+ {
+ dx = -dx;
+ velocityptr->vector.width = -VELOCITY_TO_WORLD (dx);
+ velocityptr->incr.width =
+ MAKE_WORD ((BYTE)0xFF, (BYTE)(VELOCITY_REMAINDER (dx) << 1));
+ }
+ if (dy >= 0)
+ {
+ velocityptr->vector.height = VELOCITY_TO_WORLD (dy);
+ velocityptr->incr.height = MAKE_WORD ((BYTE)1, (BYTE)0);
+ }
+ else
+ {
+ dy = -dy;
+ velocityptr->vector.height = -VELOCITY_TO_WORLD (dy);
+ velocityptr->incr.height =
+ MAKE_WORD ((BYTE)0xFF, (BYTE)(VELOCITY_REMAINDER (dy) << 1));
+ }
+
+ velocityptr->fract.width = VELOCITY_REMAINDER (dx);
+ velocityptr->fract.height = VELOCITY_REMAINDER (dy);
+ velocityptr->error.width = velocityptr->error.height = 0;
+ }
+
+ velocityptr->TravelAngle = angle;
+}
+
+void
+DeltaVelocityComponents (VELOCITY_DESC *velocityptr, SIZE dx, SIZE dy)
+{
+
+ dx += WORLD_TO_VELOCITY (velocityptr->vector.width)
+ + (velocityptr->fract.width - (SIZE)HIBYTE (velocityptr->incr.width));
+ dy += WORLD_TO_VELOCITY (velocityptr->vector.height)
+ + (velocityptr->fract.height - (SIZE)HIBYTE (velocityptr->incr.height));
+
+ SetVelocityComponents (velocityptr, dx, dy);
+}
+
diff --git a/src/uqm/velocity.h b/src/uqm/velocity.h
new file mode 100644
index 0000000..968e1f7
--- /dev/null
+++ b/src/uqm/velocity.h
@@ -0,0 +1,76 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_VELOCITY_H_
+#define UQM_VELOCITY_H_
+
+#include <string.h> /* for memset */
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct velocity_desc
+{
+ COUNT TravelAngle;
+ EXTENT vector;
+ EXTENT fract;
+ EXTENT error;
+ EXTENT incr;
+} VELOCITY_DESC;
+
+#define ZeroVelocityComponents(pv) memset(pv,0,sizeof (*(pv)))
+#define GetVelocityTravelAngle(pv) (pv)->TravelAngle
+
+extern void GetCurrentVelocityComponents (VELOCITY_DESC *velocityptr,
+ SIZE *pdx, SIZE *pdy);
+extern void GetNextVelocityComponents (VELOCITY_DESC *velocityptr,
+ SIZE *pdx, SIZE *pdy, COUNT num_frames);
+extern void SetVelocityVector (VELOCITY_DESC *velocityptr, SIZE magnitude,
+ COUNT facing);
+extern void SetVelocityComponents (VELOCITY_DESC *velocityptr, SIZE dx,
+ SIZE dy);
+extern void DeltaVelocityComponents (VELOCITY_DESC *velocityptr, SIZE dx,
+ SIZE dy);
+
+static inline bool
+IsVelocityZero (VELOCITY_DESC *vptr)
+{
+ return vptr->vector.width == 0 && vptr->vector.height == 0 &&
+ vptr->incr.width == 0 && vptr->incr.height == 0 &&
+ vptr->fract.width == 0 && vptr->fract.height == 0;
+}
+
+static inline DWORD
+VelocitySquared (SIZE dx, SIZE dy)
+{
+ return (DWORD)((long)dx * dx + (long)dy * dy);
+}
+
+#define VELOCITY_SHIFT 5
+#define VELOCITY_SCALE (1<<VELOCITY_SHIFT)
+
+#define VELOCITY_TO_WORLD(v) ((v)>>VELOCITY_SHIFT)
+#define WORLD_TO_VELOCITY(l) ((l)<<VELOCITY_SHIFT)
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_VELOCITY_H_ */
diff --git a/src/uqm/weapon.c b/src/uqm/weapon.c
new file mode 100644
index 0000000..3f88f98
--- /dev/null
+++ b/src/uqm/weapon.c
@@ -0,0 +1,414 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "weapon.h"
+
+#include "colors.h"
+#include "globdata.h"
+#include "status.h"
+#include "init.h"
+#include "races.h"
+#include "ship.h"
+#include "setup.h"
+#include "sounds.h"
+#include "units.h"
+#include "libs/mathlib.h"
+
+#include <stdio.h>
+
+// A wrapper function for weapon_collision that discards the return value.
+// This makes its signature match ElementCollisionFunc.
+static void
+weapon_collision_cb (ELEMENT *WeaponElementPtr, POINT *pWPt,
+ ELEMENT *HitElementPtr, POINT *pHPt)
+{
+ weapon_collision (WeaponElementPtr, pWPt, HitElementPtr, pHPt);
+}
+
+
+HELEMENT
+initialize_laser (LASER_BLOCK *pLaserBlock)
+{
+ HELEMENT hLaserElement;
+
+ hLaserElement = AllocElement ();
+ if (hLaserElement)
+ {
+#define LASER_LIFE 1
+ ELEMENT *LaserElementPtr;
+
+ LockElement (hLaserElement, &LaserElementPtr);
+ LaserElementPtr->playerNr = pLaserBlock->sender;
+ LaserElementPtr->hit_points = 1;
+ LaserElementPtr->mass_points = 1;
+ LaserElementPtr->state_flags = APPEARING | FINITE_LIFE
+ | pLaserBlock->flags;
+ LaserElementPtr->life_span = LASER_LIFE;
+ LaserElementPtr->collision_func = weapon_collision_cb;
+ LaserElementPtr->blast_offset = 1;
+
+ LaserElementPtr->current.location.x = pLaserBlock->cx
+ + COSINE (FACING_TO_ANGLE (pLaserBlock->face),
+ DISPLAY_TO_WORLD (pLaserBlock->pixoffs));
+ LaserElementPtr->current.location.y = pLaserBlock->cy
+ + SINE (FACING_TO_ANGLE (pLaserBlock->face),
+ DISPLAY_TO_WORLD (pLaserBlock->pixoffs));
+ SetPrimType (&DisplayArray[LaserElementPtr->PrimIndex], LINE_PRIM);
+ SetPrimColor (&DisplayArray[LaserElementPtr->PrimIndex],
+ pLaserBlock->color);
+ LaserElementPtr->current.image.frame = DecFrameIndex (stars_in_space);
+ LaserElementPtr->current.image.farray = &stars_in_space;
+ SetVelocityComponents (&LaserElementPtr->velocity,
+ WORLD_TO_VELOCITY ((pLaserBlock->cx + pLaserBlock->ex)
+ - LaserElementPtr->current.location.x),
+ WORLD_TO_VELOCITY ((pLaserBlock->cy + pLaserBlock->ey)
+ - LaserElementPtr->current.location.y));
+ UnlockElement (hLaserElement);
+ }
+
+ return (hLaserElement);
+}
+
+HELEMENT
+initialize_missile (MISSILE_BLOCK *pMissileBlock)
+{
+ HELEMENT hMissileElement;
+
+ hMissileElement = AllocElement ();
+ if (hMissileElement)
+ {
+ SIZE delta_x, delta_y;
+ COUNT angle;
+ ELEMENT *MissileElementPtr;
+
+ LockElement (hMissileElement, &MissileElementPtr);
+ MissileElementPtr->hit_points = (BYTE)pMissileBlock->hit_points;
+ MissileElementPtr->mass_points = (BYTE)pMissileBlock->damage;
+ MissileElementPtr->playerNr = pMissileBlock->sender;
+ MissileElementPtr->state_flags = APPEARING | FINITE_LIFE
+ | pMissileBlock->flags;
+ MissileElementPtr->life_span = pMissileBlock->life;
+ SetPrimType (&DisplayArray[MissileElementPtr->PrimIndex], STAMP_PRIM);
+ MissileElementPtr->current.image.farray = pMissileBlock->farray;
+ MissileElementPtr->current.image.frame =
+ SetAbsFrameIndex (pMissileBlock->farray[0],
+ pMissileBlock->index);
+ MissileElementPtr->preprocess_func = pMissileBlock->preprocess_func;
+ MissileElementPtr->collision_func = weapon_collision_cb;
+ MissileElementPtr->blast_offset = (BYTE)pMissileBlock->blast_offs;
+
+ angle = FACING_TO_ANGLE (pMissileBlock->face);
+ MissileElementPtr->current.location.x = pMissileBlock->cx
+ + COSINE (angle, DISPLAY_TO_WORLD (pMissileBlock->pixoffs));
+ MissileElementPtr->current.location.y = pMissileBlock->cy
+ + SINE (angle, DISPLAY_TO_WORLD (pMissileBlock->pixoffs));
+
+ delta_x = COSINE (angle, WORLD_TO_VELOCITY (pMissileBlock->speed));
+ delta_y = SINE (angle, WORLD_TO_VELOCITY (pMissileBlock->speed));
+ SetVelocityComponents (&MissileElementPtr->velocity,
+ delta_x, delta_y);
+
+ MissileElementPtr->current.location.x -= VELOCITY_TO_WORLD (delta_x);
+ MissileElementPtr->current.location.y -= VELOCITY_TO_WORLD (delta_y);
+ UnlockElement (hMissileElement);
+ }
+
+ return (hMissileElement);
+}
+
+HELEMENT
+weapon_collision (ELEMENT *WeaponElementPtr, POINT *pWPt,
+ ELEMENT *HitElementPtr, POINT *pHPt)
+{
+ SIZE damage;
+ HELEMENT hBlastElement;
+
+ if (WeaponElementPtr->state_flags & COLLISION) /* if already did effect */
+ return ((HELEMENT)0);
+
+ damage = (SIZE)WeaponElementPtr->mass_points;
+ if (damage
+ && ((HitElementPtr->state_flags & FINITE_LIFE)
+ || HitElementPtr->life_span == NORMAL_LIFE))
+#ifdef NEVER
+ &&
+ /* lasers from the same ship can't hit each other */
+ (GetPrimType (&DisplayArray[HitElementPtr->PrimIndex]) != LINE_PRIM
+ || GetPrimType (&DisplayArray[WeaponElementPtr->PrimIndex]) != LINE_PRIM
+ || !elementsOfSamePlayer (HitElementPtr, WeaponElementPtr)))
+#endif /* NEVER */
+ {
+ do_damage (HitElementPtr, damage);
+ if (HitElementPtr->hit_points)
+ WeaponElementPtr->state_flags |= COLLISION;
+ }
+
+ if (!(HitElementPtr->state_flags & FINITE_LIFE)
+ || (!(/* WeaponElementPtr->state_flags
+ & */ HitElementPtr->state_flags & COLLISION)
+ && WeaponElementPtr->hit_points <= HitElementPtr->mass_points))
+ {
+ if (damage)
+ {
+ damage = TARGET_DAMAGED_FOR_1_PT + (damage >> 1);
+ if (damage > TARGET_DAMAGED_FOR_6_PLUS_PT)
+ damage = TARGET_DAMAGED_FOR_6_PLUS_PT;
+ ProcessSound (SetAbsSoundIndex (GameSounds, damage),
+ HitElementPtr);
+ }
+
+ if (GetPrimType (&DisplayArray[WeaponElementPtr->PrimIndex])
+ != LINE_PRIM)
+ WeaponElementPtr->state_flags |= DISAPPEARING;
+
+ WeaponElementPtr->hit_points = 0;
+ WeaponElementPtr->life_span = 0;
+ WeaponElementPtr->state_flags |= COLLISION | NONSOLID;
+
+ hBlastElement = AllocElement ();
+ if (hBlastElement)
+ {
+ COUNT blast_index;
+ SIZE blast_offs;
+ COUNT angle, num_blast_frames;
+ ELEMENT *BlastElementPtr;
+ extern FRAME blast[];
+
+ PutElement (hBlastElement);
+ LockElement (hBlastElement, &BlastElementPtr);
+ BlastElementPtr->playerNr = WeaponElementPtr->playerNr;
+ BlastElementPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ SetPrimType (&DisplayArray[BlastElementPtr->PrimIndex], STAMP_PRIM);
+
+ BlastElementPtr->current.location.x = DISPLAY_TO_WORLD (pWPt->x);
+ BlastElementPtr->current.location.y = DISPLAY_TO_WORLD (pWPt->y);
+
+ angle = GetVelocityTravelAngle (&WeaponElementPtr->velocity);
+ if ((blast_offs = WeaponElementPtr->blast_offset) > 0)
+ {
+ BlastElementPtr->current.location.x +=
+ COSINE (angle, DISPLAY_TO_WORLD (blast_offs));
+ BlastElementPtr->current.location.y +=
+ SINE (angle, DISPLAY_TO_WORLD (blast_offs));
+ }
+
+ blast_index =
+ NORMALIZE_FACING (ANGLE_TO_FACING (angle + HALF_CIRCLE));
+ blast_index = ((blast_index >> 2) << 1) +
+ (blast_index & 0x3 ? 1 : 0);
+
+ num_blast_frames =
+ GetFrameCount (WeaponElementPtr->next.image.frame);
+ if (num_blast_frames <= ANGLE_TO_FACING (FULL_CIRCLE))
+ {
+ BlastElementPtr->life_span = 2;
+ BlastElementPtr->current.image.farray = blast;
+ BlastElementPtr->current.image.frame =
+ SetAbsFrameIndex (blast[0], blast_index);
+ }
+ else
+ {
+ BlastElementPtr->life_span = num_blast_frames
+ - ANGLE_TO_FACING (FULL_CIRCLE);
+ BlastElementPtr->turn_wait = BlastElementPtr->next_turn = 0;
+ BlastElementPtr->preprocess_func = animation_preprocess;
+ BlastElementPtr->current.image.farray =
+ WeaponElementPtr->next.image.farray;
+ BlastElementPtr->current.image.frame =
+ SetAbsFrameIndex (
+ BlastElementPtr->current.image.farray[0],
+ ANGLE_TO_FACING (FULL_CIRCLE));
+ }
+
+ UnlockElement (hBlastElement);
+
+ return (hBlastElement);
+ }
+ }
+
+ (void) pHPt; /* Satisfying compiler (unused parameter) */
+ return ((HELEMENT)0);
+}
+
+FRAME
+ModifySilhouette (ELEMENT *ElementPtr, STAMP *modify_stamp,
+ BYTE modify_flags)
+{
+ FRAME f;
+ RECT r, or;
+ INTERSECT_CONTROL ShipIntersect, ObjectIntersect;
+ STARSHIP *StarShipPtr;
+ CONTEXT OldContext;
+
+ f = 0;
+ ObjectIntersect.IntersectStamp = *modify_stamp;
+ GetFrameRect (ObjectIntersect.IntersectStamp.frame, &or);
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ if (modify_flags & MODIFY_IMAGE)
+ {
+ ShipIntersect.IntersectStamp.frame = SetAbsFrameIndex (
+ StarShipPtr->RaceDescPtr->ship_info.icons, 1);
+ if (ShipIntersect.IntersectStamp.frame == 0)
+ return (0);
+
+ GetFrameRect (ShipIntersect.IntersectStamp.frame, &r);
+
+ ShipIntersect.IntersectStamp.origin.x = 0;
+ ShipIntersect.IntersectStamp.origin.y = 0;
+ ShipIntersect.EndPoint = ShipIntersect.IntersectStamp.origin;
+ do
+ {
+ ObjectIntersect.IntersectStamp.origin.x = ((COUNT)TFB_Random ()
+ % (r.extent.width - or.extent.width))
+ + ((or.extent.width - r.extent.width) >> 1);
+ ObjectIntersect.IntersectStamp.origin.y = ((COUNT)TFB_Random ()
+ % (r.extent.height - or.extent.height))
+ + ((or.extent.height - r.extent.height) >> 1);
+ ObjectIntersect.EndPoint = ObjectIntersect.IntersectStamp.origin;
+ } while (!DrawablesIntersect (&ObjectIntersect,
+ &ShipIntersect, MAX_TIME_VALUE));
+
+ ObjectIntersect.IntersectStamp.origin.x += STATUS_WIDTH >> 1;
+ ObjectIntersect.IntersectStamp.origin.y += 31;
+ }
+
+ ObjectIntersect.IntersectStamp.origin.y +=
+ status_y_offsets[ElementPtr->playerNr];
+
+ if (modify_flags & MODIFY_SWAP)
+ {
+ or.corner.x += ObjectIntersect.IntersectStamp.origin.x;
+ or.corner.y += ObjectIntersect.IntersectStamp.origin.y;
+ InitShipStatus (&StarShipPtr->RaceDescPtr->ship_info,
+ StarShipPtr, &or);
+ }
+ else
+ {
+ OldContext = SetContext (StatusContext);
+ DrawStamp (&ObjectIntersect.IntersectStamp);
+ SetContext (OldContext);
+ }
+
+ return (f);
+}
+
+// Find the closest possible target ship, to be set in Tracker->hTarget.
+// *pfacing will be turned one angle unit into the direction towards the
+// target.
+// The return value will be the actual number of angle units to turn, or
+// -1 if no target was found.
+// Cloaked ships won't be detected, except when the APPEARING flag is
+// set for the Tracker.
+SIZE
+TrackShip (ELEMENT *Tracker, COUNT *pfacing)
+{
+ SIZE best_delta_facing, best_delta;
+ HELEMENT hShip, hNextShip;
+ ELEMENT *Trackee;
+
+ best_delta = 0;
+ best_delta_facing = -1;
+
+ hShip = Tracker->hTarget;
+ if (hShip)
+ {
+ LockElement (hShip, &Trackee);
+ Tracker->hTarget = hNextShip = 0;
+
+ goto CheckTracking;
+ }
+
+ for (hShip = GetHeadElement (); hShip != 0; hShip = hNextShip)
+ {
+ LockElement (hShip, &Trackee);
+ hNextShip = GetSuccElement (Trackee);
+ if ((Trackee->state_flags & PLAYER_SHIP)
+ && !elementsOfSamePlayer (Trackee, Tracker)
+ && (!OBJECT_CLOAKED (Trackee)
+ || ((Tracker->state_flags & PLAYER_SHIP)
+ && (Tracker->state_flags & APPEARING))
+ ))
+ {
+ STARSHIP *StarShipPtr;
+
+CheckTracking:
+ GetElementStarShip (Trackee, &StarShipPtr);
+ if (Trackee->life_span
+ && StarShipPtr->RaceDescPtr->ship_info.crew_level)
+ {
+ SIZE delta_x, delta_y, delta_facing;
+
+ if (Tracker->state_flags & PRE_PROCESS)
+ {
+ delta_x = Trackee->next.location.x
+ - Tracker->next.location.x;
+ delta_y = Trackee->next.location.y
+ - Tracker->next.location.y;
+ }
+ else
+ {
+ delta_x = Trackee->current.location.x
+ - Tracker->current.location.x;
+ delta_y = Trackee->current.location.y
+ - Tracker->current.location.y;
+ }
+
+ delta_x = WRAP_DELTA_X (delta_x);
+ delta_y = WRAP_DELTA_Y (delta_y);
+ delta_facing = NORMALIZE_FACING (
+ ANGLE_TO_FACING (ARCTAN (delta_x, delta_y)) - *pfacing
+ );
+
+ if (delta_x < 0)
+ delta_x = -delta_x;
+ if (delta_y < 0)
+ delta_y = -delta_y;
+ delta_x += delta_y;
+ // 'delta_x + delta_y' is used as an approximation
+ // of the actual distance 'sqrt(sqr(delta_x) +
+ // sqr(delta_y))'.
+
+ if (best_delta == 0 || delta_x < best_delta)
+ {
+ best_delta = delta_x;
+ best_delta_facing = delta_facing;
+ Tracker->hTarget = hShip;
+ }
+ }
+ }
+ UnlockElement (hShip);
+ }
+
+ if (best_delta_facing > 0)
+ {
+ COUNT facing;
+
+ facing = *pfacing;
+ if (best_delta_facing == ANGLE_TO_FACING (HALF_CIRCLE))
+ facing += (((BYTE)TFB_Random () & 1) << 1) - 1;
+ else if (best_delta_facing < ANGLE_TO_FACING (HALF_CIRCLE))
+ ++facing;
+ else
+ --facing;
+ *pfacing = NORMALIZE_FACING (facing);
+ }
+
+ return (best_delta_facing);
+}
+
diff --git a/src/uqm/weapon.h b/src/uqm/weapon.h
new file mode 100644
index 0000000..128d71c
--- /dev/null
+++ b/src/uqm/weapon.h
@@ -0,0 +1,68 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_WEAPON_H_
+#define UQM_WEAPON_H_
+
+#include "element.h"
+#include "libs/gfxlib.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct
+{
+ COORD cx, cy, ex, ey;
+ ELEMENT_FLAGS flags;
+ SIZE sender; // player number
+ SIZE pixoffs;
+ COUNT face;
+ Color color;
+} LASER_BLOCK;
+
+typedef struct
+{
+ COORD cx, cy;
+ ELEMENT_FLAGS flags;
+ SIZE sender; // player number
+ SIZE pixoffs, speed, hit_points, damage;
+ COUNT face, index, life;
+ FRAME *farray;
+ void (*preprocess_func) (ELEMENT *ElementPtr);
+ SIZE blast_offs;
+} MISSILE_BLOCK;
+
+extern HELEMENT initialize_laser (LASER_BLOCK *pLaserBlock);
+extern HELEMENT initialize_missile (MISSILE_BLOCK *pMissileBlock);
+extern HELEMENT weapon_collision (ELEMENT *ElementPtr0, POINT *pPt0,
+ ELEMENT *ElementPtr1, POINT *pPt1);
+extern SIZE TrackShip (ELEMENT *Tracker, COUNT *pfacing);
+extern void Untarget (ELEMENT *ElementPtr);
+
+#define MODIFY_IMAGE (1 << 0)
+#define MODIFY_SWAP (1 << 1)
+
+extern FRAME ModifySilhouette (ELEMENT *ElementPtr, STAMP *modify_stamp,
+ BYTE modify_flags);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_WEAPON_H_ */